module Hledger.Cli.Commands.Check.Ordereddates (
  journalCheckOrdereddates
)
where

import qualified Data.Text as T
import Hledger
import Hledger.Cli.CliOptions
import Control.Monad (forM)
import Data.List (groupBy)

journalCheckOrdereddates :: CliOpts -> Journal -> Either String ()
journalCheckOrdereddates CliOpts{reportspec_=rspec} j = do
  let 
    ropts = (_rsReportOpts rspec){accountlistmode_=ALFlat}
    -- check date ordering within each file, not across files
    filets = 
      groupBy (\t1 t2 -> transactionFile t1 == transactionFile t2) $
      filter (_rsQuery rspec `matchesTransaction`) $
      jtxns $ journalApplyValuationFromOpts rspec j
    checkunique = False -- boolopt "unique" rawopts  XXX was supported by checkdates command
    compare a b = if checkunique then getdate a < getdate b else getdate a <= getdate b
      where getdate = transactionDateFn ropts
  either Left (const $ Right ()) $ 
   forM filets $ \ts ->
    case checkTransactions compare ts of
      FoldAcc{fa_previous=Nothing} -> Right ()
      FoldAcc{fa_error=Nothing}    -> Right ()
      FoldAcc{fa_error=Just error, fa_previous=Just previous} -> do
        let
          datestr = if date2_ ropts then "2" else ""
          uniquestr = if checkunique then " and/or not unique" else ""
          positionstr = showSourcePosPair $ tsourcepos error
          txn1str = T.unpack . linesPrepend  (T.pack "  ")               $ showTransaction previous
          txn2str = T.unpack . linesPrepend2 (T.pack "> ") (T.pack "  ") $ showTransaction error
        Left $
          "transaction date" <> datestr <> " is out of order"
          <> uniquestr <> "\nat " <> positionstr <> ":\n\n"
          <> txn1str <> txn2str

data FoldAcc a b = FoldAcc
 { fa_error    :: Maybe a
 , fa_previous :: Maybe b
 }

checkTransactions :: (Transaction -> Transaction -> Bool)
  -> [Transaction] -> FoldAcc Transaction Transaction
checkTransactions compare = foldWhile f FoldAcc{fa_error=Nothing, fa_previous=Nothing}
  where
    f current acc@FoldAcc{fa_previous=Nothing} = acc{fa_previous=Just current}
    f current acc@FoldAcc{fa_previous=Just previous} =
      if compare previous current
      then acc{fa_previous=Just current}
      else acc{fa_error=Just current}

foldWhile :: (a -> FoldAcc a b -> FoldAcc a b) -> FoldAcc a b -> [a] -> FoldAcc a b
foldWhile _ acc [] = acc
foldWhile fold acc (a:as) =
  case fold a acc of
   acc@FoldAcc{fa_error=Just _} -> acc
   acc -> foldWhile fold acc as
