-- | Parsing options for the Convert report from the command line.
module Penny.Cabin.Balance.Convert.Parser (
  Opts(..)
  , Target(..)
  , SortBy(..)
  , RoundTo(..)
  , allOptSpecs
  ) where


import qualified Data.Text as X
import qualified Penny.Cabin.Options as CO
import qualified Penny.Cabin.Parsers as P
import qualified Penny.Lincoln as L
import qualified Penny.Copper.Parsec as Pc
import qualified System.Console.MultiArg.Combinator as C
import qualified Text.Parsec as Parsec


-- | Round to this many decimal places in the Percent report.
newtype RoundTo = RoundTo { unRoundTo :: L.NonNegative }
  deriving (Eq, Show, Ord)

-- | Is the target commodity determined by the user or automatically?
data Target = AutoTarget | ManualTarget L.To

data SortBy = SortByQty | SortByName deriving (Eq, Show, Ord)

-- | Default starting options for the Convert report. After
-- considering what is parsed in from the command line and price data,
-- a Convert.Opts will be generated.
data Opts = Opts
  { showZeroBalances :: CO.ShowZeroBalances
  , target :: Target
  , dateTime :: L.DateTime
  , sortOrder :: P.SortOrder
  , sortBy :: SortBy
  , percentRpt :: Maybe RoundTo
  -- ^ If the user wants a percentage report, set this.
  }

-- | Do not be tempted to change the setup in this module so that the
-- individual functions such as parseColor and parseBackground return
-- parsers rather than OptSpec. Such an arrangement breaks the correct
-- parsing of abbreviated long options.
allOptSpecs :: [C.OptSpec (Opts -> Opts)]
allOptSpecs =
  [ parseZeroBalances
  , parseCommodity
  , parseAuto
  , parseDate
  , parseSort
  , parseOrder
  , parsePct
  , parseRound
  ]

parseZeroBalances :: C.OptSpec (Opts -> Opts)
parseZeroBalances = fmap f P.zeroBalances
  where
    f x o = o { showZeroBalances = x }


parseCommodity :: C.OptSpec (Opts -> Opts)
parseCommodity = C.OptSpec ["commodity"] "c" (C.OneArg f)
  where
    f a1 =
      case Parsec.parse Pc.lvl1Cmdty "" (X.pack a1) of
        Left _ -> Left . C.ErrorMsg $ "invalid commodity"
        Right g -> return $ \os -> os { target = ManualTarget . L.To $ g }

parseAuto :: C.OptSpec (Opts -> Opts)
parseAuto = C.OptSpec ["auto-commodity"] "" (C.NoArg f)
  where
    f os = os { target = AutoTarget }

parseDate :: C.OptSpec (Opts -> Opts)
parseDate = C.OptSpec ["date"] "d" (C.OneArg f)
  where
    f a1 =
      case Parsec.parse Pc.dateTime "" (X.pack a1) of
        Left _ -> Left . C.ErrorMsg $ "invalid date"
        Right g -> return $ \os -> os { dateTime = g }

parseSort :: C.OptSpec (Opts -> Opts)
parseSort = C.OptSpec ["sort"] "s" (C.ChoiceArg ls)
  where
    ls = [ ("qty", (\os -> os { sortBy = SortByQty }))
         , ("name", (\os -> os { sortBy = SortByName })) ]

parseOrder :: C.OptSpec (Opts -> Opts)
parseOrder = fmap f P.order
  where
    f x o = o { sortOrder = x }

parsePct :: C.OptSpec (Opts -> Opts)
parsePct = C.OptSpec ["percent"] "%" (C.NoArg f)
  where
    f o = o { percentRpt = Just (RoundTo . maybe e id . L.nonNegative $ 0) }
    e = error $ "Penny.Cabin.Balance.Convert.Parser.parsePct: "
                ++ "error: zero is not non-negative"

parseRound :: C.OptSpec (Opts -> Opts)
parseRound = C.OptSpec ["round"] "r" (C.OneArg f)
  where
    f a = do
      i <- C.reader a
      case L.nonNegative i of
        Nothing -> Left . C.ErrorMsg $ "argument is negative"
        Just g -> return $ \o -> o { percentRpt = Just (RoundTo g) }