{-# OPTIONS_GHC -Wall #-}
{-# LANGUAGE MultiParamTypeClasses #-}
module ToySolver.Data.MIP.Solver.CPLEX
  ( CPLEX (..)
  , cplex
  ) where

import Control.Monad
import Data.Default.Class
import Data.IORef
import qualified Data.Text.Lazy.IO as TLIO
import System.Exit
import System.IO
import System.IO.Temp
import qualified ToySolver.Data.MIP.Base as MIP
import qualified ToySolver.Data.MIP.LPFile as LPFile
import ToySolver.Data.MIP.Solver.Base
import qualified ToySolver.Data.MIP.Solution.CPLEX as CPLEXSol
import ToySolver.Internal.ProcessUtil (runProcessWithOutputCallback)

data CPLEX
  = CPLEX
  { cplexPath :: String
  }

instance Default CPLEX where
  def = cplex

cplex :: CPLEX
cplex = CPLEX "cplex"

instance IsSolver CPLEX IO where
  solve solver opt prob = do
    case LPFile.render def prob of
      Left err -> ioError $ userError err
      Right lp -> do
        withSystemTempFile "cplex.lp" $ \fname1 h1 -> do
          TLIO.hPutStr h1 lp
          hClose h1
          withSystemTempFile "cplex.sol" $ \fname2 h2 -> do
            hClose h2
            isInfeasibleRef <- newIORef False
            let args = []
                input = unlines $
                  (case solveTimeLimit opt of
                          Nothing -> []
                          Just sec -> ["set timelimit ", show sec]) ++
                  [ "read " ++ show fname1
                  , "optimize"
                  , "write " ++ show fname2
                  , "y"
                  , "quit"
                  ]
                onGetLine s = do
                  when (s == "MIP - Integer infeasible.") $ do
                    writeIORef isInfeasibleRef True
                  solveLogger opt s
                onGetErrorLine = solveErrorLogger opt
            exitcode <- runProcessWithOutputCallback (cplexPath solver) args input onGetLine onGetErrorLine
            case exitcode of
              ExitFailure n -> ioError $ userError $ "exit with " ++ show n
              ExitSuccess -> do
                size <- withFile fname2 ReadMode $ hFileSize
                if size == 0 then do
                  isInfeasible <- readIORef isInfeasibleRef
                  if isInfeasible then
                    return def{ MIP.solStatus = MIP.StatusInfeasible }
                  else
                    return def
                else
                  CPLEXSol.readFile fname2