{-# LANGUAGE DataKinds             #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE OverloadedStrings     #-}
{-# LANGUAGE PolyKinds             #-}
{-# LANGUAGE StrictData            #-}
{-# LANGUAGE TypeFamilies          #-}
{-# LANGUAGE TypeOperators         #-}
module TokTok.Hello (newApp) where

import           Caching.ExpiringCacheMap.HashECM (CacheSettings (..), ECM,
                                                   consistentDuration,
                                                   lookupECM, newECMIO)
import           Control.Concurrent.MVar          (MVar)
import           Control.Monad.IO.Class           (liftIO)
import qualified Data.ByteString.Char8            as BS8
import qualified Data.ByteString.Lazy             as LBS
import           Data.HashMap.Strict              (HashMap)
import           Data.Text                        (Text)
import           Data.Text.Encoding               (encodeUtf8)
import qualified Data.Time.Clock.POSIX            as POSIX
import qualified GitHub
import           Network.HTTP.Media               ((//), (/:))
import           Servant
import           System.Environment               (getEnv)

import qualified GitHub.Tools.Changelogs          as Changelogs
import qualified GitHub.Tools.NetworkGraph        as NetworkGraph
import           GitHub.Tools.PullRequestInfo     (PullRequestInfo)
import qualified GitHub.Tools.PullStatus          as PullStatus


type GitHubCache a = ECM IO MVar () HashMap () a


data ApiContext = ApiContext
    { changelogInfo :: GitHubCache Changelogs.ChangeLog
    , roadmapInfo   :: GitHubCache Changelogs.ChangeLog
    , pullInfos     :: GitHubCache [[PullRequestInfo]]
    , networkGraph  :: GitHubCache String
    }


newGitHubCache :: Int -> IO a -> IO (GitHubCache a)
newGitHubCache timeout fetchUpdates = newECMIO
    (consistentDuration timeout $ \state () -> do
        infos <- fetchUpdates
        return (state, infos)
    )
    (round <$> POSIX.getPOSIXTime)
    1
    (CacheWithLRUList 1 1 1)


newContext :: IO ApiContext
newContext = do
    auth <- Just . GitHub.OAuth . BS8.pack <$> getEnv "GITHUB_TOKEN"

    ApiContext
        <$> newGitHubCache 300 (Changelogs.fetchChangeLog False "TokTok" "c-toxcore" auth)
        <*> newGitHubCache 300 (Changelogs.fetchChangeLog True  "TokTok" "c-toxcore" auth)
        <*> newGitHubCache  30 (PullStatus.getPullInfos "TokTok" "TokTok" auth)
        <*> newGitHubCache 900 (NetworkGraph.getNetworkGraph auth [("TokTok", "c-toxcore"), ("irungentoo", "toxcore")])


data HTML
instance Accept HTML where
    contentType _ = "text" // "html" /: ("charset", "utf-8")
instance MimeRender HTML Text where
    mimeRender _ = LBS.fromStrict . encodeUtf8

-- API specification
type TestApi =
       -- Link to the source code repository, to comply with AGPL.
       "source" :> Get '[PlainText] Text
  :<|> "changelog" :> Get '[PlainText] Text
  :<|> "roadmap" :> Get '[PlainText] Text
  :<|> "pulls.html" :> Get '[HTML] Text
  :<|> "pulls" :> Get '[JSON] [[PullRequestInfo]]
  :<|> "network" :> Get '[PlainText] String

testApi :: Proxy TestApi
testApi = Proxy

-- Server-side handlers.
--
-- There's one handler per endpoint, which, just like in the type
-- that represents the API, are glued together using :<|>.
--
-- Each handler runs in the 'Handler' monad.
server :: ApiContext -> Server TestApi
server ctx =
       sourceH
  :<|> changelogH
  :<|> roadmapH
  :<|> pullsHtmlH
  :<|> pullsH
  :<|> networkH
  where
    sourceH = return "https://github.com/TokTok/hs-github-tools"

    changelogH = liftIO $
        Changelogs.formatChangeLog False <$> lookupECM (changelogInfo ctx) ()
    roadmapH = liftIO $
        Changelogs.formatChangeLog True  <$> lookupECM (roadmapInfo   ctx) ()
    networkH = liftIO $
        lookupECM (networkGraph ctx) ()

    pullsHtmlH = liftIO $ lookupECM (pullInfos ctx) () >>= PullStatus.showPullInfos True
    pullsH = liftIO $ lookupECM (pullInfos ctx) ()

-- Turn the server into a WAI app. 'serve' is provided by servant,
-- more precisely by the Servant.Server module.
app :: ApiContext -> Application
app = serve testApi . server

newApp :: IO Application
newApp = app <$> newContext
