module Marshal (
  KnownSymbols,
  CSymbol(..),
  EnumKind(..),
  ParameterKind(..),
  genMarshalParameter,
  genMarshalOutParameter,
  genMarshalResult,
  genMarshalProperty,
  convertSignalType,
  genCall
  ) where

import MarshalFixup

import Utils
import Data.Char (isUpper)
import Data.Maybe (fromJust)
import qualified Data.Set as Set
import Data.Set (Set)
import qualified Data.Map as Map
import Data.Map (Map)

type KnownSymbols = Map String CSymbol

data CSymbol = SymObjectType { sym_object_parents :: [String] }
             | SymEnumType   EnumKind
             | SymEnumValue
             | SymStructType
             | SymBoxedType
             | SymClassType
             | SymTypeAlias
             | SymCallbackType
  deriving (Eq, Show)

data EnumKind = EnumKind | FlagsKind
  deriving (Eq, Show)

symbolIsObject (Just (SymObjectType _)) = True
symbolIsObject _                        = False

symbolIsEnum (Just (SymEnumType EnumKind)) = True
symbolIsEnum _                             = False

symbolIsFlags (Just (SymEnumType FlagsKind)) = True
symbolIsFlags _                              = False

symbolIsBoxed (Just SymBoxedType) = True
symbolIsBoxed _                   = False

-------------------------------------------------------------------------------
-- More doc formatting utils
-------------------------------------------------------------------------------

c2hsHook name d = text "{#" <+> text name <+> d <+> text "#}"
lambda var = char '\\' <> var <+> text "->"
ptr var = text var <> text "Ptr"

-------------------------------------------------------------------------------
-- Here's the interesting bit that generates the fragments of mashaling code
-------------------------------------------------------------------------------

data ParameterKind = InParam  String
                   | OutParam String
                   | UnusedParam

genMarshalParameter ::
        KnownSymbols -> --a collection of types we know to be objects or enums
        String ->       --function name (useful to lookup per-func fixup info)
        String ->       --parameter name suggestion (will be unique)
        String ->       --C type declaration for the parameter we will marshal
        (Maybe String,  --parameter class constraints (or none)
        ParameterKind,  --parameter type (or UnusedParam if the arg is not exposed)
        Doc -> Doc)     --marshaling code (\body -> ... body ...)

genMarshalParameter _ _ name "gboolean" =
        (Nothing, InParam "Bool",
        \body -> body
              $$ nest 2 (parens (text "fromBool" <+> text name)))

genMarshalParameter _ _ name typeName
                           | typeName == "guint"  --these two are unsigned types
                          || typeName == "gint"
                          || typeName == "glong"
                          || typeName == "int"
                          || typeName == "gsize"  --should they be Word or Int?
                          || typeName == "gssize" =
        (Nothing, InParam "Int",
        \body -> body
              $$ nest 2 (parens (text "fromIntegral" <+> text name)))

genMarshalParameter _ _ name "guint16" =
        (Nothing, InParam "Word16",
        \body -> body
              $$ nest 2 (parens (text "fromIntegral" <+> text name)))

genMarshalParameter _ _ name "guint32" =
        (Nothing, InParam "Word32",
        \body -> body
              $$ nest 2 (parens (text "fromIntegral" <+> text name)))

genMarshalParameter _ _ name typeName
                           | typeName == "gdouble"
                          || typeName == "double" =
        (Nothing, InParam "Double",
        \body -> body
              $$ nest 2 (parens (text "realToFrac" <+> text name)))

genMarshalParameter _ _ name "gfloat" =
        (Nothing, InParam "Float",
        \body -> body
              $$ nest 2 (parens (text "realToFrac" <+> text name)))

genMarshalParameter _ _ name "gunichar" =
        (Nothing, InParam "Char",
        \body -> body
              $$ nest 2 (parens (text "(fromIntegral . ord)" <+> text name)))

genMarshalParameter _ funcName name typeName | typeName == "const-gchar*"
                                            || typeName == "const-char*" =
  if maybeNullParameter funcName name
    then (Nothing, InParam "Maybe String",
         \body -> text "maybeWith withUTFString" <+> text name <+> char '$' <+> lambda (ptr name)
               $$ body
               $$ nest 2 (text name <> text "Ptr"))
    else (Nothing, InParam "String",
         \body -> text "withUTFString" <+> text name <+> char '$' <+> lambda (ptr name)
               $$ body
               $$ nest 2 (ptr name))

