{-# LANGUAGE NamedFieldPuns    #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TemplateHaskell   #-}

module Data.Morpheus.Execution.Internal.Decode
  ( withObject
  , withMaybe
  , withList
  , withEnum
  , withUnion
  , decodeFieldWith
  , decodeObjectExpQ
  ) where

import           Data.Semigroup                          ((<>))
import           Data.Text                               (unpack)
import           Language.Haskell.TH                     (ExpQ, conE, mkName, uInfixE, varE)

-- MORPHEUS
import           Data.Morpheus.Error.Internal            (internalArgumentError, internalTypeMismatch)
import           Data.Morpheus.Types.Internal.Data       (DataField (..), Key)
import           Data.Morpheus.Types.Internal.DataD      (ConsD (..))
import           Data.Morpheus.Types.Internal.Validation (Validation)
import           Data.Morpheus.Types.Internal.Value      (Object, Value (..))

decodeObjectExpQ :: ExpQ -> ConsD -> ExpQ
decodeObjectExpQ fieldDecoder ConsD {cName, cFields} = handleFields cFields
  where
    consName = conE (mkName cName)
    ----------------------------------------------------------------------------------
    handleFields fNames = uInfixE consName (varE '(<$>)) (applyFields fNames)
      where
        applyFields []     = fail "No Empty fields"
        applyFields [x]    = defField x
        applyFields (x:xs) = uInfixE (defField x) (varE '(<*>)) (applyFields xs)
        ------------------------------------------------------------------------
        defField DataField {fieldName} = uInfixE (varE (mkName "o")) fieldDecoder [|fName|]
          where
            fName = unpack fieldName

withObject :: (Object -> Validation a) -> Value -> Validation a
withObject f (Object object) = f object
withObject _ isType          = internalTypeMismatch "Object" isType

withMaybe :: Monad m => (Value -> m a) -> Value -> m (Maybe a)
withMaybe _ Null   = pure Nothing
withMaybe decode x = Just <$> decode x

withList :: (Value -> Validation a) -> Value -> Validation [a]
withList decode (List li) = mapM decode li
withList _ isType         = internalTypeMismatch "List" isType

withEnum :: (Key -> Validation a) -> Value -> Validation a
withEnum decode (Enum value) = decode value
withEnum _ isType            = internalTypeMismatch "Enum" isType

withUnion :: (Key -> Object -> Object -> Validation a) -> Object -> Validation a
withUnion decoder unions =
  case lookup "tag" unions of
    Just (Enum key) ->
      case lookup key unions of
        Nothing    -> internalArgumentError ("type \"" <> key <> "\" was not provided on object")
        Just value -> withObject (decoder key unions) value
    Just _ -> internalArgumentError "tag must be Enum"
    Nothing -> internalArgumentError "tag not found on Input Union"

decodeFieldWith :: (Value -> Validation a) -> Key -> Object -> Validation a
decodeFieldWith decoder name object =
  case lookup name object of
    Nothing    -> internalArgumentError ("Missing Field: \"" <> name <> "\"")
    Just value -> decoder value