module Language.Bond.Codegen.Haskell.SchemaDecl (
    getSchema,
    structNameAndType
  ) where

import Language.Bond.Codegen.Haskell.Util

import Language.Bond.Codegen.TypeMapping
import Language.Bond.Syntax.Types

import Data.Maybe
import Language.Haskell.Exts
import Language.Haskell.Exts.SrcLoc (noLoc)

makeFieldType :: String -> MappingContext -> Field -> Exp
makeFieldType settype ctx field
    | BT_Bool <- fieldType field = reuseDefault "FieldBool"
    | BT_Int8 <- fieldType field = reuseDefault "FieldInt8"
    | BT_Int16 <- fieldType field = reuseDefault "FieldInt16"
    | BT_Int32 <- fieldType field = reuseDefault "FieldInt32"
    | BT_Int64 <- fieldType field = reuseDefault "FieldInt64"
    | BT_UInt8 <- fieldType field = reuseDefault "FieldUInt8"
    | BT_UInt16 <- fieldType field = reuseDefault "FieldUInt16"
    | BT_UInt32 <- fieldType field = reuseDefault "FieldUInt32"
    | BT_UInt64 <- fieldType field = reuseDefault "FieldUInt64"
    | BT_Float <- fieldType field = reuseDefault "FieldFloat"
    | BT_Double <- fieldType field = reuseDefault "FieldDouble"
    | BT_String <- fieldType field = reuseDefault "FieldString"
    | BT_WString <- fieldType field = reuseDefault "FieldWString"
    | BT_UserDefined Enum{} _ <- fieldType field =
        App (Con $ implQual "FieldInt32") $ Paren $
            App (Con $ implQual "DefaultValue") $ Paren $
                App (Var $ pQual "fromIntegral") $ Paren $
                    App (Var $ pQual "fromEnum") $ Paren $
                        App (Var $ UnQual $ mkVar $ makeFieldName field) $ Paren $
                            InfixApp (Var $ implQual "defaultValue") (QVarOp $ implQual "asProxyTypeOf") (Var $ unqual "type'proxy")
    | BT_Maybe t <- fieldType field =
        App (Var $ implQual "elementToDefNothingFieldType") $ Paren $
            App (Var $ implQual "getElementType") $ Paren $
                proxyOf $ hsType settype ctx t
    | t <- fieldType field = 
        App (Var $ implQual "elementToFieldType") $ Paren $
            App (Var $ implQual "getElementType") $ Paren $
                proxyOf $ hsType settype ctx t
    where
    reuseDefault con = App (Con $ implQual con) $ Paren $
        App (Con $ implQual "DefaultValue") $ Paren $
            App (Var $ UnQual $ mkVar $ makeFieldName field) $ Paren $
                InfixApp (Var $ implQual "defaultValue") (QVarOp $ implQual "asProxyTypeOf") (Var $ unqual "type'proxy")

getSchema :: CodegenOpts -> MappingContext -> Declaration -> InstDecl
getSchema opts ctx decl = InsDecl $ simpleFun noLoc (Ident "getSchema") (Ident "type'proxy") $
                RecConstr (implQual "StructSchema")
                    [ FieldUpdate (implQual "structTag") $
                        App (Var $ implQual "typeRep") (Var $ unqual "type'proxy")
                    , FieldUpdate (implQual "structName") $
                        App (Var $ implQual "getName") (Var $ unqual "type'proxy")
                    , FieldUpdate (implQual "structQualifiedName") $
                        App (Var $ implQual "getQualifiedName") (Var $ unqual "type'proxy")
                    , FieldUpdate (implQual "structAttrs") $
                        App (Var $ implQual "makeMap") (List $ map makeAttr (declAttributes decl))
                    , FieldUpdate (implQual "structBase") $
                        case structBase decl of
                            Nothing -> Con (pQual "Nothing")
                            Just base -> App (Con $ pQual "Just") $ Paren $
                                App (Var $ implQual "getSchema") (Paren $ proxyOf $ hsType (setType opts) ctx base)
                    , FieldUpdate (implQual "structFields") $
                        App (Var $ implQual "makeMap") (List $ map makeFieldInfo (structFields decl))
                    , FieldUpdate (implQual "structRequiredOrdinals") $
                        App (Var $ implQual "fromOrdinalList") (List requiredOrdinals)
                    ]
    where
    requiredOrdinals = mapMaybe (\ field ->
        if fieldModifier field == Required
            then Just $ App (Con $ implQual "Ordinal") (intL $ fieldOrdinal field)
            else Nothing) (structFields decl)
    makeFieldInfo field = Tuple Boxed
        [ App (Con $ implQual "Ordinal") (intL $ fieldOrdinal field)
        , RecConstr (implQual "FieldSchema")
            [ FieldUpdate (implQual "fieldName") $ strE $ fieldName field
            , FieldUpdate (implQual "fieldAttrs") $
                App (Var $ implQual "makeMap") (List $ map makeAttr (fieldAttributes field))
            , FieldUpdate (implQual "fieldModifier") $ Con $ implQual $
                case fieldModifier field of
                    Optional -> "FieldOptional"
                    Required -> "FieldRequired"
                    RequiredOptional -> "FieldRequiredOptional"
            , FieldUpdate (implQual "fieldType") $ makeFieldType (setType opts) ctx field
            ]
        ]
    makeAttr a = Tuple Boxed
        [ strE $ fromBuilder $ getQualifiedName ctx $ attrName a
        , strE $ attrValue a 
        ]


structNameAndType :: MappingContext -> Declaration -> [InstDecl]
structNameAndType ctx decl =
    [ InsDecl $ wildcardFunc "getName" $ nameFunc $ declName decl
    , InsDecl $ wildcardFunc "getQualifiedName" $ nameFunc $ fromBuilder $ getDeclTypeName ctx{namespaceMapping = []} decl
    , InsDecl $ simpleFun noLoc (Ident "getElementType") (Ident "type'proxy") $
            App (Con $ implQual "ElementStruct") (Paren $ App (Var $ implQual "getSchema") (Var $ unqual "type'proxy"))
    ]
    where
    paramProxies = map (Paren . proxyOf . TyVar . mkVar . paramName) (declParams decl)
    nameFunc ownName = 
        if null (declParams decl)
            then strE ownName
            else appFun (Var $ implQual "makeGenericName")
                    [ strE ownName
                    , List $ map (App (Var $ implQual "getQualifiedName")) paramProxies
                    ]