{-|
Module      : MZinHaskell
Description : Integration of MiniZinc 2.0 in Haskell
Copyright   : (c) Some Guy, 2013
                  Someone Else, 2014
License     : BSD3
Maintainer  : Klara Marntirosian <klara.mar@cs.kuleuven.be>
Stability   : experimental

This module provides IO functionality for running the Haskell representation of a
MiniZinc model and getting back the solutions in Haskell values.
-}

module Interfaces.MZinHaskell (
  iTestModel,
  testModel,
  testModelWithData,
  testModelWithParser,
  writeData
) where

import Data.List
import System.Process
import System.FilePath
import Interfaces.MZAuxiliary
import Interfaces.MZASTBase (MZModel, Item(Comment))
import Interfaces.MZPrinter
import Interfaces.FZSolutionParser (Solution, tryDefaultSolutions, getSolutions)
import Text.Parsec.Error
import Text.Parsec.String (Parser)

-- | Same as `testModel` but accepts one more argument for the data of the model.
testModelWithData
  :: MZModel  -- ^ The model
  -> MZModel  -- ^ The data to be used by the model
  -> FilePath -- ^ Path of the file in which the FlatZinc translation will be printed (without ".fzn" extension)
  -> Int      -- ^ Chosen solver (@1@ for the G12/FD built-in solver or @2@ for choco3)
  -> Int      -- ^ Number of solutions to be returned
  -> IO (Either ParseError [Solution])
testModelWithData model mdata path solver num = 
  let fdata = [Comment "Model\'s data"] ++ mdata ++ [Comment "End of model\'s data"]
  in testModel (fdata ++ model) path solver num

-- | Same as `testModel`, but interactive.
-- 
-- Interactively runs a constraint model and outputs its solution(s). The 
-- function first prompts the user for the working directory: the FlatZinc file 
-- will be created in that directory. Then, for a name for the constraint model: 
-- the created FlatZinc file will be named after this. Also asks the user to 
-- choose between supported solvers and the desired number of solutions. Returns 
-- either a parse error or a list of solutions of the constraint model. The length 
-- of the list is at most equal to the number of solutions requested.
iTestModel :: MZModel -> IO (Either ParseError [Solution])
iTestModel m = do
  putStrLn "Enter working directory:"
  dirpath <- getLine
  putStr "Enter model\'s name: "
  name <- getLine
  putStr "Choose a solver from the list below:\r\n\t1. G12/FD\r\n\t2. choco3\r\n\r\nInteger value associated with the solver: "
  str_solver <- getLine
  putStr "Number of solutions to be returned: "
  str_ns <- getLine
  let solver = read str_solver
      ns = read str_ns
      path = joinPath [dirpath, name]
  testModel m path solver ns

-- | Runs a model and parses its solution(s). Use this function if the model contains no
-- @output@ item, so that the solutions have the default format.
testModel :: MZModel -- ^ The model
  -> FilePath        -- ^ The path of the file in which the FlatZinc translation will be printed (without ".fzn" extension)
  -> Int             -- ^ The chosen solver (@1@ for the G12/FD built-in solver or @2@ for choco3)
  -> Int             -- ^ The number of solutions to be returned
  -> IO (Either ParseError [Solution])
testModel = testModelWithParser tryDefaultSolutions

-- | Runs a model and parses its solution(s) with the use of the specified parser. Use
-- this function if the model outputs its solutions in a different format than the 
-- default.
testModelWithParser :: (Int -> Parser [Solution]) -- ^ The parser with which solutions will be
                                         -- parsed
                    -> MZModel           -- ^ The model
                    -> FilePath          -- ^ The path of the file in which the FlatZinc 
                                         -- translation will be printed (without ".fzn" 
                                         -- extension)
                    -> Int               -- ^ The chosen solver (@1@ for the G12/FD 
                                         -- built-in solver or @2@ for choco3)
                    -> Int               -- ^ The number of solutions to be returned
                    -> IO (Either ParseError [Solution])
testModelWithParser p m mpath s n = do
  configuration <- parseConfig
  let mz_dir = case minizinc configuration of
                ""  -> addTrailingPathSeparator "."
                str -> addTrailingPathSeparator str
  let mfzn = (spaceFix $ mz_dir ++ "mzn2fzn") ++ " -O- - -o " ++ (spaceFix (mpath ++ ".fzn"))
  let flatzinc = spaceFix $ mz_dir ++ "flatzinc"
  -- Uncomment line below for debugging
  -- writeFile (mpath ++ ".mzn") (layout m)
  readCreateProcess (shell mfzn) (layout m)
  res <- case s of
           1 -> readCreateProcess (shell $ flatzinc ++ " -a -b fd " ++ mpath ++ ".fzn") ""
           -- 1 -> readCreateProcess (shell $ flatzinc ++ " -a -b fd " ++ mpath ++ ".fzn > " ++ mpath ++ ".results.txt") ""
           2 -> let antlr       = antlr_path configuration
                    chocoParser = chocoparser configuration
                    chocoSolver = chocosolver configuration
                in readCreateProcess (shell $ "java -cp ." ++ (intercalate [searchPathSeparator] [chocoSolver, chocoParser, antlr]) ++ " org.chocosolver.parser.flatzinc.ChocoFZN -a " ++ mpath ++ ".fzn") ""
                -- in readCreateProcess (shell $ "java -cp ." ++ (intercalate [searchPathSeparator] [chocoSolver, chocoParser, antlr]) ++ " org.chocosolver.parser.flatzinc.ChocoFZN -a " ++ mpath ++ ".fzn > " ++ mpath ++ ".results.txt") ""
  -- Uncomment two lines below for debugging
  -- writeFile (mpath ++ ".results.txt") res
  -- getSolutionsFromFile (mpath ++ ".fzn.results.txt") n
  return $ getSolutions p n res

-- | Writes the model's data file. The 'MZModel' of the argument must contain
-- only 'Interfaces.MZASTBase.Assign' items.
writeData :: MZModel -> IO ()
writeData m = do
  putStrLn "Enter datafile's filepath:"
  datapath <- getLine
  writeFile datapath (Prelude.show $ printModel m)