{-# LANGUAGE NoPolyKinds #-}

module Yam.Web.Swagger(
    SwaggerConfig(..)
  , mkServeWithSwagger
  ) where

import           Control.Lens       hiding (Context)
import           Data.Aeson
import           Data.Aeson.Types   (typeMismatch)
import           Data.Default
import           Data.Maybe
import           Data.Reflection
import           Data.Swagger
import           Data.Text          (Text)
import           GHC.TypeLits
import           Network.Wai
import           Servant
import           Servant.Swagger
import           Servant.Swagger.UI

import           Yam.Web.Internal

data SwaggerUIType = Classic | Jensoleg deriving Show
data SwaggerConfig = SwaggerConfig
  { uiType        :: SwaggerUIType
  , uiPath        :: String
  , apiPath       :: String
  , enabled       :: Bool
  , apiTitle      :: Text
  , apiVersion    :: Text
  , contractName  :: Maybe Text
  , contractEmail :: Maybe Text
  } deriving Show

instance FromJSON SwaggerUIType where
  parseJSON v = go <$> parseJSON v
    where go :: Text -> SwaggerUIType
          go "default" = Classic
          go _         = Jensoleg

instance FromJSON SwaggerConfig where
  parseJSON (Object v) = SwaggerConfig
    <$> v .:? "type"    .!= Classic
    <*> v .:? "path"    .!= "swagger-ui.html"
    <*> v .:? "schema"  .!= "swagger.json"
    <*> v .:? "enabled" .!= True
    <*> v .:? "title"   .!= ""
    <*> v .:? "version" .!= "1.0.0"
    <*> v .:? "name"
    <*> v .:? "email"
  parseJSON invalid    = typeMismatch "SwaggerConfig" invalid

instance Default SwaggerConfig where
  def = fromJust $ decode "{}"

swagger :: (HasSwagger api) => SwaggerConfig -> Proxy (SwaggerSchemaUI dir schema :<|> api) -> Proxy api -> Server api -> Server (SwaggerSchemaUI dir schema :<|> api)
swagger conf _ proxy api = go (uiType conf) (f conf $ toSwagger proxy) :<|> api
    where go Jensoleg = jensolegSwaggerSchemaUIServer
          go _        = swaggerSchemaUIServer
          f SwaggerConfig{..} s = s
                & info.title       .~ apiTitle
                & info.version     .~ apiVersion
                & info.contact     ?~ Contact contractName Nothing contractEmail

mkServeWithSwagger
  :: (HasSwagger api, HasServer api context)
  => Proxy api
  -> Proxy context
  -> Context context
  -> Proxy c
  -> c
  -> [Middleware]
  -> SwaggerConfig
  -> ServerT api (App c)
  -> Application
mkServeWithSwagger proxy pcxt cxt pc c middlewares conf server =
  if enabled conf
    then reifyGroup conf $ \p -> mkServe' (swagger conf p proxy) p proxy pcxt cxt pc c middlewares server
    else mkServe proxy pcxt cxt pc c middlewares server

reifyGroup :: SwaggerConfig -> (forall d s. (KnownSymbol d ,KnownSymbol s)=> Proxy (SwaggerSchemaUI d s :<|> api) -> r) -> r
reifyGroup SwaggerConfig{..} f = reifySymbol uiPath $ \pd -> reifySymbol apiPath $ \ps -> f $ group pd ps

group :: Proxy dir -> Proxy schema -> Proxy (SwaggerSchemaUI dir schema :<|> api)
group _ _ = Proxy