{-# LANGUAGE OverloadedStrings          #-}
{-# LANGUAGE DeriveGeneric              #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
module Bittrex.Types where

import           Data.Aeson
import           Data.Aeson.Types     hiding (parse)
import           Data.ByteString      (ByteString)
import qualified Data.ByteString      as B
import qualified Data.ByteString.Lazy as L
import           Data.Fixed
import           Data.Scientific
import           Data.Text            (Text)
import qualified Data.Text            as T
import           Data.Time
import           Data.Time.Format
import           GHC.Generics
import           Text.Read            (readMaybe)

data E8

instance HasResolution E8 where
  resolution _ = 10^8

type Params = [(String,String)]

data APIType
  = PublicAPI
  | AccountAPI
  | MarketAPI
  deriving (Eq)

newtype Time = Time UTCTime
  deriving (Show, Eq)

instance FromJSON Time where
  parseJSON = withText "Time" $ \t -> do
    pure $ Time $ parse (T.unpack t)
      where
        parse :: String -> UTCTime
        parse =
          parseTimeOrError True defaultTimeLocale $
            iso8601DateFormat (Just "%H:%M:%S%Q")


instance Show APIType where
  show AccountAPI = "account"
  show PublicAPI  = "public"
  show MarketAPI  = "market"

data APIOpts
  = APIOpts
  { apiType :: APIType
  , qParams :: Params
  , version :: String
  , path    :: String
  , keys    :: APIKeys
  } deriving (Show, Eq)

data ErrorMessage
  = BittrexError BittrexError
  | DecodeFailure String Value
  deriving (Show, Eq, Generic)

data BittrexError
  = INVALID_MARKET
  | MARKET_NOT_PROVIDED
  | APIKEY_NOT_PROVIDED
  | APIKEY_INVALID
  | INVALID_SIGNATURE
  | NONCE_NOT_PROVIDED
  | INVALID_PERMISSION
  | INVALID_CURRENCY
  | WITHDRAWAL_TOO_SMALL
  | CURRENCY_DOES_NOT_EXIST
  deriving (Show, Eq, Generic)

instance FromJSON ErrorMessage
instance FromJSON BittrexError

data MarketName
  = NewMarket Text
  | MarketName MarketName'
  deriving (Show, Eq)

instance FromJSON MarketName where
  parseJSON = withText "Market Name" $ \t ->
    pure $ case readMaybe $ T.unpack (T.replace "-" "_" t) of
       Nothing -> NewMarket t
       Just k -> MarketName k

data MarketName'
  = BTC_LTC
  | BTC_DOGE
  | BTC_VTC
  | BTC_PPC
  | BTC_FTC
  | BTC_RDD
  | BTC_NXT
  | BTC_DASH
  | BTC_POT
  | BTC_BLK
  | BTC_EMC2
  | BTC_XMY
  | BTC_AUR
  | BTC_EFL
  | BTC_GLD
  | BTC_SLR
  | BTC_PTC
  | BTC_GRS
  | BTC_NLG
  | BTC_RBY
  | BTC_XWC
  | BTC_MONA
  | BTC_THC
  | BTC_ENRG
  | BTC_ERC
  | BTC_VRC
  | BTC_CURE
  | BTC_XMR
  | BTC_CLOAK
  | BTC_START
  | BTC_KORE
  | BTC_XDN
  | BTC_TRUST
  | BTC_NAV
  | BTC_XST
  | BTC_BTCD
  | BTC_VIA
  | BTC_PINK
  | BTC_IOC
  | BTC_CANN
  | BTC_SYS
  | BTC_NEOS
  | BTC_DGB
  | BTC_BURST
  | BTC_EXCL
  | BTC_SWIFT
  | BTC_DOPE
  | BTC_BLOCK
  | BTC_ABY
  | BTC_BYC
  | BTC_XMG
  | BTC_BLITZ
  | BTC_BAY
  | BTC_FAIR
  | BTC_SPR
  | BTC_VTR
  | BTC_XRP
  | BTC_GAME
  | BTC_COVAL
  | BTC_NXS
  | BTC_XCP
  | BTC_BITB
  | BTC_GEO
  | BTC_FLDC
  | BTC_GRC
  | BTC_FLO
  | BTC_NBT
  | BTC_MUE
  | BTC_XEM
  | BTC_CLAM
  | BTC_DMD
  | BTC_GAM
  | BTC_SPHR
  | BTC_OK
  | BTC_SNRG
  | BTC_PKB
  | BTC_CPC
  | BTC_AEON
  | BTC_ETH
  | BTC_GCR
  | BTC_TX
  | BTC_BCY
  | BTC_EXP
  | BTC_INFX
  | BTC_OMNI
  | BTC_AMP
  | BTC_AGRS
  | BTC_XLM
  | USDT_BTC
  | BTC_CLUB
  | BTC_VOX
  | BTC_EMC
  | BTC_FCT
  | BTC_MAID
  | BTC_EGC
  | BTC_SLS
  | BTC_RADS
  | BTC_DCR
  | BTC_BSD
  | BTC_XVG
  | BTC_PIVX
  | BTC_XVC
  | BTC_MEME
  | BTC_STEEM
  | BTC_2GIVE
  | BTC_LSK
  | BTC_PDC
  | BTC_BRK
  | BTC_DGD
  | ETH_DGD
  | BTC_WAVES
  | BTC_RISE
  | BTC_LBC
  | BTC_SBD
  | BTC_BRX
  | BTC_ETC
  | ETH_ETC
  | BTC_STRAT
  | BTC_UNB
  | BTC_SYNX
  | BTC_TRIG
  | BTC_EBST
  | BTC_VRM
  | BTC_SEQ
  | BTC_REP
  | BTC_SHIFT
  | BTC_ARDR
  | BTC_XZC
  | BTC_NEO
  | BTC_ZEC
  | BTC_ZCL
  | BTC_IOP
  | BTC_GOLOS
  | BTC_UBQ
  | BTC_KMD
  | BTC_GBG
  | BTC_SIB
  | BTC_ION
  | BTC_LMC
  | BTC_QWARK
  | BTC_CRW
  | BTC_SWT
  | BTC_MLN
  | BTC_ARK
  | BTC_DYN
  | BTC_TKS
  | BTC_MUSIC
  | BTC_DTB
  | BTC_INCNT
  | BTC_GBYTE
  | BTC_GNT
  | BTC_NXC
  | BTC_EDG
  | BTC_LGD
  | BTC_TRST
  | ETH_GNT
  | ETH_REP
  | USDT_ETH
  | ETH_WINGS
  | BTC_WINGS
  | BTC_RLC
  | BTC_GNO
  | BTC_GUP
  | BTC_LUN
  | ETH_GUP
  | ETH_RLC
  | ETH_LUN
  | ETH_GNO
  | BTC_APX
  | BTC_HMQ
  | ETH_HMQ
  | BTC_ANT
  | ETH_TRST
  | ETH_ANT
  | BTC_SC
  | ETH_BAT
  | BTC_BAT
  | BTC_ZEN
  | BTC_1ST
  | BTC_QRL
  | ETH_1ST
  | ETH_QRL
  | BTC_CRB
  | ETH_CRB
  | ETH_LGD
  | BTC_PTOY
  | ETH_PTOY
  | BTC_MYST
  | ETH_MYST
  | BTC_CFI
  | ETH_CFI
  | BTC_BNT
  | ETH_BNT
  | BTC_NMR
  | ETH_NMR
  | ETH_LTC
  | ETH_XRP
  | BTC_SNT
  | ETH_SNT
  | BTC_DCT
  | BTC_XEL
  | BTC_MCO
  | ETH_MCO
  | BTC_ADT
  | ETH_ADT
  | BTC_FUN
  | ETH_FUN
  | BTC_PAY
  | ETH_PAY
  | BTC_MTL
  | ETH_MTL
  | BTC_STORJ
  | ETH_STORJ
  | BTC_ADX
  | ETH_ADX
  | ETH_DASH
  | ETH_SC
  | ETH_ZEC
  | USDT_ZEC
  | USDT_LTC
  | USDT_ETC
  | USDT_XRP
  | BTC_OMG
  | ETH_OMG
  | BTC_CVC
  | ETH_CVC
  | BTC_PART
  | BTC_QTUM
  | ETH_QTUM
  | ETH_XMR
  | ETH_XEM
  | ETH_XLM
  | ETH_NEO
  | USDT_XMR
  | USDT_DASH
  | ETH_BCC
  | USDT_BCC
  | BTC_BCC
  | BTC_DNT
  | ETH_DNT
  | USDT_NEO
  | ETH_WAVES
  | ETH_STRAT
  | ETH_DGB
  | ETH_FCT
  | USDT_OMG
  | BTC_ADA
  | BTC_MANA
  | ETH_MANA
  | BTC_SALT
  | ETH_SALT
  | BTC_TIX
  | ETH_TIX
  | BTC_RCN
  | ETH_RCN
  | BTC_VIB
  | ETH_VIB
  | BTC_MER
  | BTC_POWR
  | ETH_POWR
  | BTC_BTG
  | ETH_BTG
  | USDT_BTG
  | ETH_ADA
  | BTC_ENG
  | ETH_ENG
  | USDT_ADA
  | USDT_XVG
  | USDT_NXT
  | BTC_UKG
  | ETH_UKG
  deriving (Show, Eq, Generic, Read)

newtype Bid = Bid (Fixed E8)
  deriving (Show, Eq, Num, FromJSON)

newtype Ask = Ask (Fixed E8)
  deriving (Show, Eq, Num, FromJSON)

newtype Last = Last (Fixed E8)
  deriving (Show, Eq, Num, FromJSON)

newtype High = High (Fixed E8)
  deriving (Show, Eq, Num, FromJSON)

newtype Low = Low (Fixed E8)
  deriving (Show, Eq, Num, FromJSON)

newtype Volume = Volume (Fixed E8)
  deriving (Show, Eq, Num, FromJSON)

newtype BaseVolume = BaseVolume (Fixed E8)
  deriving (Show, Eq, Num, FromJSON)

newtype PrevDay = PrevDay (Fixed E8)
  deriving (Show, Eq, Num, FromJSON)

newtype Quantity = Quantity (Fixed E8)
  deriving (Show, Eq, Num, FromJSON)

newtype Rate = Rate (Fixed E8)
  deriving (Show, Eq, Num, FromJSON)

newtype Price = Price (Fixed E8)
  deriving (Show, Eq, Num, FromJSON)

newtype Total = Total (Fixed E8)
  deriving (Show, Eq, Num, FromJSON)

newtype QuantityRemaining = QuantityRemaining (Fixed E8)
  deriving (Show, Eq, Num, FromJSON)

newtype Limit = Limit (Fixed E8)
  deriving (Show, Eq, Num, FromJSON)

newtype CommissionPaid = CommissionPaid (Fixed E8)
  deriving (Show, Eq, Num, FromJSON)

newtype Balance' = Balance' (Fixed E8)
  deriving (Show, Eq, Num, FromJSON)

newtype Available = Available (Fixed E8)
  deriving (Show, Eq, Num, FromJSON)

newtype Pending = Pending (Fixed E8)
  deriving (Show, Eq, Num, FromJSON)

newtype Reserved = Reserved (Fixed E8)
  deriving (Show, Eq, Num, FromJSON)

newtype ReserveRemaining = ReserveRemaining (Fixed E8)
  deriving (Show, Eq, Num, FromJSON)

newtype CommissionReserved = CommissionReserved (Fixed E8)
  deriving (Show, Eq, Num, FromJSON)

newtype CommissionReserveRemaining = CommissionReserveRemaining (Fixed E8)
  deriving (Show, Eq, Num, FromJSON)

newtype TxCost = TxCost (Fixed E8)
  deriving (Show, Eq, Num, FromJSON)

newtype Amount = Amount (Fixed E8)
  deriving (Show, Eq, Num, FromJSON)

newtype Commission = Commission (Fixed E8)
  deriving (Show, Eq, Num, FromJSON)

data Ticker
  = Ticker
  { bid :: Bid
  , ask :: Ask
  , last :: Last
  } deriving (Generic, Show)

instance FromJSON Ticker where
  parseJSON = withObject "Ticker" $ \o ->
    Ticker <$> o .: "Bid"
           <*> o .: "Ask"
           <*> o .: "Last"

newtype MinTradeSize = MinTradeSize (Fixed E8)
  deriving (Show, Eq, Num, FromJSON)

newtype TxFee = TxFee (Fixed E8)
  deriving (Show, Eq, Num, FromJSON)

data Market
  = Market
  { marketCurrency :: Text
  , baseCurrency :: Text
  , marketCurrencyLong :: Text
  , baseCurrencyLong :: Text
  , minTradeSize :: MinTradeSize
  , marketName :: MarketName
  , isActive :: Bool
  , created :: Time
  } deriving (Show, Eq)

instance FromJSON Market where
  parseJSON = withObject "Market" $ \o ->
    Market <$> o .: "MarketCurrency"
           <*> o .: "BaseCurrency"
           <*> o .: "MarketCurrencyLong"
           <*> o .: "BaseCurrencyLong"
           <*> o .: "MinTradeSize"
           <*> o .: "MarketName"
           <*> o .: "IsActive"
           <*> o .: "Created"

data Currency
  = Currency
  { currency :: Text
  , currencyLong :: Text
  , minConfirmation :: Int
  , txFee :: TxFee
  , currencyIsActive :: Bool
  , coinType :: Text
  , baseAddress :: Maybe Text
  } deriving (Show, Eq)

instance FromJSON Currency where
  parseJSON = withObject "Currency" $ \o ->
    Currency
      <$> o .: "Currency"
      <*> o .: "CurrencyLong"
      <*> o .: "MinConfirmation"
      <*> o .: "TxFee"
      <*> o .: "IsActive"
      <*> o .: "CoinType"
      <*> o .: "BaseAddress"

data OrderBookEntry
  = OrderBookEntry
  { quantity :: Quantity
  , rate :: Rate
  } deriving (Show, Eq)

instance FromJSON OrderBook where
  parseJSON = withObject "OrderBook" $ \o ->
    OrderBook <$> o .: "buy"
              <*> o .: "sell"

data OrderBook
  = OrderBook
  { buy :: [OrderBookEntry]
  , sell :: [OrderBookEntry]
  } deriving (Show, Eq)

instance FromJSON OrderBookEntry where
  parseJSON = withObject "OrderBookEntry" $ \o ->
    OrderBookEntry <$> o .: "Quantity"
                   <*> o .: "Rate"

data MarketHistory
  = MarketHistory
  { mhId :: Integer
  , mhTimeStamp :: Time
  , mhQuantity :: Quantity
  , mhPrice :: Price
  , mhTotal :: Total
  , mhFillType :: Text
  , mhOrderType :: Text
  } deriving (Show, Eq)

instance FromJSON MarketHistory where
  parseJSON = withObject "MarketHistory" $ \o ->
    MarketHistory <$> o .: "Id"
                  <*> o .: "TimeStamp"
                  <*> o .: "Quantity"
                  <*> o .: "Price"
                  <*> o .: "Total"
                  <*> o .: "FillType"
                  <*> o .: "OrderType"

-- | API Keys
data APIKeys = APIKeys
  { apiKey :: String
  , secretKey :: String
  } deriving (Show, Eq)

type Address = String
type PaymentId = String

data WithdrawalHistory
  = WithdrawalHistory
  { whPaymentUuid :: Text
  , whCurrency :: Text
  , whAmount :: Amount
  , whAddress :: Text
  , whOpened :: Text
  , whAuthorized :: Bool
  , whPendingPayment :: Bool
  , whTxCost :: Scientific
  , whTxId :: Text
  , whCanceled :: Bool
  , whInvalidAddress :: Bool
  } deriving (Show, Eq, Generic)

instance FromJSON WithdrawalHistory where
  parseJSON = genericParseJSON defaultOptions {
    fieldLabelModifier = drop 2
  }

data DepositHistory
  = DepositHistory
  { dhCurrency :: Text
  , dhAmount :: Scientific
  , dhLastUpdated :: Text
  , dhConfirmations :: Scientific
  , dhId :: Scientific
  , dhTxId :: Text
  , dhCryptoAddress :: Text
  } deriving (Show, Eq, Generic)

instance FromJSON DepositHistory where
  parseJSON = genericParseJSON defaultOptions {
    fieldLabelModifier = drop 2
  }

type CurrencyName = Text

data DepositAddress
  = DepositAddress
  { daCurrency :: Text
  , daAddress :: Text
  } deriving (Show, Eq, Generic)

instance FromJSON DepositAddress where
  parseJSON = genericParseJSON defaultOptions {
    fieldLabelModifier = drop 2
  }

newtype UUID = UUID Text
  deriving (Show, Eq)

instance FromJSON UUID where
  parseJSON = withObject "UUID" $ \o ->
    UUID <$> o .: "uuid"

data Balance
  = Balance
  { bCurrency :: Text
  , bBalance :: Balance'
  , bAvailable :: Available
  , bPending :: Pending
  , bCryptoAddress :: Text
  , bUuid :: Maybe Text
  } deriving (Show, Eq, Generic)

instance FromJSON Balance where
  parseJSON = genericParseJSON defaultOptions {
    fieldLabelModifier = drop 1
  }

data OrderType
  = SELL
  | BUY
  | LIMIT_SELL
  | LIMIT_BUY
  deriving (Show, Generic, Eq)

instance FromJSON OrderType

data OpenOrder
  = OpenOrder
  { ooUuid :: Maybe Text
  , ooOrderUuid :: Text
  , ooExchange :: Text
  , ooOrderType :: OrderType
  , ooQuantity :: Quantity
  , ooQuantityRemaining :: QuantityRemaining
  , ooLimit :: Limit
  , ooCommissionPaid :: CommissionPaid
  , ooPrice :: Price
  , ooPricePerUnit :: Maybe Price
  , ooOpened :: Time
  , ooClosed :: Maybe Time
  , ooCancelInitiated :: Bool
  , ooImmediateOrCancel :: Bool
  , ooIsConditional :: Bool
  , ooCondition :: Maybe Text
  , ooConditionTarget :: Maybe Text
  } deriving (Show, Eq, Generic)

instance FromJSON OpenOrder where
  parseJSON = genericParseJSON defaultOptions {
    fieldLabelModifier = drop 2
  }

data OrderHistory
  = OrderHistory
    { ohOrderUuid :: Text
    , ohExchange :: Text
    , ohTimeStamp :: Time
    , ohOrderType :: OrderType
    , ohLimit :: Limit
    , ohQuantity :: Quantity
    , ohQuantityRemaining :: QuantityRemaining
    , ohCommission :: Commission
    , ohPrice :: Price
    , ohPricePerUnit :: Maybe Price
    , ohIsConditional :: Bool
    , ohCondition :: Text
    , ohConditionTarget :: Maybe Text
    , ohImmediateOrCancel :: Bool
    } deriving (Show, Eq, Generic)

instance FromJSON OrderHistory where
  parseJSON = genericParseJSON defaultOptions {
    fieldLabelModifier = drop 2
  }

data Order
  = Order
    { oAccountId :: Maybe Text
    , oOrderUuid :: Text
    , oExchange :: Text
    , oOrderType :: OrderType
    , oQuantity :: Quantity
    , oQuantityRemaining :: QuantityRemaining
    , oLimit :: Limit
    , oReserved :: Reserved
    , oReservedRemaining :: ReserveRemaining
    , oCommissionReserved :: CommissionReserved
    , oCommissionReserveRemaining :: CommissionReserveRemaining
    , oCommissionPaid :: CommissionPaid
    , oPrice :: Price
    , oPricePerUnit :: Maybe Price
    , oOpened :: Time
    , oClosed :: Maybe Time
    , oIsOpen :: Bool
    , oSentinel :: Text
    , oCommission :: Commission
    , oIsConditional :: Bool
    , oCancelInitiated :: Bool
    , oImmediateOrCancel :: Bool
    , oCondition :: Text
    , oConditionTarget :: Maybe Text
    } deriving (Show, Eq, Generic)

instance FromJSON Order where
  parseJSON = genericParseJSON defaultOptions {
    fieldLabelModifier = drop 1
  }

data MarketSummary
  = MarketSummary
  { msMarketName :: MarketName
  , msHigh :: High
  , msLow :: Low
  , msVolume :: Volume
  , msLast :: Last
  , msBaseVolume :: BaseVolume
  , msTimeStamp :: Time
  , msBid :: Bid
  , msAsk :: Ask
  , msOpenBuyOrders :: Int
  , msOpenSellOrders :: Int
  , msPrevDay :: PrevDay
  , msCreated :: Time
  , msDisplayMarketName :: Maybe Text
  } deriving (Show, Eq, Generic)

instance FromJSON MarketSummary where
  parseJSON = genericParseJSON defaultOptions {
    fieldLabelModifier = drop 2
  }