module Penny.Cabin.Balance.Parser (
  Error(..)
  , ParseOpts(..)
  , parseOptions
  ) where

import qualified Data.Text as X
import Control.Applicative ((<|>), many, Applicative, pure)
import Control.Monad ((>=>))
import qualified Control.Monad.Exception.Synchronous as Ex
import qualified Penny.Cabin.Colors as Col
import qualified Penny.Cabin.Colors.DarkBackground as DB
import qualified Penny.Cabin.Colors.LightBackground as LB
import qualified Penny.Cabin.Chunk as Chk
import qualified Penny.Cabin.Options as CO
import qualified Penny.Copper.Commodity as CC
import qualified Penny.Copper.DateTime as CD
import qualified Penny.Lincoln as L
import qualified Penny.Shield as S
import System.Console.MultiArg.Prim (Parser)
import qualified System.Console.MultiArg.Combinator as C
import qualified Text.Parsec as Parsec

data ParseOpts = ParseOpts {
  drCrColors :: Col.DrCrColors
  , baseColors :: Col.BaseColors
  , colorPref :: Chk.Colors
  , showZeroBalances :: CO.ShowZeroBalances
  , convert :: Maybe (L.Commodity, L.DateTime)
  }


data Error = BadColorName String
           | BadBackground String
           | BadCommodity String
           | BadDate String
             deriving Show


processColorArg ::
  S.Runtime
  -> String
  -> Maybe Chk.Colors
processColorArg rt x
  | x == "yes" = return Chk.Colors8
  | x == "no" = return Chk.Colors0
  | x == "auto" = return (CO.maxCapableColors rt)
  | x == "256" = return Chk.Colors256
  | otherwise = Nothing

parseOpt :: [String] -> [Char] -> C.ArgSpec a -> Parser a
parseOpt ss cs a = C.parseOption [C.OptSpec ss cs a]

color :: Parser (S.Runtime
                 -> ParseOpts
                 -> Ex.Exceptional Error ParseOpts)
color = parseOpt ["color"] "" (C.OneArg f)
  where
    f a1 rt op = case processColorArg rt a1 of
      Nothing -> Ex.throw . BadColorName $ a1
      Just c -> return (op { colorPref = c })

processBackgroundArg ::
  String
  -> Maybe (Col.DrCrColors, Col.BaseColors)
processBackgroundArg x
  | x == "light" = return (LB.drCrColors, LB.baseColors)
  | x == "dark" = return (DB.drCrColors, DB.baseColors)
  | otherwise = Nothing


background :: Parser (ParseOpts -> Ex.Exceptional Error ParseOpts)
background = parseOpt ["background"] "" (C.OneArg f)
  where
    f a1 op = case processBackgroundArg a1 of
      Nothing -> Ex.throw . BadBackground $ a1
      Just (dc, base) ->
        return op { drCrColors = dc
                  , baseColors = base }

parseShowZeroBalances :: Parser (ParseOpts -> ParseOpts)
parseShowZeroBalances = parseOpt opt "" (C.NoArg f)
  where
    opt = ["show-zero-balances"] 
    f op =
      op {showZeroBalances = CO.ShowZeroBalances True }

hideZeroBalances :: Parser (ParseOpts -> ParseOpts)
hideZeroBalances = parseOpt ["hide-zero-balances"] "" (C.NoArg f)
  where
    f op =
      op {showZeroBalances = CO.ShowZeroBalances False }

convertLong ::
  Parser (CD.DefaultTimeZone
          -> ParseOpts
          -> Ex.Exceptional Error ParseOpts)
convertLong = parseOpt ["convert"] "" (C.TwoArg f)
  where
    f a1 a2 dtz op = do
      cty <- case Parsec.parse CC.lvl1Cmdty "" (X.pack a1) of
        Left _ -> Ex.throw . BadCommodity $ a1
        Right g -> return g
      let parseDate = CD.dateTime dtz
      dt <- case Parsec.parse parseDate "" (X.pack a2) of
        Left _ -> Ex.throw . BadDate $ a2
        Right g -> return g
      let op' = op { convert = Just (cty, dt) }
      return op'

convertShort :: Parser (S.Runtime
                        -> ParseOpts
                        -> Ex.Exceptional Error ParseOpts)
convertShort = parseOpt [] ['c'] (C.OneArg f)
  where
    f a1 rt op = do
      cty <- case Parsec.parse CC.lvl1Cmdty "" (X.pack a1) of
        Left _ -> Ex.throw . BadCommodity $ a1
        Right g -> return g
      let dt = S.currentTime rt
          op' = op { convert = Just (cty, dt) }
      return op'
        

parseOptions :: Parser (S.Runtime
                        -> CD.DefaultTimeZone
                        -> ParseOpts
                        -> Ex.Exceptional Error ParseOpts)
parseOptions = do
  fns <- many parseOption
  let f rt dtz o1 =
        let fns' = map (\fn -> fn rt dtz) fns
        in foldl (>=>) return fns' o1
  return f

parseOption :: Parser (S.Runtime
                       -> CD.DefaultTimeZone
                       -> ParseOpts
                       -> Ex.Exceptional Error ParseOpts)
parseOption =
  (do { f <- color; return (\rt _ o -> f rt o )})
  <|> wrap background
  <|> wrap (impurify parseShowZeroBalances)
  <|> wrap (impurify hideZeroBalances)
  <|> (do { f <- convertLong; return (\_ dtz o -> f dtz o )})
  <|> (do { f <- convertShort; return (\rt _ o -> f rt o )})
  where
    wrap p = do
      f <- p
      return (\_ _ op -> f op)

impurify ::
  (Applicative m, Functor f)
  => f (a -> a)
  -> f (a -> m a)
impurify = fmap (\f -> pure . f)