{-# LANGUAGE AllowAmbiguousTypes #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE UndecidableInstances #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeApplications #-}

module Linnet.ToResponse
  ( ToResponse(..)
  ) where

import qualified Data.ByteString.Char8 as C8
import qualified Data.ByteString.Lazy as BL
import Data.Proxy (Proxy(..))
import GHC.Base (Symbol)
import GHC.TypeLits (KnownSymbol, symbolVal)
import Linnet.Encode (Encode(..))
import Linnet.Internal.Coproduct (CNil, Coproduct(..))
import Network.HTTP.Types (status200, status404)
import Network.Wai (Response, responseLBS)

-- | Type-class to convert a value of type @a@ into Response with Content-Type of @ct@
class ToResponse (ct :: Symbol) a where
  toResponse :: a -> Response

instance {-# OVERLAPPABLE #-} (ToResponse' (ValueT a) ct a) => ToResponse ct a where
  toResponse = toResponse' @(ValueT a) @ct

class ToResponse' (value :: Value) (ct :: Symbol) a where
  toResponse' :: a -> Response

instance (Encode ct a, KnownSymbol ct) => ToResponse' 'Value ct a where
  toResponse' a = mkResponse @ct $ encode @ct a

instance ToResponse' 'ResponseValue ct Response where
  toResponse' = id

instance (KnownSymbol ct) => ToResponse' 'UnitValue ct () where
  toResponse' _ = mkResponse @ct mempty

instance ToResponse' 'CNilValue ct CNil where
  toResponse' _ = responseLBS status404 [] mempty

instance (ToResponse ct a, ToResponse ct b) => ToResponse' 'CoproductValue ct (Coproduct a b) where
  toResponse' (Inl a) = toResponse @ct a
  toResponse' (Inr b) = toResponse @ct b

mkResponse ::
     forall ct. (KnownSymbol ct)
  => BL.ByteString
  -> Response
mkResponse = responseLBS status200 [("Content-Type", C8.pack $ symbolVal (Proxy :: Proxy ct))]

data Value
  = Value
  | ResponseValue
  | CoproductValue
  | CNilValue
  | UnitValue

type family ValueT (a :: *) :: Value where
  ValueT (Coproduct _ _) = 'CoproductValue
  ValueT CNil = 'CNilValue
  ValueT Response = 'ResponseValue
  ValueT () = 'UnitValue
  ValueT _ = 'Value