-- | Amount parsers. An amount is a commodity and a quantity. (An
-- entry is an amount and a debit or credit).
--
-- Possible combinations:
--
-- * quoted Level 1 commodity, optional whitespace, quantity
--
-- * Level 3 commodity, optional whitespace, quantity
--
-- * Quantity, optional whitespace, quoted Level 1 commodity
--
-- * Quantity, optional whitespace, Level 2 commodity
--
-- Each quantity may be quoted or unquoted.
module Penny.Copper.Amount (
  amount
  , render
  ) where

import Control.Applicative ((<$>), (<*>), (<|>))
import qualified Data.Text as X
import Text.Parsec ( char, many, (<?>) )
import Text.Parsec.Text ( Parser )

import qualified Penny.Copper.Commodity as C
import qualified Penny.Copper.Qty as Q
import qualified Penny.Lincoln as L

-- | Parse optional spaces, returns appropriate metadata.
spaces :: Parser L.SpaceBetween
spaces = f <$> many (char ' ') where
  f l = if null l then L.NoSpaceBetween else L.SpaceBetween

cmdtyQty :: Parser L.Commodity
            -> Q.RadGroup
            -> Parser (L.Amount, L.Format)
cmdtyQty p rg = let
  f c s q = (a, fmt) where
    a = L.Amount q c
    fmt = L.Format L.CommodityOnLeft s
  e = "amount, commodity on left"
  in f <$> p <*> spaces <*> Q.qty rg <?> e

lvl1CmdtyQty :: Q.RadGroup -> Parser (L.Amount, L.Format)
lvl1CmdtyQty = cmdtyQty C.quotedLvl1Cmdty

lvl3CmdtyQty :: Q.RadGroup -> Parser (L.Amount, L.Format)
lvl3CmdtyQty = cmdtyQty C.lvl3Cmdty

cmdtyOnRight :: Q.RadGroup -> Parser (L.Amount, L.Format)
cmdtyOnRight rg = let  
  f q s c = (a, fmt) where
    a = L.Amount q c
    fmt = L.Format L.CommodityOnRight s
  e = "amount, commodity on right"
  in f
     <$> Q.qty rg
     <*> spaces
     <*> (C.quotedLvl1Cmdty <|> C.lvl2Cmdty)
     <?> e

-- | Parses an amount with its metadata. Handles all combinations of
-- commodities and quantities.
amount :: Q.RadGroup -> Parser (L.Amount, L.Format)
amount rg = lvl1CmdtyQty rg
            <|> lvl3CmdtyQty rg
            <|> cmdtyOnRight rg
            <?> "amount"

-- | Render an Amount. The Format is required so that the commodity
-- can be displayed in the right place.
render ::
  (Q.GroupingSpec, Q.GroupingSpec)
  -- ^ Grouping
  -> Q.RadGroup
  -> L.Format
  -> L.Amount
  -> Maybe X.Text
render gs rg f a = let
  (q, c) = (L.qty a, L.commodity a)
  qty = Q.quote $ Q.renderUnquoted rg gs q
  ws = case L.between f of
    L.SpaceBetween -> X.singleton ' '
    L.NoSpaceBetween -> X.empty
  mayLvl3 = C.renderLvl3 c
  mayLvl2 = C.renderLvl2 c
  in do
    quotedLvl1 <- C.renderQuotedLvl1 c
    let (l, r) = case L.side f of
          L.CommodityOnLeft -> case mayLvl3 of
            Nothing -> (quotedLvl1, qty)
            Just l3 -> (l3, qty)
          L.CommodityOnRight -> case mayLvl2 of
            Nothing -> (qty, quotedLvl1)
            Just l2 -> (qty, l2)
    return $ X.concat [l, ws, r]