{-# LANGUAGE ConstraintKinds      #-}
{-# LANGUAGE DataKinds            #-}
{-# LANGUAGE OverloadedStrings    #-}
{-# LANGUAGE PolyKinds            #-}
{-# LANGUAGE TypeFamilies         #-}
{-# LANGUAGE TypeOperators        #-}
{-# LANGUAGE UndecidableInstances #-}

module Data.Swagger.Build.Util where

import Control.Monad.Trans.State.Strict
import Data.Swagger.Model.Api as Api
import Data.Swagger.Model.Authorisation (Scope)
import Data.Text (Text)
import Prelude

type Elem a b = IsElem a b ~ 'True

type family IsElem a b where
    IsElem a (a ': t) = 'True
    IsElem a (h ': t) = IsElem a t

-- | Common contains recurring fields to allow reuse of names.
-- The first type variable is used to constrain the valid fields, e.g.
--
-- @
-- type Foo = Common '["description", "models"] Bar
-- @
--
-- The various state monad updates check if their field is part of the
-- type-level list, cf. for example 'description'.
--
data Common f a = Common
    { descr :: Maybe Text
    , reqrd :: Maybe Bool
    , prod  :: Maybe [Text]
    , cons  :: Maybe [Text]
    , modls :: Maybe [Model]
    , auths :: Maybe [(Text, Maybe Scope)]
    , other :: a
    }

common :: a -> Common f a
common = Common Nothing (Just True) Nothing Nothing Nothing Nothing

description :: Elem "description" f => Text -> State (Common f a) ()
description d = modify $ \c -> c { descr = Just d }

optional :: Elem "required" f => State (Common f a) ()
optional = modify $ \c -> c { reqrd = Nothing }

produces :: Elem "produces" f => Text -> State (Common f a) ()
produces t = modify $ \c -> c { prod = maybe (Just [t]) (Just . (t:)) (prod c) }

consumes :: Elem "consumes" f => Text -> State (Common f a) ()
consumes t = modify $ \c -> c { cons = maybe (Just [t]) (Just . (t:)) (cons c) }

model :: Elem "models" f => Model -> State (Common f a) ()
model m = modify $ \c -> c { modls = maybe (Just [m]) (Just . (m:)) (modls c) }

data Auth = Basic | ApiKey | OAuth2 Scope | None

authorisation :: Elem "authorisations" f => Auth -> State (Common f a) ()
authorisation a = modify $ \c ->
    c { auths = maybe (Just (f a)) (Just . (f a ++)) (auths c) }
  where
    f Basic      = [("basic", Nothing)]
    f ApiKey     = [("apiKey", Nothing)]
    f (OAuth2 s) = [("oauth2", Just s)]
    f None       = []

-- | If cases where no build steps are provided but a builder is required
-- 'end' can be used, e.g. @defineModel \"Foo\" end@
end :: Monad m => m ()
end = return ()