{-# LANGUAGE FlexibleContexts  #-}
{-# LANGUAGE OverloadedStrings #-}
module Snap.AtlassianConnect.Routes
  ( connectRoutes
  , homeHandler
  ) where

import           Control.Applicative
import qualified Control.Arrow                           as ARO
import           Control.Monad                           (mzero)
import           Control.Monad.State.Class               (get)
import qualified Data.Aeson                              as A
import qualified Data.ByteString.Char8                   as BC
import qualified Data.CaseInsensitive                    as CI
import           Data.Connect.Descriptor                 as D
import           Data.List
import           Data.List.Split                         (splitOn)
import qualified Data.Map.Lazy                           as ML
import           Data.Maybe                              (catMaybes, fromMaybe)
import qualified Network.HTTP.Media                      as NM
import qualified Snap.AtlassianConnect.Data              as CD
import qualified Snap.AtlassianConnect.LifecycleResponse as CL
import qualified Snap.Core                               as SC
import qualified Snap.Helpers                            as SH
import qualified Snap.Snaplet                            as SS

data MediaType = ApplicationJson | TextHtml deriving (Eq)

newtype OrdMediaType = OMT NM.MediaType deriving (Eq, Show)

instance Ord OrdMediaType where
   compare a b = compare (show a) (show b)

instance Show (MediaType) where
  show ApplicationJson = "application/json"
  show TextHtml = "text/html"

-- | In old versions of Atlassian Connect, if you queried the application root and your Accept headers wanted an
-- "application/json" response then they would return your Atlassian Connect descriptor and if the Accept headers
-- requested "text/html" then they returned your applications home page. This is a convenience method to enable that
-- behaviour to continue. If you use this method in your routes then you can make your home page behave in the same way.
-- This is not required for an Atlassian Connect application so feel free to just return home page content on your home
-- page and ignore this method.
homeHandler :: SS.Handler b CD.Connect () -> SS.Handler b CD.Connect ()
homeHandler sendHomePage = SC.method SC.GET handleGet <|> SH.respondWithError SH.badRequest "You can only GET the homepage."
  where
    handleGet = do
        potentialAcceptHeader <- SC.getHeader bsAccept <$> SC.getRequest
        case potentialAcceptHeader of
            Just acceptHeader -> fromMaybe unknownHeader (NM.mapAcceptMedia mediaTypeMap acceptHeader)
            Nothing -> mzero

    mediaTypeMap =
      [ (jsonMT, atlassianConnectHandler)
      , (textHtmlMT, sendHomePage)
      ]

    unknownHeader = SH.respondWithError SH.notFound "No response to a request with the provided Accept header."

    jsonMT = "application" NM.// "json"
    textHtmlMT = "text" NM.// "html"

bsAccept :: CI.CI BC.ByteString
bsAccept = CI.mk "Accept"

connectRoutes :: [(BC.ByteString, SS.Handler b CD.Connect ())]
connectRoutes = fmap (ARO.first BC.pack) simpleConnectRoutes

-- Handler b v a
-- b: lens from the base state to the current snaplets state (is the base state)
-- v: is the state of the current "view" snaplet (or simply, current state)
-- a: Monad return type
-- The MonadSnaplet type class distills the essence of the operations used with this pattern.
-- Its functions define fundamental methods for navigating snaplet trees.

simpleConnectRoutes :: [(String, SS.Handler b CD.Connect ())]
simpleConnectRoutes =
  [ ("/atlassian-connect.json" , atlassianConnectHandler)
  ]

atlassianConnectHandler :: SS.Handler b CD.Connect ()
atlassianConnectHandler = do
  connectData <- get
  let dc = genDescriptorExtrasFromConnect connectData
  SH.writeJson $ convertPluginDescriptor dc (CD.connectPlugin connectData)

genDescriptorExtrasFromConnect :: CD.Connect -> CD.DynamicDescriptorConfig
genDescriptorExtrasFromConnect connectData = CD.DynamicDescriptorConfig
    { CD.dcBaseUrl = CD.connectBaseUrl connectData
    }

convertPluginDescriptor :: CD.DynamicDescriptorConfig -> D.Plugin -> D.Plugin
convertPluginDescriptor dc plugin = plugin
    { D.pluginBaseUrl = CD.dcBaseUrl dc
    }