-- | Defines the endpoints listed in the
-- <http://developer.oanda.com/rest-live-v20/instrument-ep/ Instrument> section of the
-- API.

module OANDA.Instrument
  ( CandlestickGranularity (..)
  , granularityFromDiffTime
  , granularityToDiffTime
  , WeeklyAlignment (..)
  , PriceValue (..)
  , Candlestick (..)
  , CandlestickData (..)
  , CandlestickArgs (..)
  , candlestickArgsInstrument
  , candlestickArgsPrice
  , candlestickArgsGranularity
  , candlestickArgsCount
  , candlestickArgsFrom
  , candlestickArgsTo
  , candlestickArgsSmooth
  , candlestickArgsIncludeFirst
  , candlestickArgsDailyAlignment
  , candlestickArgsAlignmentTimezone
  , candlestickArgsWeeklyAlignment
  , candlestickArgs
  , oandaCandles
  , CandlestickResponse (..)
  ) where

import OANDA.Internal

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

deriveJSON defaultOptions ''CandlestickGranularity

granularityFromDiffTime :: NominalDiffTime -> Maybe CandlestickGranularity
granularityFromDiffTime = go . toSeconds'
  where
    go 5 = Just S5
    go 10 = Just S10
    go 15 = Just S15
    go 30 = Just S30
    go 60 = Just M1
    go 120 = Just M2
    go 240 = Just M4
    go 300 = Just M5
    go 600 = Just M10
    go 900 = Just M15
    go 1800 = Just M30
    go 3600 = Just H1
    go 7200 = Just H2
    go 10800 = Just H3
    go 14400 = Just H4
    go 21600 = Just H6
    go 28800 = Just H8
    go 43200 = Just H12
    go 86400 = Just D
    go 604800 = Just W
    -- go _ = Just M  -- Not well-defined for a month
    go _ = Nothing

-- | Utility function to convert Granularity to NominalDiffTime. __NOTE__: The
-- conversion from month to NominalDiffTime is not correct in general; we just
-- assume 31 days in a month, which is obviously false for 5 months of the
-- year.
granularityToDiffTime :: CandlestickGranularity -> NominalDiffTime
granularityToDiffTime S5 = fromSeconds' 5
granularityToDiffTime S10 = fromSeconds' 10
granularityToDiffTime S15 = fromSeconds' 15
granularityToDiffTime S30 = fromSeconds' 30
granularityToDiffTime M1 = fromSeconds' $ 1 * 60
granularityToDiffTime M2 = fromSeconds' $ 2 * 60
granularityToDiffTime M4 = fromSeconds' $ 4 * 60
granularityToDiffTime M5 = fromSeconds' $ 5 * 60
granularityToDiffTime M10 = fromSeconds' $ 10 * 60
granularityToDiffTime M15 = fromSeconds' $ 15 * 60
granularityToDiffTime M30 = fromSeconds' $ 30 * 60
granularityToDiffTime H1 = fromSeconds' $ 1 * 60 * 60
granularityToDiffTime H2 = fromSeconds' $ 2 * 60 * 60
granularityToDiffTime H3 = fromSeconds' $ 3 * 60 * 60
granularityToDiffTime H4 = fromSeconds' $ 4 * 60 * 60
granularityToDiffTime H6 = fromSeconds' $ 6 * 60 * 60
granularityToDiffTime H8 = fromSeconds' $ 8 * 60 * 60
granularityToDiffTime H12 = fromSeconds' $ 12 * 60 * 60
granularityToDiffTime D = fromSeconds' $ 1 * 60 * 60 * 24
granularityToDiffTime W = fromSeconds' $ 7 * 60 * 60 * 24
granularityToDiffTime M = fromSeconds' $ 31 * 60 * 60 * 24

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

newtype PriceValue = PriceValue { unPriceValue :: Text }
  deriving (Show, ToJSON, FromJSON)

