{-|

An 'Entry' represents a regular entry in the ledger file. It normally
contains two or more balanced 'RawTransaction's.

-}

module Ledger.Entry
where
import Ledger.Utils
import Ledger.Types
import Ledger.Dates
import Ledger.RawTransaction
import Ledger.Amount


instance Show Entry where show = showEntry

instance Show ModifierEntry where 
    show e = "= " ++ (valueexpr e) ++ "\n" ++ unlines (map show (m_transactions e))

instance Show PeriodicEntry where 
    show e = "~ " ++ (periodicexpr e) ++ "\n" ++ unlines (map show (p_transactions e))

nullentry = Entry {
              edate=parsedate "1900/1/1", 
              estatus=False, 
              ecode="", 
              edescription="", 
              ecomment="",
              etransactions=[],
              epreceding_comment_lines=""
            }

{-|
Show a ledger entry, formatted for the print command. ledger 2.x's
standard format looks like this:

@
yyyy/mm/dd[ *][ CODE] description.........          [  ; comment...............]
    account name 1.....................  ...$amount1[  ; comment...............]
    account name 2.....................  ..$-amount1[  ; comment...............]

pcodewidth    = no limit -- 10          -- mimicking ledger layout.
pdescwidth    = no limit -- 20          -- I don't remember what these mean,
pacctwidth    = 35 minimum, no maximum  -- they were important at the time.
pamtwidth     = 11
pcommentwidth = no limit -- 22
@
-}
showEntry :: Entry -> String
showEntry e = 
    unlines $ [{-precedingcomment ++ -}description] ++ (showtxns $ etransactions e) ++ [""]
    where
      precedingcomment = epreceding_comment_lines e
      description = concat [date, status, code, desc] -- , comment]
      date = showdate $ edate e
      status = if estatus e then " *" else ""
      code = if (length $ ecode e) > 0 then (printf " (%s)" $ ecode e) else ""
      desc = " " ++ edescription e
      comment = if (length $ ecomment e) > 0 then "  ; "++(ecomment e) else ""
      showtxns (t1:t2:[]) = [showtxn t1, showtxnnoamt t2]
      showtxns ts = map showtxn ts
      showtxn t = showacct t ++ "  " ++ (showamount $ tamount t) ++ (showcomment $ tcomment t)
      showtxnnoamt t = showacct t ++ "              " ++ (showcomment $ tcomment t)
      showacct t = "    " ++ (showaccountname $ taccount t)
      showamount = printf "%12s" . showMixedAmount
      showaccountname s = printf "%-34s" s
      showcomment s = if (length s) > 0 then "  ; "++s else ""
      showdate d = printf "%-10s" (showDate d)

isEntryBalanced :: Entry -> Bool
isEntryBalanced (Entry {etransactions=ts}) = 
    isZeroMixedAmount $ costOfMixedAmount $ sum $ map tamount $ filter isReal ts

-- | Ensure that this entry is balanced, possibly auto-filling a missing
-- amount first. We can auto-fill if there is just one non-virtual
-- transaction without an amount. The auto-filled balance will be
-- converted to cost basis if possible. If the entry can not be balanced,
-- raise an error.
balanceEntry :: Entry -> Entry
balanceEntry e@Entry{etransactions=ts} = (e{etransactions=ts'})
    where
      check e
          | isEntryBalanced e = e
          | otherwise = error $ "could not balance this entry:\n" ++ show e
      (withamounts, missingamounts) = partition hasAmount $ filter isReal ts
      ts' = case (length missingamounts) of
              0 -> ts
              1 -> map balance ts
              otherwise -> error $ "could not balance this entry, too many missing amounts:\n" ++ show e
      otherstotal = sum $ map tamount withamounts
      balance t
          | isReal t && not (hasAmount t) = t{tamount = costOfMixedAmount (-otherstotal)}
          | otherwise = t