{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE FlexibleInstances, MultiParamTypeClasses, DeriveDataTypeable #-}

module Laborantin.CLI (defaultMain) where

import Control.Monad
import Control.Applicative
import Control.Monad.IO.Class
import Laborantin
import Laborantin.Types
import Laborantin.Implementation
import Laborantin.Query
import Laborantin.Query.Parse
import Laborantin.Query.Interpret
import System.Environment
import System.Console.CmdLib hiding (run)
import qualified Data.Map as M
import Data.List (intercalate)
import Data.Aeson (encode)
import Data.Maybe (catMaybes)
import Data.Either (rights)
import qualified Data.ByteString.Lazy.Char8 as C
import Data.List.Split (splitOn)
import Data.Text (Text)
import qualified Data.Text as T
import qualified Data.Text.IO as T

defaultMain xs = getArgs >>= dispatchR [] >>= runLabor xs

unlines' :: [Text] -> Text
unlines' = T.intercalate "\n"

describeScenario :: ScenarioDescription m -> Text
describeScenario sc = T.unlines [
    T.append "# Scenario: " (sName sc)
  , T.append "    " (sDesc sc)
  , T.concat ["    ", (T.pack . show . length . paramSets $ sParams sc), " parameter combinations by default"]
  , "## Parameters:"
  , unlines' $ paramLines
  ]
  where paramLines = map (uncurry paramLine) pairs
        pairs = M.toList $ sParams sc
        paramLine n p = unlines' [
                          T.append "### " n
                        , describeParameter p
                        ]

describeParameter :: ParameterDescription -> Text
describeParameter p = unlines' [
    T.concat ["(", pName p , ")"]
  , T.concat ["    ", pDesc p]
  , T.concat ["    ", (T.pack . show . length $ concatMap expandValue $ pValues p), " values:"]
  , T.pack $ unlines $ map (("    - " ++) . show) (pValues p)
  ]

describeExecution :: Execution m -> Text
describeExecution e = T.pack $ intercalate " " [ ePath e
                                      , T.unpack $ sName (eScenario e)
                                      , "(" ++ show (eStatus e) ++ ")"
                                      , C.unpack $ encode (eParamSet e)
                                      ]


data Labor = Run        { scenarii   :: [String] , params :: [String] , continue :: Bool , matcher :: [String]} 
           | Describe   { scenarii   :: [String] } 
           | Find       { scenarii   :: [String] , params :: [String] , successful :: Bool, matcher :: [String]} 
           | Analyze    { scenarii   :: [String] , params :: [String] , successful :: Bool , matcher :: [String]} 
           | Rm         { scenarii   :: [String] , params :: [String] , successful :: Bool , matcher :: [String]} 
           | Query      { scenarii   :: [String] , params :: [String] , successful :: Bool , matcher :: [String]} 
    deriving (Typeable, Data, Show, Eq)

instance Attributes Labor where
    attributes _ = group "Options" [
                        scenarii %> [ Short "s"
                                    , Long ["scenario"]
                                    , Help "Restrict to the scenarios in parameter."
                                    , ArgHelp "SCENARIOS"
                                    ] 
                    ,   params   %> [ Short "p"
                                    , Long ["param"]
                                    , Help "Restrict a parameter, format name=type:val."
                                    , ArgHelp "PARAMS"
                                    ]
                    ,   continue %> [ Short "c"
                                    , Long ["continue"]
                                    , Default False
                                    , Invertible True
                                    , Help "Continue execution (skip known)"
                                    ]
                    ,   successful %> [ Long ["successful"]
                                    , Help "Successful only"
                                    , Invertible True
                                    , Default True
                                    ]
                    ,   matcher  %> [ Short "m"
                                    , Long ["matcher"]
                                    , Help "Restrict to a matching expression"
                                    , ArgHelp "MATCHER EXPRESSION"
                                    ]
                    ]

