{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE OverloadedStrings #-}

-- | Defines the endpoints listed in the
-- <http://developer.oanda.com/rest-live/rates/ Rates> section of the API.

module OANDA.Rates
       ( InstrumentsArgs (..)
       , instrumentsArgs
       , Instrument (..)
       , instruments
       , Price (..)
       , prices
       , MidpointCandlestick (..)
       , midpointCandles
       , BidAskCandlestick (..)
       , bidaskCandles
       , CandlesArgs (..)
       , candlesArgs
       , CandlesCount (..)
       , DayOfWeek (..)
       , Granularity (..)
       ) where

import           Data.Aeson
import           Data.Char (toLower)
import           Data.Decimal
import           Data.Text (Text, pack)
import           Data.Time
import qualified Data.Vector as V
import           GHC.Generics (Generic)
import           Network.Wreq (Options)

import           OANDA.Util
import           OANDA.Types


data InstrumentsArgs = InstrumentsArgs
  { instrumentsFields      :: [Text]
  , instrumentsInstruments :: [Text]
  } deriving (Show)

instrumentsArgs :: InstrumentsArgs
instrumentsArgs = InstrumentsArgs ["displayName", "pip", "maxTradeUnits"] []

data Instrument = Instrument
  { instrumentInstrument      :: String
  , instrumentPip             :: Maybe Decimal
  , instrumentMaxTradeUnits   :: Maybe Integer
  , instrumentDisplayName     :: Maybe String
  , instrumentPrecision       :: Maybe Decimal
  , instrumentMaxTrailingStop :: Maybe Decimal
  , instrumentMinTrailingStop :: Maybe Decimal
  , instrumentMarginRate      :: Maybe Decimal
  , instrumentHalted          :: Maybe String
  , instrumentInterestRate    :: Maybe Decimal
  } deriving (Show, Generic)

instance FromJSON Instrument where
  parseJSON = genericParseJSON $ jsonOpts "instrument"

-- | Retrieve a list of instruments from OANDA
instruments :: OandaEnv -> AccountID -> InstrumentsArgs -> IO (V.Vector Instrument)
instruments od (AccountID aid) (InstrumentsArgs fs is) = do
  let url = baseURL od ++ "/v1/instruments"
      opts = constructOpts od [ ("accountId", [pack $ show aid])
                              , ("instruments", is)
                              , ("fields", fs)
                              ]
  jsonResponseArray url opts "instruments"


-- | Retrieve the current prices for a list of instruments.
prices :: OandaEnv -> [InstrumentText] -> Maybe ZonedTime -> IO (V.Vector Price)
prices od is zt =
  do let url = baseURL od ++ "/v1/prices"
         ztOpt = maybe [] (\zt' -> [("since", [pack $ formatTimeRFC3339 zt'])]) zt
         opts = constructOpts od $ ("instruments", is) : ztOpt

     jsonResponseArray url opts "prices"

data Price = Price
  { priceInstrument :: InstrumentText
  , priceTime       :: ZonedTime
  , priceBid        :: Decimal
  , priceAsk        :: Decimal
  } deriving (Show, Generic)

instance FromJSON Price where
  parseJSON = genericParseJSON $ jsonOpts "price"


-- | Retrieve the price history of a single instrument in midpoint candles
midpointCandles :: OandaEnv -> InstrumentText -> CandlesArgs ->
                   IO (V.Vector MidpointCandlestick)
midpointCandles od i args =
  do let (url, opts) = candleOpts od i args "midpoint"
     response <- jsonResponse url opts :: IO MidpointCandlesResponse
     return $ _midcandlesResponseCandles response

-- | Retrieve the price history of a single instrument in bid/ask candles
bidaskCandles :: OandaEnv -> InstrumentText -> CandlesArgs ->
                 IO (V.Vector BidAskCandlestick)
bidaskCandles od i args =
  do let (url, opts) = candleOpts od i args "bidask"
     response <- jsonResponse url opts :: IO BidAskCandlesResponse
     return $ _bidaskResponseCandles response


