-- | Partial functions that make common types in Lincoln. Some data
-- types in Lincoln are deeply nested, with TextNonEmpty nested inside
-- of a newtype, nested inside of a NonEmptyList, nested inside
-- of... :) All the nesting ensures to the maximum extent possible
-- that the type system reflects the restrictions that exist on
-- Penny's data. For example, it would make no sense to have an empty
-- account (that is, an account with no sub-accounts) or a sub-account
-- whose name is an empty Text.
--
-- The disadvantage of the nesting is that building these data types
-- can be tedious if, for example, you want to build some data within
-- a short custom Haskell program. Thus, this module.

module Penny.Lincoln.Builders ( 
  crashy
  , account
  , commodity
  ) where

import Control.Monad.Exception.Synchronous as Ex
import qualified Data.List.Split as S
import Data.List.NonEmpty (NonEmpty((:|)))
import qualified Penny.Lincoln.Bits as B
import Penny.Lincoln.TextNonEmpty (TextNonEmpty(TextNonEmpty))
import Data.Text (pack)
import qualified Data.Traversable as T

-- | Makes a function partial. Use if you don't want to bother dealing
-- with the Exceptional type.
crashy :: Show e => Ex.Exceptional e a -> a
crashy = Ex.resolve (error . show)

-- | Create an Account. You supply a single String, with colons to
-- separate the different sub-accounts.
account :: String -> Ex.Exceptional String B.Account
account input = do
  subStrs <- case S.splitOn ":" input of
    [] -> error "splitOn returned an empty list; should never happen"
    []:[] -> Ex.throw "account name is null"
    (s:ss) -> return $ s :| ss
  let makeSub s = case s of
        [] -> Ex.throw $
              "sub account name is null from account: " ++ input
        (c:cs) -> return $ B.SubAccountName (TextNonEmpty c (pack cs))
  subs <- T.traverse makeSub subStrs
  return $ B.Account subs
    
-- | Create a Commodity. You supply a single String, with colons to
-- separate the different sub-commodities.
commodity :: String -> Ex.Exceptional String B.Commodity
commodity input = do
  subStrs <- case S.splitOn ":" input of
    [] -> error "splitOn returned an empty list; should never happen"
    []:[] -> Ex.throw "account name is null"
    (s:ss) -> return $ s :| ss
  let makeSub s = case s of
        [] -> Ex.throw $
              "sub account name is null from account: " ++ input
        (c:cs) -> return $ B.SubCommodity (TextNonEmpty c (pack cs))
  subs <- T.traverse makeSub subStrs
  return $ B.Commodity subs