{-# LANGUAGE DataKinds #-}
{-# LANGUAGE NumericUnderscores #-}
{-# LANGUAGE TypeApplications #-}

module Main where

import Data.Either.Extra

import Control.Monad.Except (ExceptT, liftEither, runExceptT, throwError)
import Control.Monad.IO.Class
import Control.Monad.Trans.Class (lift)

import System.Console.Haskeline
import System.IO

import Text.Read (readEither)

import GHC.TypeLits

import Budget
import qualified PrettyPrint

data AppError
    = Interrupted
    | ReadError String
    | NegativeIncome
    | Undefined
    deriving (Show)

type App = ExceptT AppError (InputT IO)

main :: IO ()
main = do
    r <- runInputT defaultSettings . runExceptT $ go
    either (hPutStrLn stderr . ("An error occured: " ++) . show) return r
  where
    go :: App ()
    go = do
        income <- readInput "Income: " (readEither @Int)
        currency <- readInput "Currency: " Right
        budget <- calculateBudget income currency
        liftIO . print . PrettyPrint.printBudget $ budget

calculateBudget :: Int -> String -> App SomeBudget
calculateBudget x curr = do
    income <- mkIncome x curr
    let rule = defaultRule
        budget = calculateSomeBudget rule income

    return budget

readInput :: String -> (String -> Either String b) -> App b
readInput prompt f = do
    input <- lift $ getInputLine prompt
    maybe (throwError Interrupted) (liftEither . mapLeft ReadError . f) input

mkIncome :: Int -> String -> App (SomePosMoney)
mkIncome input currency = do
    pos <- liftEither . mapLeft (const NegativeIncome) $ eitherPos input
    return $ withSomeSSymbol currency $ \curr -> SomePosMoney curr (PosMoney pos)