-- | Utility function for both candle history functions
candleOpts :: OandaEnv -> InstrumentText -> CandlesArgs -> String -> (String, Options)
candleOpts od i (CandlesArgs c g di atz wa) fmt = (url, opts)
  where url   = baseURL od ++ "/v1/candles"
        opts  = constructOpts od $ [ ("instrument", [i])
                                   , ("granularity", [pack $ show g])
                                   , ("candleFormat", [pack fmt])
                                   , ("dailyAlignment", [pack $ show di])
                                   , ("alignmentTimeZone", [pack atz])
                                   , ("weeklyAlignment", [pack $ show wa])
                                   ] ++ countOpts c
        countOpts (Count count) = [("count", [pack $ show count])]
        countOpts (StartEnd st ed incf) = [ ("start", [pack $ formatTimeRFC3339 st])
                                          , ("end", [pack $ formatTimeRFC3339 ed])
                                          , ("includeFirst", [pack $ map toLower (show incf)])
                                          ]

data MidpointCandlestick = MidpointCandlestick
  { midpointCandlestickTime     :: ZonedTime
  , midpointCandlestickOpenMid  :: Decimal
  , midpointCandlestickHighMid  :: Decimal
  , midpointCandlestickLowMid   :: Decimal
  , midpointCandlestickCloseMid :: Decimal
  , midpointCandlestickVolume   :: Int
  , midpointCandlestickComplete :: Bool
  } deriving (Show, Generic)

instance FromJSON MidpointCandlestick where
  parseJSON = genericParseJSON $ jsonOpts "midpointCandlestick"


data BidAskCandlestick = BidAskCandlestick
  { bidaskCandlestickTime     :: ZonedTime
  , bidaskCandlestickOpenBid  :: Decimal
  , bidaskCandlestickOpenAsk  :: Decimal
  , bidaskCandlestickHighBid  :: Decimal
  , bidaskCandlestickHighAsk  :: Decimal
  , bidaskCandlestickLowBid   :: Decimal
  , bidaskCandlestickLowAsk   :: Decimal
  , bidaskCandlestickCloseBid :: Decimal
  , bidaskCandlestickCloseAsk :: Decimal
  , bidaskCandlestickVolume   :: Int
  , bidaskCandlestickComplete :: Bool
  } deriving (Show, Generic)

instance FromJSON BidAskCandlestick where
  parseJSON = genericParseJSON $ jsonOpts "bidaskCandlestick"

data CandlesArgs = CandlesArgs
  { candlesCount           :: CandlesCount
  , candlesGranularity     :: Granularity
  , candlesDailyAlignment  :: Int
  , candlesAlignmentTZ     :: String
  , candlesWeeklyAlignment :: DayOfWeek
  } deriving (Show)

candlesArgs :: CandlesArgs
candlesArgs = CandlesArgs (Count 500) S5 17 "America/New_York" Friday

data CandlesCount = Count Int
                  | StartEnd { start :: ZonedTime
                             , end   :: ZonedTime
                             , includeFirst :: Bool
                             }
                  deriving (Show)

data DayOfWeek = Monday
               | Tuesday
               | Wednesday
               | Thursday
               | Friday
               | Saturday
               | Sunday
               deriving (Show)

data Granularity = S5 | S10 | S15 | S30
                 | M1 | M2 | M3 | M4 | M5 | M10 | M15 | M30
                 | H1 | H2 | H3 | H4 | H6 | H8 | H12
                 | D
                 | W
                 | M
                 deriving (Show)

-- | Utility type for `midpointCandles` function response. Not exported.
data MidpointCandlesResponse = MidpointCandlesResponse
  { _midcandlesResponseInstrument  :: InstrumentText
  , _midcandlesResponseGranularity :: String
  , _midcandlesResponseCandles     :: V.Vector MidpointCandlestick
  } deriving (Show, Generic)


instance FromJSON MidpointCandlesResponse where
  parseJSON = genericParseJSON $ jsonOpts "_midcandlesResponse"


-- | Utility type for `bidaskCandles` function response. Not exported.
data BidAskCandlesResponse = BidAskCandlesResponse
  { _bidaskResponseInstrument  :: InstrumentText
  , _bidaskResponseGranularity :: String
  , _bidaskResponseCandles     :: V.Vector BidAskCandlestick
  } deriving (Show, Generic)


instance FromJSON BidAskCandlesResponse where
  parseJSON = genericParseJSON $ jsonOpts "_bidaskResponse"