{-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE TypeOperators #-}
--------------------------------------------------------------------------------
module Main where
--------------------------------------------------------------------------------
import RIO hiding (Handler)
import RIO.Text
import RIO.Time
import Servant
import Data.Time.Clock.POSIX
import Prelude (read)
import Text.ParserCombinators.ReadP

import qualified Network.Wai.Handler.Warp as Warp
--------------------------------------------------------------------------------

type Api = "run"
           :> QueryParam' '[Required] "offset" Text
           :> Get '[JSON] UTCTime
      :<|> "hello"
           :> QueryParam "name" Text
           :> Get '[JSON] Text

server :: Server Api
server = compute :<|> hello
  where
    compute :: Text -> Handler UTCTime
    compute x = do
      case parseInput x of
        Nothing -> throwError err401
        Just req -> do
          res <- liftIO $ shiftTime req
          pure res
    hello :: Maybe Text -> Handler Text
    hello mName =
      case mName of
        Nothing -> pure "Hello, world!"
        Just name -> pure $ RIO.Text.concat ["Hello, ", name]

data ShiftTimeRequest = ShiftTimeRequest
  { shiftSeconds :: Int
  , shiftMinutes :: Int
  , shiftHours :: Int
  , shiftDays :: Int
  , shiftWeeks :: Int
  , shiftMonths :: Int
  , shiftQuarters :: Int
  , shiftYears :: Int
  } deriving (Eq, Show)

instance Semigroup ShiftTimeRequest where
  (ShiftTimeRequest as am ah ad aw amonths aq ay) <> (ShiftTimeRequest bs bm bh bd bw bmonths bq by) =
    ShiftTimeRequest
    { shiftSeconds = as + bs
    , shiftMinutes = am + bm
    , shiftHours = ah + bh
    , shiftDays = ad + bd
    , shiftWeeks = aw + bw
    , shiftMonths = amonths + bmonths
    , shiftQuarters = aq + bq
    , shiftYears = ay + by
    }

instance Monoid ShiftTimeRequest where
  mempty = defaultShiftTimeRequest

defaultShiftTimeRequest :: ShiftTimeRequest
defaultShiftTimeRequest = ShiftTimeRequest
  { shiftSeconds = 0
  , shiftMinutes = 0
  , shiftHours = 0
  , shiftDays = 0
  , shiftWeeks = 0
  , shiftMonths = 0
  , shiftQuarters = 0
  , shiftYears = 0
  }

-- This basically broken because it doesn't account for:
-- Exhales... time stuff
--   - Leap seconds, leap days, leap years...
--   - Months like February having 28 days and others having 31
--   - other things that I'm probably not considering
toSeconds :: ShiftTimeRequest -> NominalDiffTime
toSeconds ShiftTimeRequest{..} = do
  let minutes = 60
      hours = minutes * 60
      days = hours * 24
      weeks = days * 7
      months = weeks * 4
      quarters = months * 3
      years = days * 365
  fromIntegral $ shiftSeconds +
    shiftMinutes * minutes +
    shiftHours * hours +
    shiftDays * days +
    shiftWeeks * weeks +
    shiftMonths * months +
    shiftQuarters * quarters +
    shiftYears * years

shiftTime :: ShiftTimeRequest -> IO UTCTime
shiftTime req = do
  t <- getPOSIXTime
  let t' = t + toSeconds req
  pure $ posixSecondsToUTCTime t'

data Unit = Second
          | Minute
          | Hour
          | Day
          | Week
          | Month
          | Quarter
          | Year
  deriving (Eq, Show)

digit :: ReadP Char
digit =
  satisfy (\c -> c >= '0' && c <= '9')

unit :: ReadP Unit
unit = do
  c <- get
  case c of
    's' -> pure Second
    'm' -> pure Minute
    'h' -> pure Hour
    'd' -> pure Day
    'w' -> pure Week
    'M' -> pure Month
    'q' -> pure Quarter
    'y' -> pure Year
    _ -> fail $ "We don't support this unit: " ++ show c

request :: ReadP ShiftTimeRequest
request = do
  negative <- option Nothing $ fmap Just (satisfy (== '-'))
  n <- read <$> many1 digit
  u <- unit
  let amt = if isJust negative then -1 * n else n
  case u of
    Second  -> pure $ defaultShiftTimeRequest { shiftSeconds = amt }
    Minute  -> pure $ defaultShiftTimeRequest { shiftMinutes = amt }
    Hour    -> pure $ defaultShiftTimeRequest { shiftHours = amt }
    Day     -> pure $ defaultShiftTimeRequest { shiftDays = amt }
    Week    -> pure $ defaultShiftTimeRequest { shiftWeeks = amt }
    Month   -> pure $ defaultShiftTimeRequest { shiftMonths = amt }
    Quarter -> pure $ defaultShiftTimeRequest { shiftQuarters = amt }
    Year    -> pure $ defaultShiftTimeRequest { shiftYears = amt }

parseInput :: Text -> Maybe ShiftTimeRequest
parseInput x =
  case readP_to_S (manyTill request eof) (unpack x) of
    [(xs, "")] -> Just $ mconcat xs
    _ -> Nothing

main :: IO ()
main = Warp.run 8000 $ serve (Proxy @Api) server
