{-# LANGUAGE DefaultSignatures    #-}
{-# LANGUAGE FlexibleInstances    #-}
{-# LANGUAGE NamedFieldPuns       #-}
{-# LANGUAGE OverloadedStrings    #-}

module Data.Morpheus.Rendering.RenderGQL
  ( RenderGQL(..)
  , renderGraphQLDocument
  )
where

import           Data.ByteString.Lazy.Char8     ( ByteString )
import           Data.Semigroup                 ( (<>) )
import           Data.Text                      ( Text
                                                , intercalate
                                                )
import qualified Data.Text.Lazy                as LT
                                                ( fromStrict )
import           Data.Text.Lazy.Encoding        ( encodeUtf8 )

-- MORPHEUS
import           Data.Morpheus.Types.Internal.AST
                                                ( DataField(..)
                                                , DataTyCon(..)
                                                , DataType(..)
                                                , DataTypeLib
                                                , DataTypeWrapper(..)
                                                , Key
                                                , TypeAlias(..)
                                                , TypeWrapper(..)
                                                , allDataTypes
                                                , createInputUnionFields
                                                , fieldVisibility
                                                , isDefaultTypeName
                                                , toGQLWrapper
                                                , DataEnumValue(..)
                                                , convertToJSONName )


renderGraphQLDocument :: DataTypeLib -> ByteString
renderGraphQLDocument lib =
  encodeUtf8 $ LT.fromStrict $ intercalate "\n\n" $ map render visibleTypes
 where
  visibleTypes = filter (not . isDefaultTypeName . fst) (allDataTypes lib)

class RenderGQL a where
  render :: a -> Key
  renderWrapped :: a -> [TypeWrapper] -> Key
  default renderWrapped :: a -> [TypeWrapper] -> Key
  renderWrapped x wrappers = showGQLWrapper (toGQLWrapper wrappers)
    where
      showGQLWrapper []               = render x
      showGQLWrapper (ListType:xs)    = "[" <> showGQLWrapper xs <> "]"
      showGQLWrapper (NonNullType:xs) = showGQLWrapper xs <> "!"

instance RenderGQL Key where
  render = id

instance RenderGQL TypeAlias where
  render TypeAlias { aliasTyCon, aliasWrappers } =
    renderWrapped aliasTyCon aliasWrappers

instance RenderGQL DataType where
  render (DataScalar      x) = typeName x
  render (DataEnum        x) = typeName x
  render (DataUnion       x) = typeName x
  render (DataInputObject x) = typeName x
  render (DataInputUnion  x) = typeName x
  render (DataObject      x) = typeName x

instance RenderGQL DataEnumValue where
  render DataEnumValue { enumName } = enumName

instance RenderGQL (Key, DataType) where
  render (name, DataScalar{}) = "scalar " <> name
  render (name, DataEnum DataTyCon { typeData }) =
    "enum " <> name <> renderObject render typeData
  render (name, DataUnion DataTyCon { typeData }) =
    "union "
      <> name
      <> " =\n    "
      <> intercalate ("\n" <> renderIndent <> "| ") typeData
  render (name, DataInputObject DataTyCon { typeData }) =
    "input " <> name <> render typeData
  render (name, DataInputUnion DataTyCon { typeData }) =
    "input " <> name <> render fields
    where fields = createInputUnionFields name typeData
  render (name, DataObject DataTyCon { typeData }) =
    "type " <> name <> render typeData



-- OBJECT
instance RenderGQL [(Text, DataField)] where
  render = renderObject renderField . ignoreHidden
   where
    renderField :: (Text, DataField) -> Text
    renderField (key, DataField { fieldType, fieldArgs }) =
      convertToJSONName key <> renderArgs fieldArgs <> ": " <> render fieldType
     where
      renderArgs []   = ""
      renderArgs list = "(" <> intercalate ", " (map renderField list) <> ")"
    -----------------------------------------------------------
    ignoreHidden :: [(Text, DataField)] -> [(Text, DataField)]
    ignoreHidden = filter fieldVisibility

renderIndent :: Text
renderIndent = "  "

renderObject :: (a -> Text) -> [a] -> Text
renderObject f list =
  " { \n  " <> intercalate ("\n" <> renderIndent) (map f list) <> "\n}"