{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE StandaloneDeriving #-}

import Test.HUnit hiding (test)
import Test.Framework.Providers.HUnit (hUnitTestToTests)
import Test.Framework.Runners.Console (defaultMain)

import           Control.Applicative (Applicative(..))
import           Control.Arrow (ArrowChoice(..))
import           Control.Monad (Monad(..))
import           Control.Monad.IO.Class (liftIO)
import           Data.Bool (Bool(..))
import           Data.Decimal (DecimalRaw(..))
import qualified Data.Either
import           Data.Either (rights, either)
import           Data.Eq (Eq(..))
import           Data.Function (($), (.), id, const)
import           Data.Functor (Functor(..), (<$>))
import           Data.List ((++))
import           Data.List.NonEmpty (NonEmpty(..))
import qualified Data.Map.Strict as Map
import           Data.Maybe (Maybe(..), maybe)
import           Data.Monoid (Monoid(..))
import           Data.Text (Text)
import qualified Data.Text as Text
import qualified Data.Text.Lazy as TL
import qualified Data.Time.Calendar  as Time
import qualified Data.Time.LocalTime as Time
import           System.IO (IO)
import qualified Text.Parsec     as R hiding (char, space, spaces, string)
import qualified Text.Parsec.Pos as R

import qualified Hcompta.Chart as Chart
import qualified Hcompta.Date as Date
import qualified Hcompta.Lib.Parsec as R
import qualified Hcompta.Posting as Posting
import qualified Hcompta.Tag as Tag
import qualified Hcompta.Transaction as Transaction

import qualified Hcompta.Format.Ledger       as F
import qualified Hcompta.Format.Ledger.Read  as F
import qualified Hcompta.Format.Ledger.Write as F

deriving instance Eq F.Amount

main :: IO ()
main = defaultMain $ hUnitTestToTests test

test :: Test
test = TestList
 [ "Read" ~: TestList
	 [ "read_date" ~:
		let (==>) (txt::Text) =
			(~:) (Text.unpack txt) .
			(~?=)
			 (rights [R.runParser_with_Error
				 (F.read_date id Nothing <* R.eof) () "" txt])
		in TestList $
		 [ "2000-01-01" ==>
			 [ Time.zonedTimeToUTC $
				Time.ZonedTime
				 (Time.LocalTime
					 (Time.fromGregorian 2000 01 01)
					 (Time.TimeOfDay 0 0 0))
				 (Time.utc) ]
		 , "2000/01/01" ==>
			 [ Time.zonedTimeToUTC $
				Time.ZonedTime
				 (Time.LocalTime
					 (Time.fromGregorian 2000 01 01)
					 (Time.TimeOfDay 0 0 0))
				 (Time.utc) ]
		 , "2000-01-01_12:34" ==>
			 [ Time.zonedTimeToUTC $
				Time.ZonedTime
				 (Time.LocalTime
					 (Time.fromGregorian 2000 01 01)
					 (Time.TimeOfDay 12 34 0))
				 (Time.utc) ]
		 , "2000-01-01_12:34:56" ==>
			 [ Time.zonedTimeToUTC $
				Time.ZonedTime
				 (Time.LocalTime
					 (Time.fromGregorian 2000 01 01)
					 (Time.TimeOfDay 12 34 56))
				 (Time.utc) ]
		 , "2000-01-01_12:34_CET" ==>
			 [ Time.zonedTimeToUTC $
				Time.ZonedTime
				 (Time.LocalTime
					 (Time.fromGregorian 2000 01 01)
					 (Time.TimeOfDay 12 34 0))
				 (Time.TimeZone 60 True "CET") ]
		 , "2000-01-01_12:34+01:30" ==>
			 [ Time.zonedTimeToUTC $
				Time.ZonedTime
				 (Time.LocalTime
					 (Time.fromGregorian 2000 01 01)
					 (Time.TimeOfDay 12 34 0))
				 (Time.TimeZone 90 False "+01:30") ]
		 , "2000-01-01_12:34:56_CET" ==>
			 [ Time.zonedTimeToUTC $
				Time.ZonedTime
				 (Time.LocalTime
					 (Time.fromGregorian 2000 01 01)
					 (Time.TimeOfDay 12 34 56))
				 (Time.TimeZone 60 True "CET") ]
		 , "2001-02-29" ==> []
		 ] ++
		let (==>) (txt::Text, def) =
			(~:) (Text.unpack txt) .
			(~?=) (rights [R.runParser_with_Error
				 (F.read_date id (Just def) <* R.eof) () "" txt])
		in
		 [ ("01-01", 2000) ==>
			 [ Time.zonedTimeToUTC $
				Time.ZonedTime
				 (Time.LocalTime
					 (Time.fromGregorian 2000 01 01)
					 (Time.TimeOfDay 0 0 0))
				 (Time.utc)]
		 ]
	 , "read_account_section" ~:
		let (==>) (txt::Text) b =
			(~:) (Text.unpack txt) $
			(~?=)
			 (rights [R.runParser (F.read_account_section <* R.eof) () "" txt])
			 (if b then [txt] else [])
		in TestList
		 [ ""      ==> False
		 , "A"     ==> True
		 , "AA"    ==> True
		 , " "     ==> False
		 , ":"     ==> False
		 , "A:"    ==> False
		 , ":A"    ==> False
		 , "A "    ==> False
		 , "A A"   ==> True
		 , "A "    ==> False
		 , "A\t"   ==> False
		 , "A \n"  ==> False
		 , "(A)A"  ==> True
		 , "( )A"  ==> True
		 , "(A) A" ==> True
		 , "[ ] A" ==> True
		 , "(A)  " ==> False
		 , "(A)"   ==> True
		 , "A(A)"  ==> True
		 , "[A]A"  ==> True
		 , "[A] A" ==> True
		 , "[A]  " ==> False
		 , "[A]"   ==> True
		 , "\"A \"" ~:
			 (rights
				[R.runParser
				 (F.read_account_section)
				 () "" ("A "::Text)])
			 ~?=
			 ["A"]
		 ]
	 , "read_account" ~:
		let (==>) (txt::Text) =
			(~:) (Text.unpack txt) .
			(~?=) (rights [R.runParser
				 (F.read_account <* R.eof) () "" txt])
		in TestList
		 [ "" ==> []
		 , "A" ==> ["A":|[]]
		 , "A:" ==> []
		 , ":A" ==> []
		 , "A " ==> []
		 , " A" ==> []
		 , "A:B" ==> ["A":|["B"]]
		 , "A:B:C" ==> ["A":|["B","C"]]
		 , "Aa:Bbb:Cccc" ==> ["Aa":|["Bbb", "Cccc"]]
		 , "A a : B b b : C c c c" ==> ["A a ":|[" B b b ", " C c c c"]]
		 , "A: :C" ==> ["A":|[" ", "C"]]
		 , "A::C" ==> []
		 , "A:B:(C)" ==> ["A":|["B", "(C)"]]
		 ]
	 , "read_amount" ~:
		let (==>) (txt::Text) =
			(~:) (Text.unpack txt) .
			(~?=) (rights [R.runParser (F.read_amount <* R.eof) () "" txt])
		in TestList
		 [ ""  ==> []
		 , "0" ==>
			 [( mempty
				, F.amount { F.amount_quantity = Decimal 0 0 } )]
		 , "00" ==>
			 [( mempty
				, F.amount { F.amount_quantity = Decimal 0 0 } )]
		 , "0." ==>
			 [( mempty { F.amount_style_fractioning = Just '.' }
				, F.amount { F.amount_quantity = Decimal 0 0 } )]
		 , ".0" ==>
			 [( mempty { F.amount_style_fractioning = Just '.' }
				, F.amount { F.amount_quantity = Decimal 1 0 } )]
		 , "0," ==>
			 [( mempty { F.amount_style_fractioning = Just ',' }
				, F.amount { F.amount_quantity = Decimal 0 0 } )]
		 , ",0" ==>
			 [( mempty { F.amount_style_fractioning = Just ',' }
				, F.amount { F.amount_quantity = Decimal 1 0 } )]
		 , "0_" ==> []
		 , "_0" ==> []
		 , "0.0" ==>
			 [( mempty { F.amount_style_fractioning = Just '.' }
				, F.amount { F.amount_quantity = Decimal 1 0 } )]
		 , "00.00" ==>
			 [( mempty { F.amount_style_fractioning = Just '.' }
				, F.amount { F.amount_quantity = Decimal 2 0 } )]
		 , "0,0" ==>
			 [( mempty { F.amount_style_fractioning = Just ',' }
				, F.amount { F.amount_quantity = Decimal 1 0 } )]
		 , "00,00" ==>
			 [( mempty { F.amount_style_fractioning = Just ',' }
				, F.amount { F.amount_quantity = Decimal 2 0 } )]
		 , "0_0" ==>
			 [( mempty { F.amount_style_grouping_integral = Just $ F.Amount_Style_Grouping '_' [1] }
				, F.amount { F.amount_quantity = Decimal 0 0 } )]
		 , "00_00" ==>
			 [( mempty { F.amount_style_grouping_integral = Just $ F.Amount_Style_Grouping '_' [2] }
				, F.amount { F.amount_quantity = Decimal 0 0 } )]
		 , "0,000.00" ==>
			 [( mempty
				 { F.amount_style_fractioning       = Just '.'
				 , F.amount_style_grouping_integral = Just $ F.Amount_Style_Grouping ',' [3] }
				, F.amount { F.amount_quantity = Decimal 2 0 } )]
		 , "0.000,00" ==>
			 [( mempty
				 { F.amount_style_fractioning       = Just ','
				 , F.amount_style_grouping_integral = Just $ F.Amount_Style_Grouping '.' [3] }
				, F.amount { F.amount_quantity = Decimal 2 0 } )]
		 , "1,000.00" ==>
			 [( mempty
				 { F.amount_style_fractioning       = Just '.'
				 , F.amount_style_grouping_integral = Just $ F.Amount_Style_Grouping ',' [3] }
				, F.amount { F.amount_quantity = Decimal 2 100000 } )]
		 , "1.000,00" ==>
			 [( mempty
				 { F.amount_style_fractioning       = Just ','
				 , F.amount_style_grouping_integral = Just $ F.Amount_Style_Grouping '.' [3] }
				, F.amount { F.amount_quantity = Decimal 2 100000 } )]
		 , "1,000.00." ==> []
		 , "1.000,00," ==> []
		 , "1,000.00_" ==> []
		 , "123" ==>
			 [( mempty
				, F.amount { F.amount_quantity = Decimal 0 123 } )]
		 , "1.2" ==>
			 [( mempty { F.amount_style_fractioning = Just '.' }
				, F.amount { F.amount_quantity = Decimal 1 12 } )]
		 , "1,2" ==>
			 [( mempty { F.amount_style_fractioning = Just ',' }
				, F.amount { F.amount_quantity = Decimal 1 12 } )]
		 , "12.34" ==>
			 [( mempty { F.amount_style_fractioning = Just '.' }
				, F.amount { F.amount_quantity = Decimal 2 1234 } )]
		 , "12,34" ==>
			 [( mempty { F.amount_style_fractioning = Just ',' }
				, F.amount { F.amount_quantity = Decimal 2 1234 } )]
		 , "1_2" ==>
			 [( mempty { F.amount_style_grouping_integral = Just $ F.Amount_Style_Grouping '_' [1] }
				, F.amount { F.amount_quantity = Decimal 0 12 } )]
		 , "1_23" ==>
			 [( mempty { F.amount_style_grouping_integral = Just $ F.Amount_Style_Grouping '_' [2] }
				, F.amount { F.amount_quantity = Decimal 0 123 } )]
		 , "1_23_456" ==>
			 [( mempty { F.amount_style_grouping_integral = Just $ F.Amount_Style_Grouping '_' [3, 2] }
				, F.amount { F.amount_quantity = Decimal 0 123456 } )]
		 , "1_23_456,7890_12345_678901" ==>
			 [( mempty
				 { F.amount_style_fractioning         = Just ','
				 , F.amount_style_grouping_integral   = Just $ F.Amount_Style_Grouping '_' [3, 2]
				 , F.amount_style_grouping_fractional = Just $ F.Amount_Style_Grouping '_' [4, 5, 6] }
				, F.amount { F.amount_quantity = Decimal 15 123456789012345678901 } )]
		 , "1_23_456.7890_12345_678901" ==>
			 [( mempty
				 { F.amount_style_fractioning         = Just '.'
				 , F.amount_style_grouping_integral   = Just $ F.Amount_Style_Grouping '_' [3, 2]
				 , F.amount_style_grouping_fractional = Just $ F.Amount_Style_Grouping '_' [4, 5, 6] }
				, F.amount { F.amount_quantity = Decimal 15 123456789012345678901 } )]
		 , "1,23,456.7890_12345_678901" ==>
			 [( mempty
				 { F.amount_style_fractioning         = Just '.'
				 , F.amount_style_grouping_integral   = Just $ F.Amount_Style_Grouping ',' [3, 2]
				 , F.amount_style_grouping_fractional = Just $ F.Amount_Style_Grouping '_' [4, 5, 6] }
				, F.amount { F.amount_quantity = Decimal 15 123456789012345678901 } )]
		 , "1.23.456,7890_12345_678901" ==>
			 [( mempty
				 { F.amount_style_fractioning         = Just ','
				 , F.amount_style_grouping_integral   = Just $ F.Amount_Style_Grouping '.' [3, 2]
				 , F.amount_style_grouping_fractional = Just $ F.Amount_Style_Grouping '_' [4, 5, 6] }
				, F.amount { F.amount_quantity = Decimal 15 123456789012345678901 } )]
		 , "123456_78901_2345.678_90_1" ==>
			 [( mempty
				 { F.amount_style_fractioning         = Just '.'
				 , F.amount_style_grouping_integral   = Just $ F.Amount_Style_Grouping '_' [4, 5, 6]
				 , F.amount_style_grouping_fractional = Just $ F.Amount_Style_Grouping '_' [3, 2] }
				, F.amount { F.amount_quantity = Decimal 6 123456789012345678901 } )]
		 , "$1" ==>
			 [( mempty
				 { F.amount_style_unit_side   = Just F.Amount_Style_Side_Left
				 , F.amount_style_unit_spaced = Just False }
			 , F.amount
				 { F.amount_quantity = Decimal 0 1
				 , F.amount_unit     = "$" } )]
		 , "1$" ==>
			 [( mempty
				 { F.amount_style_unit_side   = Just F.Amount_Style_Side_Right
				 , F.amount_style_unit_spaced = Just False }
			 , F.amount
				 { F.amount_quantity = Decimal 0 1
				 , F.amount_unit     = "$" } )]
		 , "$ 1" ==>
			 [( mempty
				 { F.amount_style_unit_side   = Just F.Amount_Style_Side_Left
				 , F.amount_style_unit_spaced = Just True }
			 , F.amount
				 { F.amount_quantity = Decimal 0 1
				 , F.amount_unit     = "$" } )]
		 , "1 $" ==>
			 [( mempty
				 { F.amount_style_unit_side   = Just F.Amount_Style_Side_Right
				 , F.amount_style_unit_spaced = Just True }
			 , F.amount
				 { F.amount_quantity = Decimal 0 1
				 , F.amount_unit     = "$" } )]
		 , "-$1" ==>
			 [( mempty
				 { F.amount_style_unit_side   = Just F.Amount_Style_Side_Left
				 , F.amount_style_unit_spaced = Just False }
			 , F.amount
				 { F.amount_quantity = Decimal 0 (-1)
				 , F.amount_unit     = "$" } )]
		 , "\"4 2\"1" ==>
			 [( mempty
				 { F.amount_style_unit_side   = Just F.Amount_Style_Side_Left
				 , F.amount_style_unit_spaced = Just False }
			 , F.amount
				 { F.amount_quantity = Decimal 0 1
				 , F.amount_unit     = "4 2" } )]
		 , "1\"4 2\"" ==>
			 [( mempty
				 { F.amount_style_unit_side   = Just F.Amount_Style_Side_Right
				 , F.amount_style_unit_spaced = Just False }
			 , F.amount
				 { F.amount_quantity = Decimal 0 1
				 , F.amount_unit     = "4 2" } )]
		 , "$1.000,00" ==>
			 [( mempty
				 { F.amount_style_fractioning       = Just ','
				 , F.amount_style_grouping_integral = Just $ F.Amount_Style_Grouping '.' [3]
				 , F.amount_style_unit_side         = Just F.Amount_Style_Side_Left
				 , F.amount_style_unit_spaced       = Just False }
				, F.amount
				 { F.amount_quantity = Decimal 2 100000
				 , F.amount_unit     = "$" } )]
		 , "1.000,00$" ==>
			 [( mempty
				 { F.amount_style_fractioning       = Just ','
				 , F.amount_style_grouping_integral = Just $ F.Amount_Style_Grouping '.' [3]
				 , F.amount_style_unit_side         = Just F.Amount_Style_Side_Right
				 , F.amount_style_unit_spaced       = Just False }
				, F.amount
				 { F.amount_quantity = Decimal 2 100000
				 , F.amount_unit     = "$" } )]
		 ]
	 , "read_posting_type" ~:
		let (==>) a (ty, ac) =
			let read (t::Text) = rights [R.runParser
				 (F.read_account <* R.eof) () "" t] in
			(~:) (Text.unpack a) $
			(~?=)
			 (F.read_posting_type <$> read a)
			 (F.Posting_Typed ty  <$> read (maybe a id ac))
		in TestList
		 [ "A"       ==> (F.Posting_Type_Regular, Nothing)
		 , "("       ==> (F.Posting_Type_Regular, Nothing)
		 , ")"       ==> (F.Posting_Type_Regular, Nothing)
		 , "()"      ==> (F.Posting_Type_Regular, Nothing)
		 , "( )"     ==> (F.Posting_Type_Regular, Nothing)
		 , "(A)"     ==> (F.Posting_Type_Virtual, Just "A")
		 , "(A:B:C)" ==> (F.Posting_Type_Virtual, Just "A:B:C")
		 , "A:B:C"   ==> (F.Posting_Type_Regular, Nothing)
		 , "(A):B:C" ==> (F.Posting_Type_Regular, Nothing)
		 , "A:(B):C" ==> (F.Posting_Type_Regular, Nothing)
		 , "A:B:(C)" ==> (F.Posting_Type_Regular, Nothing)
		 , "["       ==> (F.Posting_Type_Regular, Nothing)
		 , "]"       ==> (F.Posting_Type_Regular, Nothing)
		 , "[]"      ==> (F.Posting_Type_Regular, Nothing)
		 , "[ ]"     ==> (F.Posting_Type_Regular, Nothing)
		 , "[A]"     ==> (F.Posting_Type_Virtual_Balanced, Just "A")
		 , "[A:B:C]" ==> (F.Posting_Type_Virtual_Balanced, Just "A:B:C")
		 , "A:B:C"   ==> (F.Posting_Type_Regular, Nothing)
		 , "[A]:B:C" ==> (F.Posting_Type_Regular, Nothing)
		 , "A:[B]:C" ==> (F.Posting_Type_Regular, Nothing)
		 , "A:B:[C]" ==> (F.Posting_Type_Regular, Nothing)
		 ]
	 , "read_comment" ~:
		let (==>) (txt::Text, end) =
			(~:) (Text.unpack txt) .
			(~?=) (rights [R.runParser (F.read_comment <* end) () "" txt])
		in TestList
		 [ ("; some comment", R.eof) ==> [" some comment"]
		 , ("; some comment \n", R.newline <* R.eof) ==> [ " some comment " ]
		 , ("; some comment \r\n", R.string "\r\n" <* R.eof) ==> [ " some comment " ]
		 ]
	 , "read_comments" ~:
		let (==>) (txt::Text, end) =
			(~:) (Text.unpack txt) .
			(~?=) (rights [R.runParser (F.read_comments <* end) () "" txt])
		in TestList
		 [ ("; some comment\n  ; some other comment", R.eof) ==> [ [" some comment", " some other comment"] ]
		 , ("; some comment \n", R.string "\n" <* R.eof) ==> [ [" some comment "] ]
		 ]
	 , "read_tag_value" ~:
		let (==>) (txt::Text, end) =
			(~:) (Text.unpack txt) .
			(~?=) (rights [R.runParser (F.read_tag_value <* end) () "" txt])
		in TestList
		 [ (",", R.eof) ==> [","]
		 , (",\n", R.char '\n' <* R.eof) ==> [","]
		 , (",x", R.eof) ==> [",x"]
		 , (",x:", R.string ",x:" <* R.eof) ==> [""]
		 , ("v, v, n:", R.string ", n:" <* R.eof) ==> ["v, v"]
		 ]
	 , "read_tag" ~:
		let (==>) (txt::Text, end) =
			(~:) (Text.unpack txt) .
			(~?=) (rights [R.runParser (F.read_tag <* end) () "" txt])
		in TestList
		 [ ("Name:"        , R.eof)                    ==> [("Name":|[], "")]
		 , ("Name:Value"   , R.eof)                    ==> [("Name":|[], "Value")]
		 , ("Name:Value\n" , R.string "\n" <* R.eof)   ==> [("Name":|[], "Value")]
		 , ("Name:Val ue"  , R.eof)                    ==> [("Name":|[], "Val ue")]
		 , ("Name:,"       , R.eof)                    ==> [("Name":|[], ",")]
		 , ("Name:Val,ue"  , R.eof)                    ==> [("Name":|[], "Val,ue")]
		 , ("Name:Val,ue:" , R.string ",ue:" <* R.eof) ==> [("Name":|[], "Val")]
		 , ("Name:Val,ue :", R.eof)                    ==> [("Name":|[], "Val,ue :")]
		 ]
	 , "read_tags" ~:
		let (==>) (txt::Text) =
			(~:) (Text.unpack txt) .
			(~?=) (rights [R.runParser (F.read_tags <* R.eof) () "" txt]) .
			pure . Map.fromList
		in TestList
		 [ "Name:" ==> [ ("Name":|[], [""]) ]
		 , "Name:," ==> [ ("Name":|[], [","]) ]
		 , "Name:,Name:" ==> [ ("Name":|[], ["", ""]) ]
		 , "Name:,Name2:" ==>
			 [ ("Name":|[], [""])
			 , ("Name2":|[], [""])
			 ]
		 , "Name: , Name2:" ==>
			 [ ("Name":|[], [" "])
			 , ("Name2":|[], [""])
			 ]
		 , "Name:,Name2:,Name3:" ==>
			 [ ("Name":|[], [""])
			 , ("Name2":|[], [""])
			 , ("Name3":|[], [""])
			 ]
		 , "Name:Val ue,Name2:V a l u e,Name3:V al ue" ==>
			 [ ("Name":|[], ["Val ue"])
			 , ("Name2":|[], ["V a l u e"])
			 , ("Name3":|[], ["V al ue"])
			 ]
		 ]
	 , "read_posting" ~:
		let (==>) (txt::Text) =
			let read_context =
				 ( F.read_context (const ()) F.journal
				 ::F.Read_Context ()        ()) in
			(~:) (Text.unpack txt) .
			(~?=) (rights [R.runParser_with_Error
				 (F.read_posting <* R.eof) read_context "" txt]) .
			fmap (\p -> F.Posting_Typed F.Posting_Type_Regular
				p { F.posting_sourcepos = R.newPos "" 1 1 })
		in TestList
		 [ " A:B:C" ==> [F.posting ("A":|["B", "C"])]
		 , "A:B:C" ==> []
		 , " !A:B:C" ==> [(F.posting ("A":|["B", "C"]))
			 { F.posting_status = True }]
		 , " *A:B:C" ==> [(F.posting ("A":|["B", "C"]))
			 { F.posting_status = True }]
		 , " A:B:C $1" ==> [F.posting ("A":|["B", "C $1"])]
		 , " A:B:C  $1" ==> [(F.posting ("A":|["B", "C"]))
			 { F.posting_amounts = Map.fromList [("$", 1)] }]
		 , " A:B:C  $1 + 1€" ==> [(F.posting ("A":|["B", "C"]))
			 { F.posting_amounts = Map.fromList [("$", 1), ("€", 1)] }]
		 , " A:B:C  $1 + 1$" ==> [(F.posting ("A":|["B", "C"]))
			 { F.posting_amounts = Map.fromList [("$", 2)] }]
		 , " A:B:C  $1 + 1$ + 1$" ==> [(F.posting ("A":|["B", "C"]))
			 { F.posting_amounts = Map.fromList [("$", 3)] }]
		 , " A:B:C ; some comment" ==> [(F.posting ("A":|["B", "C"]))
			 { F.posting_amounts  = Map.fromList []
			 , F.posting_comments = [" some comment"] }]
		 , " A:B:C ; some comment\n  ; some other comment" ==>
			[(F.posting ("A":|["B", "C"]))
			 { F.posting_amounts  = Map.fromList []
			 , F.posting_comments = [" some comment", " some other comment"] }]
		 , " A:B:C  $1 ; some comment" ==>
			[(F.posting ("A":|["B", "C"]))
			 { F.posting_amounts  = Map.fromList [("$", 1)]
			 , F.posting_comments = [" some comment"] }]
		 , " A:B:C ; N:V" ==>
			[(F.posting ("A":|["B", "C"]))
			 { F.posting_comments = [" N:V"]
			 , F.posting_tags     = Posting.Posting_Tags $
				Tag.from_List [ ("N":|[], "V") ] }]
		 , " A:B:C ; some comment N:V" ==>
			[(F.posting ("A":|["B", "C"]))
			 { F.posting_comments = [" some comment N:V"]
			 , F.posting_tags = Posting.Posting_Tags $
				Tag.from_List [ ("N":|[], "V") ] }]
		 , " A:B:C ; some comment N:V v, N2:V2 v2" ==>
			[(F.posting ("A":|["B", "C"]))
			 { F.posting_comments = [" some comment N:V v, N2:V2 v2"]
			 , F.posting_tags     = Posting.Posting_Tags $
				Tag.from_List
				 [ ("N":|[], "V v")
				 , ("N2":|[], "V2 v2") ] }]
		 , " A:B:C ; N:V\n ; N:V2" ==>
			[(F.posting ("A":|["B", "C"]))
			 { F.posting_comments = [" N:V", " N:V2"]
			 , F.posting_tags     = Posting.Posting_Tags $
				Tag.from_List
				 [ ("N":|[], "V")
				 , ("N":|[], "V2")
				 ] }]
		 , " A:B:C ; N:V\n ; N2:V" ==>
			[(F.posting ("A":|["B", "C"]))
			 { F.posting_comments = [" N:V", " N2:V"]
			 , F.posting_tags     = Posting.Posting_Tags $
				Tag.from_List
				 [ ("N":|[], "V")
				 , ("N2":|[], "V")
				 ] }]
		 , " A:B:C ; date:2001-01-01" ==>
			[(F.posting ("A":|["B", "C"]))
			 { F.posting_comments = [" date:2001-01-01"]
			 , F.posting_dates =
				 [ Time.zonedTimeToUTC $
					Time.ZonedTime
					 (Time.LocalTime
						 (Time.fromGregorian 2001 01 01)
						 (Time.TimeOfDay 0 0 0))
					 Time.utc
				 ]
			 , F.posting_tags = Posting.Posting_Tags $
				Tag.from_List
				 [ ("date":|[], "2001-01-01") ] }]
		 , " (A:B:C) = Right (A:B:C)" ~:
			 (rights [R.runParser_with_Error
				 (F.read_posting <* R.eof)
					 ( F.read_context (const ()) F.journal
					 ::F.Read_Context () ())
					 "" (" (A:B:C)"::Text)]) ~?=
			 [F.Posting_Typed
				 F.Posting_Type_Virtual
				 (F.posting ("A":|["B", "C"]))]
		 , " [A:B:C] = Right [A:B:C]" ~:
			 (rights [R.runParser_with_Error
				 (F.read_posting <* R.eof)
					 ( F.read_context (const ()) F.journal
					 ::F.Read_Context () ())
					 "" (" [A:B:C]"::Text)]) ~?=
			 [F.Posting_Typed
				 F.Posting_Type_Virtual_Balanced
				 (F.posting ("A":|["B", "C"]))]
		 ]
	 , "read_transaction" ~:
		let (==>) (txt::Text) =
			let read_context =
				 ( F.read_context (const ()) F.journal
				 ::F.Read_Context () ()) in
			(~:) (Text.unpack txt) .
			(~?=) (rights [R.runParser_with_Error
				 (F.read_transaction <* R.eof) read_context "" txt]) .
			fmap (\t -> t { F.transaction_sourcepos = R.newPos "" 1 1 })
		in TestList
		 [ "2000-01-01 some wording\n A:B:C  $1\n a:b:c" ==>
			[F.transaction
			 { F.transaction_dates=
				 ( Time.zonedTimeToUTC $
					Time.ZonedTime
					 (Time.LocalTime
						 (Time.fromGregorian 2000 01 01)
						 (Time.TimeOfDay 0 0 0))
					 (Time.utc)
				 , [] )
			 , F.transaction_wording="some wording"
			 , F.transaction_postings = F.postings_by_account
				 [ (F.posting ("A":|["B", "C"]))
					 { F.posting_amounts = Map.fromList [ ("$", 1) ]
					 , F.posting_sourcepos = R.newPos "" 2 1 }
				 , (F.posting ("a":|["b", "c"]))
					 { F.posting_amounts = Map.fromList [ ("$", -1) ]
					 , F.posting_sourcepos = R.newPos "" 3 1 }
				 ]
			 }]
		 , "2000-01-01 some wording\n A:B:C  $1\n a:b:c\n" ==> []
		 , "2000-01-01 some wording  ;  some comment\n ; some other;comment\n ; some Tag:\n ; some last comment\n A:B:C  $1\n a:b:c" ==>
			[F.transaction
			 { F.transaction_comments_after =
				 [ "  some comment"
				 , " some other;comment"
				 , " some Tag:"
				 , " some last comment"
				 ]
			 , F.transaction_dates=
				 ( Time.zonedTimeToUTC $
					Time.ZonedTime
					 (Time.LocalTime
						 (Time.fromGregorian 2000 01 01)
						 (Time.TimeOfDay 0 0 0))
					 (Time.utc)
				 , [] )
			 , F.transaction_wording="some wording"
			 , F.transaction_postings = F.postings_by_account
				 [ (F.posting ("A":|["B", "C"]))
					 { F.posting_amounts = Map.fromList [ ("$", 1) ]
					 , F.posting_sourcepos = R.newPos "" 5 1 }
				 , (F.posting ("a":|["b", "c"]))
					 { F.posting_amounts = Map.fromList [ ("$", -1) ]
					 , F.posting_sourcepos = R.newPos "" 6 1 } ]
			 , F.transaction_tags = Transaction.Transaction_Tags $
				Tag.from_List [ ("Tag":|[], "") ] }]
		 ]
	 , "read_journal" ~: TestList
		 [ "2000-01-01 1° wording\\n A:B:C  $1\\n a:b:c\\n2000-01-02 2° wording\\n A:B:C  $1\\n x:y:z" ~: TestCase $ do
			jnl <- liftIO $
				R.runParserT_with_Error
				 (F.read_journal "" {-<* R.eof-})
					 ( F.read_context id  F.journal
					 ::F.Read_Context (F.Charted F.Transaction)
					                      ([F.Charted F.Transaction]))
					 "" ("2000-01-01 1° wording\n A:B:C  $1\n a:b:c\n2000-01-02 2° wording\n A:B:C  $1\n x:y:z"::Text)
			((\j -> j{F.journal_last_read_time=Date.nil}) <$>
				Data.Either.rights [jnl])
			 @?=
			 [F.journal
				 { F.journal_content =
					 fmap (Chart.Charted mempty) $
					 [ F.transaction
						 { F.transaction_dates=
							 ( Time.zonedTimeToUTC $
								Time.ZonedTime
								 (Time.LocalTime
									 (Time.fromGregorian 2000 01 02)
									 (Time.TimeOfDay 0 0 0))
								 (Time.utc)
							 , [] )
						 , F.transaction_wording="2° wording"
						 , F.transaction_postings = F.postings_by_account
							 [ (F.posting ("A":|["B", "C"]))
								 { F.posting_amounts = Map.fromList [ ("$", 1) ]
								 , F.posting_sourcepos = R.newPos "" 5 1
								 }
							 , (F.posting ("x":|["y", "z"]))
								 { F.posting_amounts = Map.fromList [ ("$", -1) ]
								 , F.posting_sourcepos = R.newPos "" 6 1
								 }
							 ]
						 , F.transaction_sourcepos = R.newPos "" 4 1
						 }
					 , F.transaction
						 { F.transaction_dates=
							 ( Time.zonedTimeToUTC $
								Time.ZonedTime
								 (Time.LocalTime
									 (Time.fromGregorian 2000 01 01)
									 (Time.TimeOfDay 0 0 0))
								 (Time.utc)
							 , [] )
						 , F.transaction_wording="1° wording"
						 , F.transaction_postings = F.postings_by_account
							 [ (F.posting ("A":|["B", "C"]))
								 { F.posting_amounts = Map.fromList [ ("$", 1) ]
								 , F.posting_sourcepos = R.newPos "" 2 1
								 }
							 , (F.posting ("a":|["b", "c"]))
								 { F.posting_amounts = Map.fromList [ ("$", -1) ]
								 , F.posting_sourcepos = R.newPos "" 3 1
								 }
							 ]
						 , F.transaction_sourcepos = R.newPos "" 1 1
						 }
					 ]
				 , F.journal_files = [""]
				 , F.journal_amount_styles = F.Amount_Styles $ Map.fromList
					 [ ( F.Unit "$"
						 , mempty
							 { F.amount_style_unit_side   = Just F.Amount_Style_Side_Left
							 , F.amount_style_unit_spaced = Just False }
						 )
					 ]
				 }
			 ]
		 ]
	 , "read_journal" ~: TestList
		 [ let (==>) (txt::Text) e =
				(~:) (Text.unpack txt) $
				TestCase $ do
				jnl <-
					liftIO $
					right (\j -> j{F.journal_last_read_time=Date.nil}) <$>
					R.runParserT_with_Error
					 (F.read_journal "" {-<* R.eof-})
						 ( F.read_context id  F.journal
						 ::F.Read_Context (F.Charted F.Transaction)
						                 ([F.Charted F.Transaction]))
						 "" (txt::Text)
				(@?=) (rights [jnl]) e
			in TestList
			 [ Text.unlines
				 [ "2000-01-01 1° wording"
				 , " A:B:C  $1"
				 , " a:b:c"
				 , "2000-01-02 2° wording"
				 , " A:B:C  $1"
				 , " x:y:z"
				 ] ==>
				[ F.journal
					 { F.journal_content =
						 fmap (Chart.Charted mempty) $
						 [ F.transaction
							 { F.transaction_dates=
								 ( Time.zonedTimeToUTC $
									Time.ZonedTime
									 (Time.LocalTime
										 (Time.fromGregorian 2000 01 02)
										 (Time.TimeOfDay 0 0 0))
									 (Time.utc)
								 , [] )
							 , F.transaction_wording="2° wording"
							 , F.transaction_postings = F.postings_by_account
								 [ (F.posting ("A":|["B", "C"]))
									 { F.posting_amounts = Map.fromList [ ("$", 1) ]
									 , F.posting_sourcepos = R.newPos "" 5 1
									 }
								 , (F.posting ("x":|["y", "z"]))
									 { F.posting_amounts = Map.fromList [ ("$", -1) ]
									 , F.posting_sourcepos = R.newPos "" 6 1
									 }
								 ]
							 , F.transaction_sourcepos = R.newPos "" 4 1
							 }
						 , F.transaction
							 { F.transaction_dates=
								 ( Time.zonedTimeToUTC $
									Time.ZonedTime
									 (Time.LocalTime
										 (Time.fromGregorian 2000 01 01)
										 (Time.TimeOfDay 0 0 0))
									 (Time.utc)
								 , [] )
							 , F.transaction_wording="1° wording"
							 , F.transaction_postings = F.postings_by_account
								 [ (F.posting ("A":|["B", "C"]))
									 { F.posting_amounts = Map.fromList [ ("$", 1) ]
									 , F.posting_sourcepos = R.newPos "" 2 1
									 }
								 , (F.posting ("a":|["b", "c"]))
									 { F.posting_amounts = Map.fromList [ ("$", -1) ]
									 , F.posting_sourcepos = R.newPos "" 3 1
									 }
								 ]
							 , F.transaction_sourcepos = R.newPos "" 1 1
							 }
						 ]
					 , F.journal_files = [""]
					 , F.journal_amount_styles = F.Amount_Styles $ Map.fromList
						 [ ( F.Unit "$"
							 , mempty
								 { F.amount_style_unit_side   = Just F.Amount_Style_Side_Left
								 , F.amount_style_unit_spaced = Just False }
							 )
						 ]
					 }
				 ]
			 ]
		 ]
	 ]
 , "Write" ~: TestList
	 [ "write_date" ~:
		let (==>) (txt::Text) e =
			(~:) (Text.unpack txt) $
			(~?=)
			 (F.write
				F.write_style
				 { F.write_style_color = False
				 , F.write_style_align = True } .
				F.write_date <$>
				rights [R.runParser_with_Error
				 (F.read_date id Nothing <* R.eof) () "" txt])
			 [e]
		in TestList
		 [ "" ~:
			((F.write
			F.write_style
			 { F.write_style_color = False
			 , F.write_style_align = True } $
			F.write_date Date.nil)
			~?= "1970-01-01")
		 , "2000-01-01"                ==> "2000-01-01"
		 , "2000-01-01_12:34:51_CET"   ==> "2000-01-01_11:34:51"
		 , "2000-01-01_12:34:51+01:10" ==> "2000-01-01_11:24:51"
		 , "2000-01-01_12:34:51-01:10" ==> "2000-01-01_13:44:51"
		 , "2000-01-01_01:02:03"       ==> "2000-01-01_01:02:03"
		 , "2000-01-01_01:02"          ==> "2000-01-01_01:02"
		 , "2000-01-01_01:00"          ==> "2000-01-01_01:00"
		 ]
	 , "write_amount" ~:
		let (<==) (txt::Text) e =
			(~:) (Text.unpack txt) $
			(~?=)
			 (F.write
				F.write_style
				 { F.write_style_color = False
				 , F.write_style_align = True } $
				F.write_amount e)
			 (TL.fromStrict txt)
		in TestList
		 [ "0" <==
			( mempty
			, F.amount )
		 , "0.00" <==
			( mempty
			, F.amount { F.amount_quantity = Decimal 2 0 } )
		 , "123" <==
			( mempty
			, F.amount { F.amount_quantity = Decimal 0 123 } )
		 , "-123" <==
			( mempty
			, F.amount { F.amount_quantity = Decimal 0 (- 123) } )
		 , "12.3" <==
			( mempty { F.amount_style_fractioning = Just '.' }
			, F.amount { F.amount_quantity = Decimal 1 123 } )
		 , "1,234.56" <==
			( mempty
				 { F.amount_style_fractioning       = Just '.'
				 , F.amount_style_grouping_integral = Just $ F.Amount_Style_Grouping ',' [3]
				 }
			, F.amount { F.amount_quantity = Decimal 2 123456 })
		 , "123,456,789,01,2.3456789" <==
			( mempty
				 { F.amount_style_fractioning       = Just '.'
				 , F.amount_style_grouping_integral = Just $ F.Amount_Style_Grouping ',' [1, 2, 3]
				 }
			, F.amount { F.amount_quantity = Decimal 7 1234567890123456789 } )
		 , "1234567.8_90_123_456_789" <==
			( mempty
				 { F.amount_style_fractioning         = Just '.'
				 , F.amount_style_grouping_fractional = Just $ F.Amount_Style_Grouping '_' [1, 2, 3]
				 }
			, F.amount { F.amount_quantity = Decimal 12 1234567890123456789 })
		 , "1,2,3,4,5,6,7,89,012.3456789" <==
			( mempty
				 { F.amount_style_fractioning       = Just '.'
				 , F.amount_style_grouping_integral = Just $ F.Amount_Style_Grouping ',' [3, 2, 1]
				 }
			, F.amount { F.amount_quantity = Decimal 7 1234567890123456789 })
		 , "1234567.890_12_3_4_5_6_7_8_9" <==
			( mempty
				 { F.amount_style_fractioning         = Just '.'
				 , F.amount_style_grouping_fractional = Just $ F.Amount_Style_Grouping '_' [3, 2, 1]
				 }
			, F.amount { F.amount_quantity = Decimal 12 1234567890123456789 })
		 ]
	 , "write_amount_length" ~:
		let (==>) (txt::Text) =
			(~:) (Text.unpack txt) $
			(~?=)
			 (F.write_amount_length <$>
				rights [R.runParser (F.read_amount <* R.eof) () "" txt])
			 [Text.length txt]
		in TestList $ (==>) <$>
		 [ "0.00"
		 , "123"
		 , "-123"
		 , "12.3"
		 , "12.5"
		 , "12.3"
		 , "1,234.56"
		 , "123,456,789,01,2.3456789"
		 , "1234567.8_90_123_456_789"
		 , "1,2,3,4,5,6,7,89,012.3456789"
		 , "1234567.890_12_3_4_5_6_7_8_9"
		 , "1000000.000_00_0_0_0_0_0_0_0"
		 , "999"
		 , "1000"
		 , "10,00€"
		 , "10,00 €"
		 , "€10,00"
		 , "€ 10,00"
		 , "EUR 10,00"
		 , "10,00 EUR"
		 , "\"4 2\" 10,00"
		 ]
	 , "write_account" ~:
		let (==>) txt =
			(~:) (Text.unpack txt) $
			(~?=)
			 (let read (t::Text) =
					rights [R.runParser
					 (F.read_account <* R.eof)
					 () "" t] in
				F.write
				 F.write_style
					 { F.write_style_color = False
					 , F.write_style_align = True } <$>
				 (read txt >>= \a ->
					let F.Posting_Typed ty ac = F.read_posting_type a in
					return $ F.write_account ty ac)
			 )
			 [TL.fromStrict txt]
		in TestList $ (==>) <$>
		 [ "A"
		 , "(A:B:C)"
		 , "[A:B:C]"
		 ]
	 , "write_transaction" ~:
		let (==>) (txt::Text) =
			(~:) (Text.unpack txt) .
			(~?=) (
				let write (txn, ctx) =
					F.write
					 F.write_style
						 { F.write_style_color = False
						 , F.write_style_align = True } $
						let jnl = F.read_context_journal ctx in
						let sty = F.journal_amount_styles jnl in
						F.write_transaction sty txn in
				either
				 (const []) {-(pure . TL.pack . show)-}
				 (pure . write) $
				R.runParser_with_Error
				 (R.and_state (F.read_transaction <* R.newline <* R.eof))
				 ( F.read_context Chart.charted F.journal
				 ::F.Read_Context F.Transaction [F.Transaction] )
				 "" txt)
		in TestList $
		 [ Text.unlines
			 [ "2000-01-01 some wording"
			 , "  A:B:C  $1"
			 , "  a:b:c"
			 ] ==> [TL.unlines
			 [ "2000-01-01 some wording"
			 , "  A:B:C   $1"
			 , "  a:b:c  $-1"
			 ]]
		 , Text.unlines
			 [ "2000-01-01 some wording"
			 , "  A:B:C  $1"
			 , "  a:b:c"
			 , "   ; first comment"
			 , "   ; second comment"
			 , "   ; third comment"
			 ] ==> [TL.unlines
			 [ "2000-01-01 some wording"
			 , "  A:B:C   $1"
			 , "  a:b:c  $-1"
			 , "   ; first comment"
			 , "   ; second comment"
			 , "   ; third comment"
			 ]]
		 , Text.unlines
			 [ "2000-01-01 some wording"
			 , "  A:B:C       $1"
			 , "  AA:BB:CC  $123"
			 ] ==> []
		 ] ++
		 [ "nil" ~:
			((F.write
			F.write_style
			 { F.write_style_color = False
			 , F.write_style_align = True } $
			F.write_transaction
			F.amount_styles
			F.transaction)
			~?= "1970-01-01\n\n")
		 ]
	 ]
 ]