
module Zero.Exchange.Internal
  (
    -- * Type synonyms
    CurrencyPair
  , Currency
  , Amount
  , Price
  , BidEntry
  , AskEntry
  , MarketBidEntry
  , MarketAskEntry
  , UserId
  , OrderId
  , OrderBookId
  , BidId
  , AskId
  , EntryId
  , TradeId

    -- * Data types
  , LimitOrder(..)
  , MarketOrder(..)
  , Bid(..)
  , Ask(..)
  , OrderBook(..)
  , SingleEntry(..)
  , DoubleEntry(..)
  , Trade(..)

    -- * Functions
  , newDoubleEntry
  , newTrade
  , newBid
  , newAsk
  ) where

import           GHC.Generics (Generic)
import           Data.Text (Text)
import           Data.Function (on)
import           ProjectM36.Tupleable
import           Zero.Crypto (generateUUID)

------------------------------------------------------------------------------

type UserId = Text
type Currency = Text
type CurrencyPair = (Currency, Currency)
-- FIXME: Should be Integer
type Amount = Int
type Price = Double
type OrderId = Text
type OrderBookId = Text
type BidId = Text
type AskId = Text
type EntryId = Text
type TradeId = Text

data LimitOrder = LimitOrder
  { lo_id         :: OrderId
  , lo_user       :: UserId
  , lo_fromAmount :: Amount
  , lo_toAmount   :: Amount
  } deriving (Eq, Show, Generic)

instance Tupleable LimitOrder

newLimitOrder :: UserId -> Amount -> Amount -> IO LimitOrder
newLimitOrder uid fromAmount toAmount = do
  id <- generateUUID
  return $ LimitOrder id uid fromAmount toAmount

data MarketOrder = MarketOrder
  { mo_id     :: OrderId
  , mo_user   :: UserId
  , mo_amount :: Amount
  } deriving (Eq, Show, Generic)

instance Tupleable MarketOrder

newMarketOrder :: UserId -> Amount -> IO MarketOrder
newMarketOrder uid amount = do
  id <- generateUUID
  return $ MarketOrder id uid amount

data Bid = Bid
  { b_id    :: BidId
  , b_price :: Price
  , b_order :: OrderId
  } deriving (Eq, Show, Generic)

data Ask = Ask
  { a_id    :: AskId
  , a_price :: Price
  , a_order :: OrderId
  } deriving (Eq, Show, Generic)

instance Tupleable Bid
instance Tupleable Ask

type BidEntry = (Bid, LimitOrder)
type MarketBidEntry = (Bid, MarketOrder)

type AskEntry = (Ask, LimitOrder)
type MarketAskEntry = (Ask, MarketOrder)

newBid :: UserId -> Amount -> Amount -> IO BidEntry
newBid user fromAmount toAmount = do
  id <- generateUUID
  let price = ((/) `on` fromIntegral) fromAmount toAmount
  order <- newLimitOrder user fromAmount toAmount
  return (Bid id price (lo_id order), order)

newAsk :: UserId -> Amount -> Amount -> IO AskEntry
newAsk user fromAmount toAmount = do
  id <- generateUUID
  let price = ((/) `on` fromIntegral) fromAmount toAmount
  order <- newLimitOrder user fromAmount toAmount
  return (Ask id price (lo_id order), order)

data OrderBook = OrderBook
  { ob_id           :: OrderBookId
  , ob_fromCurrency :: Currency
  , ob_toCurrency   :: Currency
  } deriving (Eq, Show, Generic)

instance Tupleable OrderBook

data SingleEntry = SingleEntry
  { se_id       :: EntryId
  , se_account  :: UserId
  , se_currency :: Currency
  , se_amount   :: Amount
  } deriving (Eq, Show, Generic)

instance Tupleable SingleEntry

data DoubleEntry = DoubleEntry
  { de_id          :: EntryId
  , de_fromAccount :: UserId
  , de_toAccount   :: UserId
  , de_currency    :: Currency
  , de_amount      :: Amount
  } deriving (Eq, Show, Generic)

instance Tupleable DoubleEntry

newDoubleEntry :: UserId -> UserId -> Currency -> Amount -> IO DoubleEntry
newDoubleEntry from to currency amount = do
  id <- generateUUID
  return $ DoubleEntry id from to currency amount

data Trade = Trade
  { t_id   :: TradeId
  , t_from :: EntryId
  , t_to   :: EntryId
  } deriving (Eq, Show, Generic)

instance Tupleable Trade

newTrade :: EntryId -> EntryId -> IO Trade
newTrade uid1 uid2 = do
  id <- generateUUID
  return $ Trade id uid1 uid2
