{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE MultiWayIf #-}
{-# LANGUAGE NamedFieldPuns #-}

module Hledger.Cli.Commands.Import (
  importmode
 ,importcmd
)
where

import Control.Monad
import Data.List
import qualified Data.Text.IO as T
import Hledger
import Hledger.Cli.CliOptions
import Hledger.Cli.Commands.Add (journalAddTransaction)
-- import Hledger.Cli.Commands.Print (print')
import System.Console.CmdArgs.Explicit
import Text.Printf

importmode :: Mode RawOpts
importmode = CommandDoc
-> [Flag RawOpts]
-> [(CommandDoc, [Flag RawOpts])]
-> [Flag RawOpts]
-> ([Arg RawOpts], Maybe (Arg RawOpts))
-> Mode RawOpts
hledgerCommandMode
  $(embedFileRelative "Hledger/Cli/Commands/Import.txt")
  [forall a. [CommandDoc] -> (a -> a) -> CommandDoc -> Flag a
flagNone [CommandDoc
"catchup"] (CommandDoc -> RawOpts -> RawOpts
setboolopt CommandDoc
"catchup") CommandDoc
"just mark all transactions as already imported"
  ,forall a. [CommandDoc] -> (a -> a) -> CommandDoc -> Flag a
flagNone [CommandDoc
"dry-run"] (CommandDoc -> RawOpts -> RawOpts
setboolopt CommandDoc
"dry-run") CommandDoc
"just show the transactions to be imported"
  ]
  [(CommandDoc, [Flag RawOpts])
generalflagsgroup1]
  [Flag RawOpts]
hiddenflags
  ([], forall a. a -> Maybe a
Just forall a b. (a -> b) -> a -> b
$ CommandDoc -> Arg RawOpts
argsFlag CommandDoc
"FILE [...]")

importcmd :: CliOpts -> Journal -> IO ()
importcmd opts :: CliOpts
opts@CliOpts{rawopts_ :: CliOpts -> RawOpts
rawopts_=RawOpts
rawopts,inputopts_ :: CliOpts -> InputOpts
inputopts_=InputOpts
iopts} Journal
j = do
  -- XXX could be helpful to show the last-seen date, and number of old transactions, too
  let
    inputfiles :: [CommandDoc]
inputfiles = CommandDoc -> RawOpts -> [CommandDoc]
listofstringopt CommandDoc
"args" RawOpts
rawopts
    inputstr :: CommandDoc
inputstr = forall a. [a] -> [[a]] -> [a]
intercalate CommandDoc
", " forall a b. (a -> b) -> a -> b
$ forall a b. (a -> b) -> [a] -> [b]
map CommandDoc -> CommandDoc
quoteIfNeeded [CommandDoc]
inputfiles
    catchup :: Bool
catchup = CommandDoc -> RawOpts -> Bool
boolopt CommandDoc
"catchup" RawOpts
rawopts
    dryrun :: Bool
dryrun = CommandDoc -> RawOpts -> Bool
boolopt CommandDoc
"dry-run" RawOpts
rawopts
    combinedStyles :: Maybe (Map CommoditySymbol AmountStyle)
combinedStyles = 
      let
        maybeInputStyles :: Maybe (Map CommoditySymbol AmountStyle)
maybeInputStyles = BalancingOpts -> Maybe (Map CommoditySymbol AmountStyle)
commodity_styles_ forall b c a. (b -> c) -> (a -> b) -> a -> c
. InputOpts -> BalancingOpts
balancingopts_ forall a b. (a -> b) -> a -> b
$ InputOpts
iopts
        inferredStyles :: Map CommoditySymbol AmountStyle
inferredStyles =  Journal -> Map CommoditySymbol AmountStyle
journalCommodityStyles Journal
j
      in
        case Maybe (Map CommoditySymbol AmountStyle)
maybeInputStyles of
          Maybe (Map CommoditySymbol AmountStyle)
Nothing -> forall a. a -> Maybe a
Just Map CommoditySymbol AmountStyle
inferredStyles
          Just Map CommoditySymbol AmountStyle
inputStyles -> forall a. a -> Maybe a
Just forall a b. (a -> b) -> a -> b
$ Map CommoditySymbol AmountStyle
inputStyles forall a. Semigroup a => a -> a -> a
<> Map CommoditySymbol AmountStyle
inferredStyles

    iopts' :: InputOpts
iopts' = InputOpts
iopts{
      new_ :: Bool
new_=Bool
True,  -- read only new transactions since last time
      new_save_ :: Bool
new_save_=Bool
False,  -- defer saving .latest files until the end
      strict_ :: Bool
strict_=Bool
False,  -- defer strict checks until the end
      balancingopts_ :: BalancingOpts
balancingopts_=BalancingOpts
defbalancingopts{commodity_styles_ :: Maybe (Map CommoditySymbol AmountStyle)
commodity_styles_= Maybe (Map CommoditySymbol AmountStyle)
combinedStyles}  -- use amount styles from both when balancing txns
      }

  case [CommandDoc]
inputfiles of
    [] -> forall a. CommandDoc -> a
error' CommandDoc
"please provide one or more input files as arguments"  -- PARTIAL:
    [CommandDoc]
fs -> do
      Either CommandDoc (Journal, [LatestDatesForFile])
enewjandlatestdatesforfiles <- forall e (m :: * -> *) a. ExceptT e m a -> m (Either e a)
runExceptT forall a b. (a -> b) -> a -> b
$ InputOpts
-> [CommandDoc]
-> ExceptT CommandDoc IO (Journal, [LatestDatesForFile])
readJournalFilesAndLatestDates InputOpts
iopts' [CommandDoc]
fs
      case Either CommandDoc (Journal, [LatestDatesForFile])
enewjandlatestdatesforfiles of
        Left CommandDoc
err -> forall a. CommandDoc -> a
error' CommandDoc
err
        Right (Journal
newj, [LatestDatesForFile]
latestdatesforfiles) ->
          case forall b a. Ord b => (a -> b) -> [a] -> [a]
sortOn Transaction -> Day
tdate forall a b. (a -> b) -> a -> b
$ Journal -> [Transaction]
jtxns Journal
newj of
            -- with --dry-run the output should be valid journal format, so messages have ; prepended
            [] -> do
              -- in this case, we vary the output depending on --dry-run, which is a bit awkward
              let semicolon :: CommandDoc
semicolon = if Bool
dryrun then CommandDoc
"; " else CommandDoc
"" :: String
              forall r. PrintfType r => CommandDoc -> r
printf CommandDoc
"%sno new transactions found in %s\n\n" CommandDoc
semicolon CommandDoc
inputstr

            [Transaction]
newts | Bool
catchup -> do
              forall r. PrintfType r => CommandDoc -> r
printf CommandDoc
"marked %s as caught up, skipping %d unimported transactions\n\n" CommandDoc
inputstr (forall (t :: * -> *) a. Foldable t => t a -> Int
length [Transaction]
newts)

            [Transaction]
newts -> do
              if Bool
dryrun
              then do
                -- show txns to be imported
                forall r. PrintfType r => CommandDoc -> r
printf CommandDoc
"; would import %d new transactions from %s:\n\n" (forall (t :: * -> *) a. Foldable t => t a -> Int
length [Transaction]
newts) CommandDoc
inputstr
                forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
(a -> m b) -> t a -> m ()
mapM_ (CommoditySymbol -> IO ()
T.putStr forall b c a. (b -> c) -> (a -> b) -> a -> c
. Transaction -> CommoditySymbol
showTransaction) [Transaction]
newts

                -- then check the whole journal with them added, if in strict mode
                forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (InputOpts -> Bool
strict_ InputOpts
iopts) forall a b. (a -> b) -> a -> b
$ IO ()
strictChecks

              else do
                -- first check the whole journal with them added, if in strict mode
                forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (InputOpts -> Bool
strict_ InputOpts
iopts) forall a b. (a -> b) -> a -> b
$ IO ()
strictChecks

                -- then append the transactions to the main journal file.
                -- XXX This writes unix line endings (\n), some at least,
                -- even if the file uses dos line endings (\r\n), which could leave
                -- mixed line endings in the file. See also writeFileWithBackupIfChanged.
                forall (t :: * -> *) (m :: * -> *) b a.
(Foldable t, Monad m) =>
(b -> a -> m b) -> b -> t a -> m ()
foldM_ (Journal -> CliOpts -> Transaction -> IO Journal
`journalAddTransaction` CliOpts
opts) Journal
j [Transaction]
newts  -- gets forced somehow.. (how ?)

                forall r. PrintfType r => CommandDoc -> r
printf CommandDoc
"imported %d new transactions from %s to %s\n" (forall (t :: * -> *) a. Foldable t => t a -> Int
length [Transaction]
newts) CommandDoc
inputstr (Journal -> CommandDoc
journalFilePath Journal
j)

                -- and if we got this far, update each file's .latest file
                [LatestDatesForFile] -> IO ()
saveLatestDatesForFiles [LatestDatesForFile]
latestdatesforfiles

              where
                -- add the new transactions to the journal in memory and check the whole thing
                strictChecks :: IO ()
strictChecks = forall a c b. (a -> c) -> (b -> c) -> Either a b -> c
either forall (m :: * -> *) a. MonadFail m => CommandDoc -> m a
fail forall (f :: * -> *) a. Applicative f => a -> f a
pure forall a b. (a -> b) -> a -> b
$ Journal -> Either CommandDoc ()
journalStrictChecks Journal
j'
                  where j' :: Journal
j' = forall (t :: * -> *) b a.
Foldable t =>
(b -> a -> b) -> b -> t a -> b
foldl' (forall a b c. (a -> b -> c) -> b -> a -> c
flip Transaction -> Journal -> Journal
addTransaction) Journal
j [Transaction]
newts