{-# OPTIONS_GHC -Wall #-}
{-# LANGUAGE OverloadedStrings #-}
module ToySolver.Data.MIP.Solution.CBC
  ( Solution (..)
  , parse
  , readFile
  ) where

import Prelude hiding (readFile, writeFile)
import Control.Applicative
import Control.Monad.Except
import Data.Interned (intern)
import Data.Map (Map)
import qualified Data.Map as Map
import Data.Scientific (Scientific)
import qualified Data.Text as T
import qualified Data.Text.Lazy as TL
import qualified Data.Text.Lazy.IO as TLIO
import ToySolver.Data.MIP (Solution)
import qualified ToySolver.Data.MIP as MIP

parse :: TL.Text -> MIP.Solution Scientific
parse t =
  case parse' $ TL.lines t of
    Left e -> error e
    Right x -> x

parse' :: [TL.Text] -> Either String (MIP.Solution Scientific)
parse' (l1:ls) = do
  (status, obj) <-
    case TL.break ('-'==) l1 of
      (s1,s2) ->
        case TL.stripPrefix "- objective value " s2 of
          Nothing -> throwError "fail to parse header"
          Just s3 -> do
            let s1' = TL.toStrict (TL.strip s1)
            return
              ( case Map.lookup s1' statusTable of
                  Just st -> st
                  Nothing ->
                    if T.isPrefixOf "Stopped on " s1'
                    then MIP.StatusUnknown
                    else MIP.StatusUnknown
              , read (TL.unpack s3)
              )
  let f :: [(MIP.Var, Scientific)] -> TL.Text -> Either String [(MIP.Var, Scientific)]
      f vs t =
        case TL.words t of
          ("**":_no:var:val:_) -> return $ (intern (TL.toStrict var), read (TL.unpack val)) : vs
          (_no:var:val:_) -> return $ (intern (TL.toStrict var), read (TL.unpack val)) : vs
          [] -> return $ vs
          _ -> throwError ("ToySolver.Data.MIP.Solution.CBC: invalid line " ++ show t)
  vs <- foldM f [] ls
  return $
    MIP.Solution
    { MIP.solStatus = status
    , MIP.solObjectiveValue = Just obj
    , MIP.solVariables = Map.fromList vs
    }
parse' _ = throwError "must have >=1 lines"

statusTable :: Map T.Text MIP.Status
statusTable = Map.fromList
  [ ("Optimal", MIP.StatusOptimal)
  , ("Unbounded", MIP.StatusInfeasibleOrUnbounded)
  , ("Integer infeasible", MIP.StatusInfeasible)
  , ("Infeasible", MIP.StatusInfeasible)
  ]

readFile :: FilePath -> IO (MIP.Solution Scientific)
readFile fname = parse <$> TLIO.readFile fname