genMarshalParameter _ funcName name "const-gchar**" =
  (Nothing, InParam "[String]",
   \body -> text "withUTFStringArray0" <+> text name <+> char '$' <+> lambda (ptr name)
         $$ body
         $$ nest 2 (ptr name))

genMarshalParameter _ _ name "GError**" =
        (Nothing, UnusedParam,
        \body -> text "propagateGError $" <+> lambda (text name <> text "Ptr")
              $$ body
              $$ nest 2 (ptr name))

-- Objects -----------------------------
genMarshalParameter knownSymbols funcName name typeName'
            | isUpper (head typeName')
           && last typeName' == '*'
           && last typeName /= '*'
           && symbolIsObject typeKind =
  let classContext
        | leafClass typeName = Nothing
        | otherwise          = Just $ shortTypeName ++ "Class " ++ name
      argType = (if maybeNullParameter funcName name then "Maybe " else "")
             ++ (if leafClass typeName then shortTypeName else name)
      implementation
        | leafClass typeName && maybeNullParameter funcName name
                             = parens (text "fromMaybe"
                                   <+> parens (text shortTypeName <+> text "nullForeignPtr")
                                   <+> text name)
        | leafClass typeName = text name
        | maybeNullParameter funcName name
                             = parens (text "maybe"
                                   <+> parens (text shortTypeName <+> text "nullForeignPtr")
                                   <+> text "to" <> text shortTypeName <+> text name)
        | otherwise          = parens (text "to" <> text shortTypeName <+> text name)
   in (classContext, InParam argType,
      \body -> body
            $$ nest 2 implementation)
  where typeName = init typeName'
        shortTypeName = cTypeNameToHSType typeName
        typeKind = Map.lookup typeName knownSymbols

-- Enums -------------------------------
genMarshalParameter knownSymbols _ name typeName
            | isUpper (head typeName)
           && symbolIsEnum typeKind =
        (Nothing, InParam shortTypeName,
        \body -> body
              $$ nest 2 (parens (text "(fromIntegral . fromEnum)" <+> text name)))
  where shortTypeName = cTypeNameToHSType typeName
        typeKind = Map.lookup typeName knownSymbols

-- Flags -------------------------------
genMarshalParameter knownSymbols _ name typeName
            | isUpper (head typeName)
           && symbolIsFlags typeKind =
        (Nothing, InParam ("[" ++ shortTypeName ++ "]"),
        \body -> body
              $$ nest 2 (parens (text "(fromIntegral . fromFlags)" <+> text name)))
  where shortTypeName = cTypeNameToHSType typeName
        typeKind = Map.lookup typeName knownSymbols

genMarshalParameter _ _ name textIter | textIter == "const-GtkTextIter*"
                                     || textIter == "GtkTextIter*" =
        (Nothing, InParam "TextIter",
        \body -> body
              $$ nest 2 (text name))

genMarshalParameter _ funcName name "GtkTreeIter*" =
  if maybeNullParameter funcName name
    then (Nothing, InParam "Maybe TreeIter",
         \body -> body
               $$ nest 2 (parens (text "fromMaybe (TreeIter nullForeignPtr)" <+> text name)))
    else (Nothing, InParam "TreeIter",
         \body -> body
               $$ nest 2 (text name))

genMarshalParameter _ funcName name "GtkTreePath*" =
  if maybeNullParameter funcName name
    then (Nothing, InParam "Maybe TreePath",
         \body -> text "maybeWith withTreePath" <+> text name <+> char '$' <+> lambda (text name)
               $$ body
               $$ nest 2 (text name))
    else (Nothing, InParam "TreePath",
         \body -> text "withTreePath" <+> text name <+> char '$' <+> lambda (text name)
               $$ body
               $$ nest 2 (text name))

genMarshalParameter _ _ name "const-GdkColor*" =
        (Nothing, InParam "Color",
        \body -> text "with" <+> text name <+> char '$' <+> lambda (ptr name)
              $$ body
              $$ nest 2 (ptr name))

-- Out parameters -------------------------------

genMarshalParameter _ _ name "gboolean*" =
        (Nothing, OutParam "Boolean",
        \body -> body
              $$ nest 2 (ptr name))

genMarshalParameter _ _ name typeName
                           | typeName == "gint*"
                          || typeName == "guint*"
                          || typeName == "glong*" =
        (Nothing, OutParam "Int",
        \body -> body
              $$ nest 2 (ptr name))

genMarshalParameter _ _ name "gfloat*" =
        (Nothing, OutParam "Float",
        \body -> body
              $$ nest 2 (ptr name))

genMarshalParameter _ _ name "gdouble*" =
        (Nothing, OutParam "Double",
        \body -> body
              $$ nest 2 (ptr name))

genMarshalParameter _ _ name "gchar**" =
        (Nothing, OutParam "String",
        \body -> body
              $$ nest 2 (ptr name))

genMarshalParameter _ _ name "GdkColor*" =
        (Nothing, OutParam "Color",
        \body -> body
              $$ nest 2 (ptr name))

-- Catch all case -------------------------------
genMarshalParameter _ _ name unknownType =
        (Nothing, InParam $ "{-" ++ unknownType ++ "-}",
        \body -> body
              $$ nest 2 (text "{-" <> text name <> text "-}"))


genMarshalOutParameter :: String -> String -> (Doc, Doc, Doc)
genMarshalOutParameter "Boolean" name = (text "alloca" <+> char '$' <+> lambda (ptr name)
                                        ,text "peek" <+> ptr name <+> text ">>=" <+> lambda (text name)
                                        ,text "toBool" <+> text name)

genMarshalOutParameter "Int"     name = (text "alloca" <+> char '$' <+> lambda (ptr name)
                                        ,text "peek" <+> ptr name <+> text ">>=" <+> lambda (text name)
                                        ,text "fromIntegral" <+> text name)

genMarshalOutParameter "Float"   name = (text "alloca" <+> char '$' <+> lambda (ptr name)
                                        ,text "peek" <+> ptr name <+> text ">>=" <+> lambda (text name)
                                        ,text "realToFrac" <+> text name)

genMarshalOutParameter "Double"  name = (text "alloca" <+> char '$' <+> lambda (ptr name)
                                        ,text "peek" <+> ptr name <+> text ">>=" <+> lambda (text name)
                                        ,text "realToFrac" <+> text name)
genMarshalOutParameter "String"  name = (text "alloca" <+> char '$' <+> lambda (ptr name)
                                        ,text "peek" <+> ptr name <+> text ">>= readUTFString >>=" <+> lambda (text name)
                                        ,text name)
genMarshalOutParameter "Color"   name = (text "alloca" <+> char '$' <+> lambda (ptr name)
                                        ,text "peek" <+> ptr name <+> text ">>=" <+> lambda (text name)
                                        ,text name)

genMarshalOutParameter _         name = (empty, empty, text name)

-- Takes the type string and returns the Haskell Type and the marshaling code
--
genMarshalResult ::
        KnownSymbols -> --a collection of types we know to be objects or enums
        String ->       --function name (useful to lookup per-func fixup info)
        Bool ->         --is the function a constructor or ordinary method?
        String ->       --C type declaration for the return value we will marshal
        (String,        --Haskell return type
        Doc -> Doc)     --marshaling code (\body -> ... body ...)
genMarshalResult _ _ _ "gboolean" = ("Bool", \body -> text "liftM toBool $" $$ body)
genMarshalResult _ _ _ "gint"     = ("Int",  \body -> text "liftM fromIntegral $"   $$ body)
genMarshalResult _ _ _ "guint"    = ("Int",  \body -> text "liftM fromIntegral $"   $$ body)
genMarshalResult _ _ _ "guint16"  = ("Word16", \body -> text "liftM fromIntegral $" $$ body)
genMarshalResult _ _ _ "guint32"  = ("Word32", \body -> text "liftM fromIntegral $" $$ body)
genMarshalResult _ _ _ "glong"    = ("Int",    \body -> text "liftM fromIntegral $" $$ body)
genMarshalResult _ _ _ "gdouble"  = ("Double", \body -> text "liftM realToFrac $"   $$ body)
genMarshalResult _ _ _ "gfloat"   = ("Float",  \body -> text "liftM realToFrac $"   $$ body)
genMarshalResult _ _ _ "gunichar" = ("Char", \body -> text "liftM (chr . fromIntegral) $" $$ body)
genMarshalResult _ _ _ "void"     = ("()", id)
genMarshalResult _ funcName _ "const-gchar*" =
  if maybeNullResult funcName
    then ("(Maybe String)",
         \body -> body
               $$ text ">>= maybePeek peekUTFString")
    else ("String",
         \body -> body
               $$ text ">>= peekUTFString")
genMarshalResult _ funcName _ "const-gchar**" =
  ("[String]",\body -> body
                    $$ text ">>= peekUTFStringArray0")
genMarshalResult _ funcName _ typeName
                            | typeName == "gchar*"
                           || typeName == "char*" =
  if maybeNullResult funcName
    then ("(Maybe String)",
         \body -> body
               $$ text ">>= maybePeek readUTFString")
    else ("String",
         \body -> body
               $$ text ">>= readUTFString")
genMarshalResult _ _ _ "const-GSList*" =
  ("[{- element type -}]",
  \body -> body
        $$ text ">>= readGSList"
        $$ text ">>= mapM (\\elemPtr -> {-marshal elem-})")
genMarshalResult _ _ _ "GSList*" =
  ("[{- element type -}]",
  \body -> body
        $$ text ">>= fromGSList"
        $$ text ">>= mapM (\\elemPtr -> {-marshal elem-})")
genMarshalResult _ _ _ "GList*" =
  ("[{- element type -}]",
  \body -> body
        $$ text ">>= fromGList"
        $$ text ">>= mapM (\\elemPtr -> {-marshal elem-})")

genMarshalResult _ _ _ "GtkTreePath*" =
  ("TreePath",
  \body -> body
        $$ text ">>= fromTreePath")

genMarshalResult knownSymbols funcName funcIsConstructor typeName'
            | isUpper (head typeName')
           && last typeName' == '*'
           && last typeName /= '*'
           && symbolIsObject typeKind =
  if maybeNullResult funcName
    then ("(Maybe " ++ shortTypeName ++ ")",
         \body -> text "maybeNull" <+> parens (text constructor <+> text "mk" <> text shortTypeName) <+> char '$'
               $$ cast
               $$ body)
    else (shortTypeName,
         \body -> text constructor <+> text "mk" <> text shortTypeName <+> char '$'
               $$ cast
               $$ body)
  where typeName = init typeName'
        shortTypeName = cTypeNameToHSType typeName
        typeKind = Map.lookup typeName knownSymbols
        constructor | "GtkObject" `elem` sym_object_parents (fromJust typeKind)
                                = "makeNewObject"
                    | "GObject" `elem` sym_object_parents (fromJust typeKind)
                                = if funcIsConstructor then "constructNewGObject"
                                                       else "makeNewGObject"
        cast | funcIsConstructor
            && constructorReturnType /= typeName =
            text "liftM (castPtr :: Ptr" <+> text (cTypeNameToHSType constructorReturnType)
                       <+> text "-> Ptr" <+> text (cTypeNameToHSType typeName) <> text ") $"
             | otherwise = empty
          where constructorReturnType | "GtkToolItem" `elem` sym_object_parents (fromJust typeKind)
                                                  = "GtkToolItem"
                                      | "GtkWidget" `elem` sym_object_parents (fromJust typeKind)
                                                  = "GtkWidget"
                                      | otherwise = typeName

genMarshalResult knownSymbols _ _ typeName
            | isUpper (head typeName)
           && symbolIsEnum typeKind =
  (shortTypeName,
  \body -> text "liftM (toEnum . fromIntegral) $"
        $$ body)
  where shortTypeName = cTypeNameToHSType typeName
        typeKind = Map.lookup typeName knownSymbols

genMarshalResult knownSymbols _ _ typeName
            | isUpper (head typeName)
           && symbolIsFlags typeKind =
  ("[" ++ shortTypeName ++ "]",
  \body -> text "liftM (toFlags . fromIntegral) $"
        $$ body)
  where shortTypeName = cTypeNameToHSType typeName
        typeKind = Map.lookup typeName knownSymbols

genMarshalResult _ _ _ unknownType = ("{-" ++ unknownType ++ "-}", id)

-- Takes the type string and returns the Haskell Type and the GValue variety
--
genMarshalProperty :: KnownSymbols -> String -> (String, String, Bool)
genMarshalProperty _ "gint"      = ("Int",    "Int",       False)
genMarshalProperty _ "guint"     = ("Int",    "UInt",      False)
genMarshalProperty _ "gfloat"    = ("Float",  "Float",     False)
genMarshalProperty _ "gdouble"   = ("Double", "Double",    False)
genMarshalProperty _ "gboolean"  = ("Bool",   "Bool",      False)
genMarshalProperty _ "gunichar"  = ("Char",   "Char",      False)
genMarshalProperty _ "gchar*"    = ("String", "String",    False)
genMarshalProperty _ "GStrv"     = ("[String]", "Strings", False)

genMarshalProperty knownSymbols typeName
            | isUpper (head typeName)
           && symbolIsObject typeKind =
  (shortTypeName, "Object", True)
  where shortTypeName = cTypeNameToHSType typeName
        typeKind = Map.lookup typeName knownSymbols

genMarshalProperty knownSymbols typeName
            | isUpper (head typeName)
           && symbolIsEnum typeKind =
  (shortTypeName, "Enum", True)
  where shortTypeName = cTypeNameToHSType typeName
        typeKind = Map.lookup typeName knownSymbols

genMarshalProperty knownSymbols typeName
            | isUpper (head typeName)
           && symbolIsFlags typeKind =
  ("[" ++ shortTypeName ++ "]", "Flags", True)
  where shortTypeName = cTypeNameToHSType typeName
        typeKind = Map.lookup typeName knownSymbols

genMarshalProperty _ unknown = ("{-" ++ unknown ++ "-}",
                                "{-" ++ unknown ++ "-}", False)

-- Takes the type string and returns the signal marshaing category and the
-- Haskell type
--
convertSignalType :: KnownSymbols -> String -> (String, String)
convertSignalType _ "void"     = ("NONE",   "()")
convertSignalType _ "gchar"    = ("CHAR",   "Char")
convertSignalType _ "guchar"   = ("UCHAR",  "Char")
convertSignalType _ "gboolean" = ("BOOL",   "Bool")
convertSignalType _ "gint"     = ("INT",    "Int")
convertSignalType _ "guint"    = ("UINT",   "Int")
convertSignalType _ "guint32"  = ("UINT",   "Int")
convertSignalType _ "glong"    = ("LONG",   "Int")
convertSignalType _ "gulong"   = ("ULONG",  "Int")
convertSignalType _ "gfloat"   = ("FLOAT",  "Float")
convertSignalType _ "gdouble"  = ("DOUBLE", "Double")
convertSignalType _ "gchar*"   = ("STRING", "String")
convertSignalType _ "const-gchar*" = ("STRING", "String")
convertSignalType knownSymbols typeName
  | symbolIsEnum   typeKind    = ("ENUM",  cTypeNameToHSType typeName)
  | symbolIsFlags  typeKind    = ("FLAGS", cTypeNameToHSType typeName)
  where typeKind = Map.lookup typeName knownSymbols
convertSignalType knownSymbols typeName@(_:_)
    | last typeName == '*'
   && symbolIsBoxed  typeKind  = ("BOXED",  cTypeNameToHSType (init typeName))
    | last typeName == '*'
   && symbolIsObject typeKind  = ("OBJECT", cTypeNameToHSType (init typeName))
  where typeKind = Map.lookup (init typeName) knownSymbols
convertSignalType _ typeName   =  ("{-" ++ typeName ++ "-}", "{-" ++ typeName ++ "-}")

-------------------------------------------------------------------------------
-- Now for some special cases, we can override the generation of {# call #}'s
-------------------------------------------------------------------------------

-- The ordinary case:
genCallOrdinary :: String -> Bool -> Doc
genCallOrdinary cname _unsafe@True  = c2hsHook "call unsafe" (text cname)
genCallOrdinary cname _unsafe@False = c2hsHook "call" (text cname)

-- On win32 for glib/gtk 2.6 they changed the interpretation of functions that
-- take or return system file names (as opposed to user displayable
-- representations of file names). Previously the string encoding of the file
-- name was that of the systems native 'codepage' which was usually ascii but
-- could be one of several obscure multi-byte encodings. For 2.6 they have
-- changed to always use a UTF8 encoding. However to maintain binary backwards
-- compatibility they kept the old names and added new ones with a _utf8 suffix
-- for the new interpretation. However the old names are only in the binary,
-- they are not exposed through the C header files so all software building
-- against glib/gtk 2.6 on windows must use the _utf8 versions. Hence we
-- generate code uses the _utf8 version if we're building on windows and using
-- gtk version 2.6 or later. Ugh.

genCall :: String -> Bool -> Doc
genCall cname safty | cname `Set.member` win32FileNameFunctions
                           = nest (-2) (text "#if defined (WIN32) && GTK_CHECK_VERSION(2,6,0)")
                          $$ genCallOrdinary (cname ++ "_utf8") safty
                          $$ nest (-2) (text "#else")
                          $$ genCallOrdinary cname safty
                          $$ nest (-2) (text "#endif")
genCall cname unsafe = genCallOrdinary cname unsafe
