-- | Essential data types used to make Transactions and Postings.
module Penny.Lincoln.Bits
  ( module Penny.Lincoln.Bits.Open
  , module Penny.Lincoln.Bits.DateTime
  , module Penny.Lincoln.Bits.Price
  , module Penny.Lincoln.Bits.Qty
  , PricePoint ( .. )

  -- * Aggregates
  , TopLineCore(..)
  , emptyTopLineCore
  , TopLineFileMeta(..)
  , TopLineData(..)
  , emptyTopLineData
  , PostingCore(..)
  , emptyPostingCore
  , PostingFileMeta(..)
  , PostingData(..)
  , emptyPostingData
  ) where


import Data.Monoid (mconcat)
import Penny.Lincoln.Bits.Open
import Penny.Lincoln.Bits.DateTime
import Penny.Lincoln.Bits.Qty
import Penny.Lincoln.Bits.Price

import qualified Penny.Lincoln.Bits.Open as O
import qualified Penny.Lincoln.Bits.DateTime as DT
import qualified Penny.Lincoln.Bits.Price as Pr
import qualified Penny.Lincoln.Equivalent as Ev
import Penny.Lincoln.Equivalent ((==~))

data PricePoint = PricePoint { dateTime :: DT.DateTime
                             , price :: Pr.Price
                             , ppSide :: Maybe O.Side
                             , ppSpaceBetween :: Maybe O.SpaceBetween
                             , priceLine :: Maybe O.PriceLine }
                  deriving (Eq, Show)


-- | PricePoint are equivalent if the dateTime and the Price are
-- equivalent. Other elements of the PricePoint are ignored.
instance Ev.Equivalent PricePoint where
  equivalent (PricePoint dx px _ _ _) (PricePoint dy py _ _ _) =
    dx ==~ dy && px ==~ py
  compareEv (PricePoint dx px _ _ _) (PricePoint dy py _ _ _) =
    mconcat [ Ev.compareEv dx dy
            , Ev.compareEv px py ]

-- | All the data that a TopLine might have.
data TopLineData = TopLineData
  { tlCore :: TopLineCore
  , tlFileMeta :: Maybe TopLineFileMeta
  , tlGlobal :: Maybe O.GlobalTransaction
  } deriving (Eq, Show)

emptyTopLineData :: DT.DateTime -> TopLineData
emptyTopLineData dt = TopLineData (emptyTopLineCore dt) Nothing Nothing

-- | Every TopLine has this data.
data TopLineCore = TopLineCore
  { tDateTime :: DT.DateTime
  , tNumber :: Maybe O.Number
  , tFlag :: Maybe O.Flag
  , tPayee :: Maybe O.Payee
  , tMemo :: Maybe O.Memo
  } deriving (Eq, Show)

-- | TopLineCore are equivalent if their dates are equivalent and if
-- everything else is equal.
instance Ev.Equivalent TopLineCore where
  equivalent x y =
    tDateTime x ==~ tDateTime y
    && tNumber x == tNumber y
    && tFlag x == tFlag y
    && tPayee x == tPayee y
    && tMemo x == tMemo y

  compareEv x y = mconcat
    [ Ev.compareEv (tDateTime x) (tDateTime y)
    , compare (tNumber x) (tNumber y)
    , compare (tFlag x) (tFlag y)
    , compare (tPayee x) (tPayee y)
    , compare (tMemo x) (tMemo y)
    ]

emptyTopLineCore :: DT.DateTime -> TopLineCore
emptyTopLineCore dt = TopLineCore dt Nothing Nothing Nothing Nothing

-- | TopLines from files have this metadata.
data TopLineFileMeta = TopLineFileMeta
  { tFilename :: O.Filename
  , tTopLineLine :: O.TopLineLine
  , tTopMemoLine :: Maybe O.TopMemoLine
  , tFileTransaction :: O.FileTransaction
  } deriving (Eq, Show)


-- | All Postings have this data.
data PostingCore = PostingCore
  { pPayee :: Maybe O.Payee
  , pNumber :: Maybe O.Number
  , pFlag :: Maybe O.Flag
  , pAccount :: O.Account
  , pTags :: O.Tags
  , pMemo :: Maybe O.Memo
  , pSide :: Maybe O.Side
  , pSpaceBetween :: Maybe O.SpaceBetween
  } deriving (Eq, Show)

-- | Two PostingCore are equivalent if the Tags are equivalent and the
-- other data is equal, exlucing the Side and the SpaceBetween, which are not considered at all.
instance Ev.Equivalent PostingCore where
  equivalent (PostingCore p1 n1 f1 a1 t1 m1 _ _)
             (PostingCore p2 n2 f2 a2 t2 m2 _ _)
    = p1 == p2 && n1 == n2 && f1 == f2
    && a1 == a2 && t1 ==~ t2 && m1 == m2

  compareEv (PostingCore p1 n1 f1 a1 t1 m1 _ _)
            (PostingCore p2 n2 f2 a2 t2 m2 _ _)
    = mconcat
        [ compare p1 p2
        , compare n1 n2
        , compare f1 f2
        , compare a1 a2
        , Ev.compareEv t1 t2
        , compare m1 m2
        ]

emptyPostingCore :: O.Account -> PostingCore
emptyPostingCore ac = PostingCore
  { pPayee = Nothing
  , pNumber = Nothing
  , pFlag = Nothing
  , pAccount = ac
  , pTags = O.Tags []
  , pMemo = Nothing
  , pSide = Nothing
  , pSpaceBetween = Nothing
  }

-- | Postings from files have this additional data.
data PostingFileMeta = PostingFileMeta
  { pPostingLine :: O.PostingLine
  , pFilePosting :: O.FilePosting
  } deriving (Eq, Show)


-- | All the data that a Posting might have.
data PostingData = PostingData
  { pdCore :: PostingCore
  , pdFileMeta :: Maybe PostingFileMeta
  , pdGlobal :: Maybe O.GlobalPosting
  } deriving (Eq, Show)

emptyPostingData :: O.Account -> PostingData
emptyPostingData ac = PostingData
  { pdCore = emptyPostingCore ac
  , pdFileMeta = Nothing
  , pdGlobal = Nothing
  }