data CandlestickData
  = CandlestickData
  { candlestickDataO :: PriceValue
  , candlestickDataH :: PriceValue
  , candlestickDataL :: PriceValue
  , candlestickDataC :: PriceValue
  } deriving (Show)

deriveJSON (unPrefix "candlestickData") ''CandlestickData

data Candlestick
  = Candlestick
  { candlestickTime :: OandaZonedTime
  , candlestickBid :: Maybe CandlestickData
  , candlestickAsk :: Maybe CandlestickData
  , candlestickMid :: Maybe CandlestickData
  , candlestickVolume :: Integer
  , candlestickComplete :: Bool
  } deriving (Show)

deriveJSON (unPrefix "candlestick") ''Candlestick

data CandlestickArgs
  = CandlestickArgs
  { _candlestickArgsInstrument :: InstrumentName
  , _candlestickArgsPrice :: Maybe Text
  , _candlestickArgsGranularity :: CandlestickGranularity
  , _candlestickArgsCount :: Maybe Int
  , _candlestickArgsFrom :: Maybe ZonedTime
  , _candlestickArgsTo :: Maybe ZonedTime
  , _candlestickArgsSmooth :: Maybe Bool
  , _candlestickArgsIncludeFirst :: Maybe Bool
  , _candlestickArgsDailyAlignment :: Maybe Int
  , _candlestickArgsAlignmentTimezone :: Maybe String
  , _candlestickArgsWeeklyAlignment :: Maybe WeeklyAlignment
  }

candlestickArgs :: InstrumentName -> CandlestickGranularity -> CandlestickArgs
candlestickArgs instrument granularity =
  CandlestickArgs
  { _candlestickArgsInstrument = instrument
  , _candlestickArgsPrice = Nothing
  , _candlestickArgsGranularity = granularity
  , _candlestickArgsCount = Nothing
  , _candlestickArgsFrom = Nothing
  , _candlestickArgsTo = Nothing
  , _candlestickArgsSmooth = Nothing
  , _candlestickArgsIncludeFirst = Nothing
  , _candlestickArgsDailyAlignment = Nothing
  , _candlestickArgsAlignmentTimezone = Nothing
  , _candlestickArgsWeeklyAlignment = Nothing
  }

makeLenses ''CandlestickArgs

oandaCandles :: OandaEnv -> CandlestickArgs -> OANDARequest CandlestickResponse
oandaCandles env CandlestickArgs{..} = OANDARequest request
  where
    instrumentText = unpack $ unInstrumentName _candlestickArgsInstrument
    request =
      baseApiRequest env "GET" ("/v3/instruments/" ++ instrumentText ++ "/candles")
      & setRequestQueryString params
    params =
      catMaybes
      [ ("price",) . Just . encodeUtf8 <$> _candlestickArgsPrice
      , Just ("granularity", Just . fromString $ show _candlestickArgsGranularity)
      , ("count",) . Just . fromString . show <$> _candlestickArgsCount
      , ("from",) . Just . fromString . formatTimeRFC3339 <$> _candlestickArgsFrom
      , ("to",) . Just . fromString . formatTimeRFC3339 <$> _candlestickArgsTo
      , ("smooth",) . Just . fromString . show <$> _candlestickArgsSmooth
      , ("includeFirst",) . Just . fromString . show <$> _candlestickArgsIncludeFirst
      , ("dailyAlignment",) . Just . fromString . show <$> _candlestickArgsDailyAlignment
      , ("alignmentTimezone",) . Just . fromString . show <$> _candlestickArgsAlignmentTimezone
      , ("weeklyAlignment",) . Just . fromString . show <$> _candlestickArgsWeeklyAlignment
      ]

data CandlestickResponse
  = CandlestickResponse
  { candlestickResponseInstrument :: InstrumentName
  , candlestickResponseGranularity :: CandlestickGranularity
  , candlestickResponseCandles :: [Candlestick]
  } deriving (Show)

deriveJSON (unPrefix "candlestickResponse") ''CandlestickResponse