instance RecordCommand Labor where
    mode_summary _ = "Laborantin command-line interface"
    run' = error "should not arrive here"

data DescriptionTExpr = ScenarioName [Text]
    deriving (Show)

parseParamTExpr :: Text -> Maybe (TExpr Bool)
parseParamTExpr str = let vals = T.splitOn ":" str in
    case vals of
    [k,"ratio",v]    -> Just (Eq (NCoerce (ScParam k)) (N $ unsafeReadText v))
    [k,"int",v]      -> Just (Eq (NCoerce (ScParam k)) (N . toRational $ unsafeReadText v))
    [k,"float",v]    -> Just (Eq (NCoerce (ScParam k)) (N $ toRational (unsafeReadText v :: Float)))
    [k,"str",v]      -> Just (Eq (SCoerce (ScParam k)) (S v))
    _                -> Nothing

    where   unsafeReadText :: (Read a) => Text -> a 
            unsafeReadText = read . T.unpack

paramsToTExpr :: [Text] -> TExpr Bool
paramsToTExpr xs = let atoms = catMaybes (map parseParamTExpr xs) in
  conjunctionQueries atoms

-- if no scenario: True, otherwise any of the scenarios
scenarsToTExpr :: [Text] -> TExpr Bool
scenarsToTExpr [] = B True
scenarsToTExpr scii = let atoms = map (\name -> (Eq ScName (S name))) scii in
    disjunctionQueries atoms

statusToTExpr :: Bool -> TExpr Bool
statusToTExpr True  =     (Eq ScStatus (S "success"))
statusToTExpr False = Not (Eq ScStatus (S "success"))

conjunctionQueries :: [TExpr Bool] -> TExpr Bool
conjunctionQueries []     = B True
conjunctionQueries (q:qs) = And q (conjunctionQueries qs)

disjunctionQueries :: [TExpr Bool] -> TExpr Bool
disjunctionQueries []     = B False
disjunctionQueries (q:qs) = Or q (disjunctionQueries qs)

filterDescriptions :: DescriptionTExpr -> [ScenarioDescription m] -> [ScenarioDescription m]
filterDescriptions (ScenarioName []) xs = xs
filterDescriptions (ScenarioName ns) xs = filter ((flip elem ns) . sName) xs


runLabor :: [ScenarioDescription EnvIO] -> Labor -> IO ()
runLabor xs labor =
    case labor of
    (Describe scii)                 -> forM_ xs' (T.putStrLn . describeScenario)
    Find {}                         -> do (execs,_) <- runEnvIO loadMatching
                                          mapM_ (T.putStrLn . describeExecution) execs
    (Rm {})                         -> runSc loadAndRemove
    (Run { continue = False })      -> runSc execAll
    (Run { continue = True })       -> runSc execRemaining
    Analyze {}                      -> runSc loadAndAnalyze
    Query {}                        -> putStrLn $ showTExpr $ simplifyOneBoolLevel query

    where xs'           = filterDescriptions (ScenarioName $ map T.pack $ scenarii labor) xs
          matcherUExprs = rights $ map parseUExpr (matcher labor)
          matcherTExprs = map (toTExpr (B True)) matcherUExprs
          paramsTExpr   = paramsToTExpr $ map T.pack $ params labor
          scenarioTExpr = scenarsToTExpr $ map T.pack $ scenarii labor
          statusTExpr   = statusToTExpr $ successful labor
          !query        = conjunctionQueries (paramsTExpr:scenarioTExpr:statusTExpr:matcherTExprs)
          runSc         = void . runEnvIO
          loadMatching  = load defaultBackend xs' query
          loadAndRemove = loadMatching >>= mapM (remove defaultBackend)
          loadAndAnalyze= loadMatching >>= mapM (executeAnalysis defaultBackend)
          execAll       = forM_ xs' $ executeExhaustive defaultBackend
          execRemaining = forM_ xs' $ executeMissing defaultBackend