-- Output contents of a .hsc file to standard output

module WriteHsc where

import Data.Char
import C_BNF
import SplitBounds
import Template
import Text.ParserCombinators.Parsec
import HsfUtils
import System.Exit
import qualified Data.Map as Map
import Data.Maybe
import Data.List
import Control.Monad
import System.IO
import Control.Concurrent
import GHC.Conc

-- Name for the HSFFIG field access class, and module to import

fldclass = "HSFFIG.FieldAccess.FieldAccess"
fldmodule = "HSFFIG.FieldAccess"

-------------------------------------------------------------------------------------------

-- Write the beginning of the hsc code if the header filename
-- is known (Just ...). The beginning consists of
--  * an include statement (with header file name)
--  * module declaration (module name derived form the header file name)
--  * import statements for Foreign modules.

-- If header file name cannot be obtained (gcc -E -P) (mfn == Nothing)
-- then placeholders will be placed instead of header file name and module
-- name declared. This may be useful if something other is desired for
-- module name rather than header filename derivative.
-- Derivation of module name includes stripping file path, uppercasing the header filename
-- and replacing dots with underscores (so most likely module name will end
-- with _H).

ghcopts = "{-# OPTIONS -fglasgow-exts -XForeignFunctionInterface #-}"

writeModHdr mfn = do let fmfn = finalizeModuleName mfn
                     writeTemplate stdout
                     putStrLn $ "#def void _dummy_force_" ++ fmfn ++ "_hsc_c (void) { }"
                     putStrLn $ ghcopts
                     putStrLn $ "\n" ++ splitBegin ++ "/" ++ fmfn ++ "\n"
                     putStrLn $ ghcopts
                     putStrLn $ "#include " ++ (finalizeFileName mfn)
                     putStrLn $ "#ifndef __quote__"
                     putStrLn $ "#define __quote__(x...) x"
                     putStrLn $ "#endif"
                     putStrLn $ ""
                     putStrLn $ "module " ++ fmfn ++ "("
                     putStrLn $ "  module " ++ fmfn ++ ","
                     putStrLn $ splitOpen
                     putStrLn $ "  module " ++ fmfn ++ "_C,"
                     putStrLn $ "  module " ++ fmfn ++ "_S,"
                     putStrLn $ "  module " ++ fmfn ++ "_F,"
                     putStrLn $ "  module " ++ fmfn ++ "_E,"
                     putStrLn $ "  module " ++ fmfn ++ "_S_d,"
                     putStrLn $ "  module " ++ fmfn ++ "_S_t,"
                     putStrLn $ "  module " ++ fmfn ++ "_S_n,"
                     putStrLn $ splitClose
                     putStrLn $ "  module " ++ fldmodule ++ ","
                     putStrLn $ "  module Foreign,"
                     putStrLn $ "  module Foreign.C.String,"
                     putStrLn $ "  module Foreign.C.Types) where"
                     putStrLn $ ""
                     putStrLn $ "import Foreign"
                     putStrLn $ "import Foreign.Ptr"
                     putStrLn $ "import Foreign.C.Types"
                     putStrLn $ "import Foreign.C.String"
                     putStrLn $ "import " ++ fldmodule
                     putStrLn $ splitOpen
                     putStrLn $ "import " ++ fmfn ++ "_C"
                     putStrLn $ "import " ++ fmfn ++ "_S"
                     putStrLn $ "import " ++ fmfn ++ "_F"
                     putStrLn $ "import " ++ fmfn ++ "_E"
                     putStrLn $ "import " ++ fmfn ++ "_S_d"
                     putStrLn $ "import " ++ fmfn ++ "_S_t"
                     putStrLn $ "import " ++ fmfn ++ "_S_n"
                     putStrLn $ "import " ++ fldmodule
                     putStrLn $ splitClose
                     putStrLn $ "\n" ++ splitEnd ++ "\n"

writeSplitHeaderX imps rexps mn = do
  putStrLn $ splitOpen
  putStrLn $ ghcopts
  putStrLn $ "module " ++ mn ++ " ("
  mapM (putStrLn . (\s -> "  module " ++ s ++ ",")) rexps
  putStrLn $ "  module " ++ mn
  putStrLn $ ") where\n"
  putStrLn $ "import Foreign"
  putStrLn $ "import Foreign.C.Types"
  mapM (putStrLn . ("import " ++)) imps
  putStrLn $ splitClose
  putStrLn $ ""

writeSplitHeader imps mn = writeSplitHeaderX imps [] mn

--------------------------------------------------------------------------------------

-- For every constant defined in the headers, produce accessor functions
-- using the following pattern:
-- c_CONST = #const CONST i. e. first character of the constant name
-- is lowercased.
-- Not all constants defined in the header file are good for inclusion
-- in the generated bindings code. For each, a short two-liner program
-- will be compiled. If there are no syntax errors, the constant qualifies.
-- This requires knowledge of the header file name. Therefore if it was impossible
-- to determine it, constants will not be included.

writeConstAccess _ _ Nothing = return ()
writeConstAccess tus gcc mbfn@(Just fn) = do
  let    cnsts = Map.keys $ Map.filterWithKey constonly tus
         constonly _ DictDef = True
         constonly _ _ = False
  nsect <- writeConstAccess' 0 cnsts gcc mbfn
  let fmfnc = (finalizeModuleName (Just fn)) ++ "_C"
      cmods = map (((fmfnc ++ "_") ++) . show) [0 .. (nsect - 1)]
  putStrLn $ "\n" ++ splitBegin ++ "/" ++ fmfnc ++ "\n"
  writeSplitHeaderX cmods cmods fmfnc
  putStrLn $ "\n" ++ splitEnd ++ "\n"
  return ()

writeConstAccess' n [] _ _ = return n
writeConstAccess' n tusd gcc Nothing = return 0
writeConstAccess' n tusd gcc (Just fn) = 
  do let cnsts = take 100 tusd
         trem = drop 100 tusd
         fmfnc = (finalizeModuleName (Just fn)) ++ "_C_" ++ show n
         finfn = finalizeFileName (Just fn)
     putStrLn $ "\n" ++ splitBegin ++ "/" ++ fmfnc ++ "\n"
     writeSplitHeader [] fmfnc
     guess <- guessConsts finfn gcc cnsts >>= return . filter ((/= NoGuess) . fst . snd)
     let vagues = filter ((== Vague) . fst . snd) guess
         ints = filter ((== GuessInt) . fst . snd) guess
         floats = filter ((== GuessFloat) . fst . snd) guess
     mapM (oneconst ExitSuccess) (map fst ints)
     mapM (oneconst ExitSuccess) (map fst floats)
     testsyn (testConst finfn gcc) (map fst vagues)
     putStrLn $ "\n" ++ splitEnd ++ "\n"
     writeConstAccess' (n + 1) trem gcc (Just fn)

testsyn fn [] = return ()

testsyn fn cs = do
  let nproc = 4
  let h = take nproc cs
      t = drop nproc cs
  let nt = length h
  mvs <- mapM (\_ -> newEmptyMVar) h
  hx <- zipWithM (\c v -> forkOS (do
    x <- fn c
    putMVar v x)) h mvs
  whx <- mapM takeMVar mvs
  zipWithM oneconst whx h
  testsyn fn t


-- No import for #define alloca.

oneconst _ "alloca" = return ()

oneconst rc cnst = 
  case rc of
    ExitSuccess -> (putStrLn $ "c_" ++ cnst ++ " = #const " ++ cnst) >> hFlush stdout
    _ -> return ()

---------------------------------------------------------------------------------------

-- For every enumeration, compute values for implicitly valued variants.
-- Then emit all variants as constants similarly to constants themselves.

writeEnums tus tymap fn =
  do let enums = Map.toList $ Map.filterWithKey enumsonly tymap
         enumsonly _ (DictEnum _) = True
         enumsonly _ _ = False
         enumsof (DictEnum e) = map simplifyenum e
         simplifyenum (Enumerator s Nothing) = (s,"")
         simplifyenum (Enumerator s (Just e)) = (s,show e)
         allvariants = concat $ map (fillenum "0") $ map (enumsof . snd) enums
         fmfne = (finalizeModuleName fn) ++ "_E"
     putStrLn $ "\n" ++ splitBegin ++ "/" ++ fmfne ++ "\n"
     writeSplitHeader [] fmfne
     mapM onevariant allvariants
     putStrLn $ "\n" ++ splitEnd ++ "\n"


onevariant (s,e) = 
  putStrLn $ "e_" ++ s ++ " = #const " ++ e

-- Fill an enumeration with explicit variants. Every implicit variant
-- is considered to be its predecessor plus 1. The first implicit variant
-- is 0.

fillenum _ [] = []
fillenum val (ed:eds) = fv : (fillenum (nxtval fv) eds) where
  fv = fixval val ed
  fixval val (s,"") = (s,val)
  fixval _ (s,e) = (s,e)
  nxtval (_,vals) = "(" ++ vals ++ ") + 1"

---------------------------------------------------------------------------------------

-- For every C structure or union, declare a newtype, and field access functions.
-- The type map will be scanned for recorded structures.

-- To access structure fields, special data constructor will be created,
-- containing variants for all field names found in the header.

-- To provide convinient syntax of field access, a class is defined in each
-- module created from a header, e. g. for `example.h':

-- class EXAMPLE_H_fieldaccess a b c | a c -> b where
--   (-->) :: Ptr a -> c -> IO b
--   (<--) :: (Ptr a, c) -> b -> IO ()

-- and for each named field an algebraic data type whose name is derived from
-- the field name:

--  data V_a = V_a 
--  data V_b = V_b
--  data V_c = V_c

-- also similar constructs with X_ and D_ prefixes.

-- The combinator, (-->), is defined to access fields of structures by their
-- derivative names. For example, given a struct STR {int blah;};, and
-- a pointer `ptrstr' returned from some function, the field blah may be
-- accessed as `ptrstr --> V_blah.

-- For the size of the structure, a fictive field, V_sizeof, is defined.

-- Functional dependencies in the class are necessary to eliminate the need
-- to explicitly specify retrieved value's type in the calling function. That is,
-- structure type (a) and field selector type (c) uniquely determine the return type (b).

-- Instance (minimal) of Storable is also provided for each structure
-- to be able to use alloca/malloc when necessary.

data StructInfo = StructInfo {
  strName :: String,				-- structure type name as supplied
  trueName :: String,				-- true name of the structure
  convName :: String,				-- converted name of the structure
  cSyntax :: String				-- C syntax layout of the structure
} deriving (Show)

data FieldInfo = FieldInfo {
  fldName :: String,				-- field name
  fldType :: String,				-- field type
  instType :: String,				-- field type for instance FieldAccess
  fldTypeString :: TypeString,			-- field type string after the TCM state machine
  fldArity :: Int,				-- field arity (0 for non-functions)
  fldDims :: String,				-- dimensions if an array ([] for scalars)
  isDynamic :: Bool,				-- field represents a dynamic import (FunPtr)
  isDirect :: Bool,				-- field represents a direct structure/union
  isVariadic :: Bool,				-- field represents a variadic function
  isBitField :: Bool,				-- bit field
  internId :: String				-- field symbol internal ID
} deriving (Show)

sd2fld (StructDeclarator (Just d) _) = id2name (InitDeclarator d Nothing)
sd2fld _ = ""

collectfields dss = concat $ map collectfields' dss where
  collectfields' (DictStruct su ss) = concat $ map collectfields'' ss where
    collectfields'' (StructDecl _ sds) = map sd2fld sds where

-- Anonymous identifiers (starting with "_@_") will not be processed.

writefields flds fn =
  mapM fldata flds where
    fldata ('_':'@':'_':_) = return ()
    fldata fld = do putStrLn $ "data V_" ++ fld ++ " = V_" ++ fld
                    putStrLn $ "data X_" ++ fld ++ " = X_" ++ fld
                    putStrLn $ "data D_" ++ fld ++ " = D_" ++ fld

writeStructures tus tymap fn = 
  do let structs = Map.filterWithKey structonly tymap
         typedefs = Map.toList $ Map.filterWithKey tdefonly tymap
         typeeqs = Map.toList $ Map.filterWithKey tteqonly tymap
         structonly _ (DictStruct _ _) = True
         structonly _ _ = False
         tdefonly _ (DictDecl _) = True
         tdefonly _ _ = False
         tteqonly _ (DictTypeEq _) = True
         tteqonly _ _ = False
         allfields = nub $ collectfields (Map.elems structs)
         fmfns = (finalizeModuleName fn) ++ "_S"
         strpairs = Map.toList structs
         strnames = map fst strpairs
         submods = map (((fmfns ++ "_") ++) . convname) strnames
     putStrLn $ "\n" ++ splitBegin ++ "/" ++ fmfns ++ "\n"
     writeSplitHeaderX submods submods fmfns
     putStrLn $ "\n" ++ splitEnd ++ "\n"
     putStrLn $ "\n" ++ splitBegin ++ "/" ++ fmfns ++ "_d\n"
     writeSplitHeader [] $ fmfns ++ "_d"
     writefields ("sizeof" : allfields) fn
     putStrLn $ "\n" ++ splitEnd ++ "\n"
     putStrLn ""
     putStrLn $ "\n" ++ splitBegin ++ "/" ++ fmfns ++ "_t\n"
     writeSplitHeader [fmfns ++ "_n"] $ fmfns ++ "_t"
     mapM (tdefalias tymap) (typedefs ++ typeeqs)
     putStrLn $ "\n" ++ splitEnd ++ "\n"
     putStrLn ""
     putStrLn $ "\n" ++ splitBegin ++ "/" ++ fmfns ++ "_n\n"
     writeSplitHeader [] $ fmfns ++ "_n"
     mapM structnewtype strnames
     putStrLn $ "\n" ++ splitEnd ++ "\n"
     mapM (onestruct tymap fn) strpairs
     return ()

-- Fill out a strinfo data structure.

mkStrInfo strname strdecl =
  StructInfo {
    strName = strname,
    trueName = if (isAnon strname) then (show strdecl) else (truename strname),
    convName = convname strname,
    cSyntax = show strdecl
  }

-- Fill out a fldinfo data structure.

mkFldInfo tymap xd = 
  let state = (dcl2ts . (connectta tymap) . simplifystructdecl) xd
      sdcl2sd (StructDecl _ s) = head s
      fldn = (sd2fld . sdcl2sd) xd
      stt = state2ts state
      cmap = mapc2hs stt
      stsp = condmonadify cmap
      condmonadify t = if (dyn t) then (monadify "IO" t) else t
      dyn t = case t of
        (PtrF _ _) -> True
        other -> False
      dir = isdirstruct cmap
      sdecl = ts2ts stsp
      bit = '|' `elem` sdecl
      instt =
        (case isarray stt of
           True -> "Ptr ("
           False -> "(" )
          ++ (case dyn stt of
                True  -> unfptr sdecl
                False -> case dir of
                  True  -> "Ptr " ++ (drop 1 sdecl)
                  False -> case bit of
                    True -> drop 1 $ dropWhile (/= '|') sdecl
                    False -> sdecl) ++ ")"
      dims = case isarray stt of
        True -> arrdims stt
        False -> []
      arity tsp = case tsp of
        TApply ts -> (length ts) - 1
        PtrF _ t -> arity t
        other -> 0
      intern = case (Map.lookup ("ID " ++ fldn) tymap) of
        Just (DictId n) -> "___" ++ (show n) ++ "___"
        other -> ""
      isarray tt = case tt of
        TString ts -> isarrt ts
        TString' ts -> isarrt ts
        PtrV _ t -> isarray t
        other -> False
      arrdims tt = case tt of
        TString ts -> (fst . splitarrt) ts
        TString' ts -> (fst . splitarrt) ts
        PtrV _ t -> arrdims t
        other -> []
  in  FieldInfo {
        fldName = fldn,
        fldType = sdecl,
        instType = instt,
        fldTypeString = stsp,
        fldArity = arity stsp,
        fldDims = dims,
        isDynamic = dyn stt,
        isDirect = dir,
        isBitField = bit,
        isVariadic = isvariadic cmap,
        internId = intern
      }

-- Toplevel wrapper to fill out structure and field descriptors.

mkStructsInfo tymap strname strdecl = 
  let xdecls = decls strdecl
      decls (DictStruct su s) = expdecls s
      strinfo = mkStrInfo strname strdecl
      fldinfos = map (mkFldInfo tymap) xdecls
  in  (strinfo, fldinfos)

-- Write complete definition for a single structure/union.

onestruct tymap fn (strname,strdecl) = 
  do let strm = fmfns ++ "_" ++ (convname strname)
         fmfns = (finalizeModuleName fn) ++ "_S"
         (strinfo, fldinfos) = mkStructsInfo tymap strname strdecl
     putStrLn ""
     putStrLn "--"
     putStrLn ""
     putStrLn $ "\n" ++ splitBegin ++ "/" ++ strm ++ "\n"
     writeSplitHeader (fldmodule : (map (fmfns ++) ["_t", "_n", "_d"])) strm
     mapM (structinstance fn strinfo) fldinfos
     when ((length fldinfos) /= 0) $ structinstance fn strinfo FieldInfo {
       fldName = "sizeof",
       fldType = "CInt",
       instType = "CInt",
       fldArity = 0,
       fldTypeString = TString' "CInt",
       fldDims = [],
       isDynamic = False,
       isDirect = False,
       isBitField = False,
       isVariadic = False,
       internId = ""
     }
     putStrLn $ splitEnd

-- Expand declarations so that if there are multiple member declarations
-- of the same type, the same number of one-member declarations will be created.

expdecls sds = concat $ map expdecls' sds where
  expdecls' (StructDecl dss []) = []
  expdecls' (StructDecl dss (sdd:sdds)) = (StructDecl dss [sdd]):(expdecls' (StructDecl dss sdds)) 

-- Convert a structure/union name to Haskell type name.

convname ('s':'t':'r':'u':'c':'t':'@':strname) = "S_" ++ strname
convname ('u':'n':'i':'o':'n':'@':strname) = "U_" ++ strname

-- Restore true structure/union/typedef name for use with hsc2hs.
-- If the structure name ends with a prime, then this is a typedef,
-- and the word struct/union along with the prime must be removed.
-- If it does not end with a prime, @ is replaced with space.

truename strname
  | head (reverse strname) == '\'' = reverse (drop 1 (reverse (drop 2 (convname strname))))
  | otherwise = map at2space strname
      where at2space '@' = ' '
            at2space z = z

-- Write the instance of the field access class for this structure.
-- For direct structures/unions (fldtype starts with '@') a pointer
-- will be retrieved, otherwise a value. For function pointers,
-- a pointer factory will be created and applied, so ready to use
-- function (in Haskell sense) will be returned.
-- For anonymous structures, their C syntax (deparsed) will be used
-- to form the #peek construction.

structinstance fn strinfo fldinfo | take 3 (fldName fldinfo) == "_@_" = return ()

structinstance fn strinfo fldinfo = do
  let icid = (internId fldinfo) ++ (convName strinfo)
      mkfld = icid ++ "___mk"
      wrfld = icid ++ "___wr"
      arglist = intlv (take (fldArity fldinfo) $ map (('_' :) . show) [1..]) " "
      exclimp = case (isDynamic fldinfo, isVariadic fldinfo||isDirect fldinfo) of
                  (False, _)    -> False
                  (True, False) -> False
                  (True, True)  -> True
  instheader fldclass strinfo fldinfo instType "V_"
  case exclimp of
    True -> excludeimport (fldName fldinfo) (isVariadic fldinfo) (isDirect fldinfo)
    False -> case (isBitField fldinfo) of
      True -> bitfield strinfo fldinfo
      False -> do 
        case (isDynamic fldinfo) of
          False -> case (isDirect fldinfo) of
            True  -> ptrfield strinfo (fldName fldinfo)
            False -> case (fldName fldinfo == "sizeof") of
              True  -> sizefield strinfo
              False -> peekfield strinfo (fldName fldinfo)
          True  -> do peekdynfld strinfo (fldName fldinfo) mkfld wrfld
                      makedynfld fldinfo mkfld
                      makewrpfld fldinfo wrfld
                      cbckimport ((convName strinfo) ++ "_" ++ (fldName fldinfo)) 
                                 (fldTypeString fldinfo)
                      when (fldArity fldinfo > 0) $ do
                        instheader fldclass strinfo fldinfo instType "X_"
                        putStrLn $ "  z ==> X_" ++ (fldName fldinfo) ++ " = \\" ++ 
                                   arglist ++ " -> do"
                        putStrLn $ "    x <- z --> V_" ++ (fldName fldinfo) 
                        putStrLn $ "    r <- x " ++ arglist
                        putStrLn $ "    return r"
        when ((length $ fldDims fldinfo) > 0) $ dimfield strinfo fldinfo

-- Output an instance header for the given structure, field, type.

instheader fc si fi ft pfx = 
  putStrLn $ "\ninstance " ++ fc ++ " " ++ (convName si) ++ " (" ++
             (ft fi) ++ ") " ++ pfx ++ (fldName fi) ++ " where"

-- For structures members which are arrays, output 
-- a pseudo-member to access the dimensions

dimfield si fi = do
  instheader fldclass si fi (\_ -> "[Int]") "D_"
  putStrLn $ "  z --> D_" ++ (fldName fi) ++ " = return " ++ (fldDims fi)
  putStrLn $
    "  (z, D_" ++ (fldName fi) ++ ") <-- v = error $ \"dimensions of a field  cannot be set\""

-- Output code to access a bit field.

bitfield si fi = do
  let icid = (internId fi) ++ (convName si)
      getbf = icid ++ "___get___" ++ (fldName fi) ++ "___"
      setbf = icid ++ "___set___" ++ (fldName fi) ++ "___"
      getbfhs = getbf ++ "___hs___"
      setbfhs = setbf ++ "___hs___"
      ctype = map u2sp $ takeWhile (/= '|') (fldType fi)
      u2sp '_' = ' '
      u2sp z = z
  putStrLn $ "  z --> V_" ++ (fldName fi) ++ " = " ++ getbfhs ++ " z"
  putStrLn $ "  (z, V_" ++ (fldName fi) ++ ") <-- v = " ++ setbfhs ++ " z v"
  putStrLn $ ""
  putStrLn $ "foreign import ccall unsafe \"static " ++ getbf ++ "\""
  putStrLn $ "  " ++ getbfhs ++ " :: Ptr " ++ (convName si) ++
             " -> IO " ++ (instType fi)
  putStrLn $ "foreign import ccall unsafe \"static " ++ setbf ++ "\""
  putStrLn $ "  " ++ setbfhs ++ " :: Ptr " ++ (convName si) ++
             " -> " ++ (instType fi) ++ " -> IO ()"
  putStrLn $ ""
  putStrLn $ "#def inline " ++ ctype ++ " " ++ getbf ++ "(void *s) {"
  putStrLn $ "  return ((" ++ (trueName si) ++ " *)s) -> " ++ (fldName fi) ++ ";"
  putStrLn $ "}"
  putStrLn $ ""
  putStrLn $ "#def inline void " ++ setbf ++
             "(void *s, " ++ ctype ++ " v) {"
  putStrLn $ "  ((" ++ (trueName si) ++ " *)s) -> " ++ (fldName fi) ++ " = v;"
  putStrLn $ "}"

ptrfield _ ('_':'@':'_':_) = return ()

ptrfield si fld = do
  putStrLn $ 
    "  z --> V_" ++ fld ++ " = return $ (#ptr __quote__(" ++ (trueName si) ++ "), " ++ fld ++ ") z"
  putStrLn $
    "  (z, V_" ++ fld ++ ") <-- v = error $ \"field " ++ fld ++ " is a structure or an array:" 
                                                      ++ " cannot be set\""

-- Write the V_sizeof field.
-- Also write a instance Storable for the structure. Only sizeOf and alignment
-- are effective. Peek and poke will cause error if used. 

sizefield si = do
  putStrLn $ "  z --> V_sizeof = return $ (#size __quote__(" ++ (trueName si) ++ "))"
  putStrLn $ ""
  putStrLn $ "instance Storable " ++ (convName si) ++ " where"
  putStrLn $ "  sizeOf _ = (#size __quote__(" ++ (trueName si) ++ "))"
  putStrLn $ "  alignment _ = 1"
  putStrLn $ "  peek _ = error $ \"peek and poke cannot be used with " ++ (trueName si) ++ "\""
  putStrLn $ "  poke _ = error $ \"peek and poke cannot be used with " ++ (trueName si) ++ "\""

-- A regular structure member which can be read and set.

peekfield si fld = do
  putStrLn $ "  z --> V_" ++ fld ++ " = (#peek __quote__(" ++ (trueName si) ++ "), " ++ fld ++ ") z"
  putStrLn $ "  (z, V_" ++ fld ++ ") <-- v = (#poke __quote__(" ++ (trueName si) ++ "), " ++ 
             fld ++ ") z v"

-- Output dynamic wrappers for a structure member holding function pointer.

makedynfld fldinfo mkf = do
  putStrLn $ "foreign import ccall \"dynamic\"\n" ++
             "  " ++ mkf ++ " :: (" ++ (fldType fldinfo) ++ ") -> (" ++ (instType fldinfo) ++ ")"

makewrpfld fldinfo wrp  = do
  putStrLn $ "foreign import ccall \"wrapper\"\n" ++
             "  " ++ wrp ++ " :: (" ++ (instType fldinfo) ++ ") -> IO (" ++ (fldType fldinfo) ++ ")"

-- Output code to access a structure member containing a function pointer.

peekdynfld si fld mkf wrp = do
  putStrLn $ "  z --> V_" ++ fld ++ " = (#peek __quote__(" ++ (trueName si) ++ "), " ++ 
             fld ++ ") z" ++
             " >>= (return . " ++ mkf ++ ")"
  putStrLn $ "  (z, V_" ++ fld ++ ") <-- v = (" ++ wrp ++ " v) >>= " ++
             "(#poke __quote__(" ++ (trueName si) ++ "), " ++ fld ++ ") z"


-- Write newtype statements for every structure.

structnewtype strname = do
  putStrLn $ "newtype " ++ (convname strname) ++ " = " ++ (convname strname) ++ " ()"

-- Write type declarations for all type aliases.

tdefalias tymap (tal, DictDecl (Declaration dss [id] _)) = do
  let target = (ts2ts . 
                (monadify "IO") . 
                mapc2hs . 
                state2ts . 
                dcl2ts . 
                (connectta tymap)) (simplifydecl dss id)
      polish ('@':s) = s
      polish z = z
  putStrLn $ "type T_" ++ tal ++ " = " ++ (polish target)

-- Write type equivalencies created during fixing named-within-anonymous struct
-- declarations.

tdefalias tymap (tal, DictTypeEq trg) =
  putStrLn $ "type " ++ tal ++ " = " ++ trg


-- Similarly to Declaration, simplify a structure/union member declaration.

simplifystructdecl (StructDecl dss [sd]) = simplifydecl (fst isd) (snd isd) where
  isd = initdecl dss sd
  initdecl dss (StructDeclarator (Just d) Nothing) = (dss, InitDeclarator d Nothing)
  initdecl _ (StructDeclarator (Just d) (Just c)) = 
    ((DeclSpecType (TypeSpecPrim "$BF$")):dss, InitDeclarator d Nothing)


---------------------------------------------------------------------------------------

-- For every standalone (declared at top level) function or variable
-- (i. e. an unary function), unless it returns a structure.
-- To determine whether a function returns a structure rather than a pointer,
-- its return type alias (if any) is traced back to the original type.
-- If include file name is provided, it will be used in FFI declarations.

inclname (Just fn) = fn
inclname Nothing = ""

writeStandaloneFunctions tus tymap fn = 
  do let imps = map ((finalizeModuleName fn) ++) ["_S", "_C", "_E", "_S_d","_S_t","_S_n"]
         fmfnf = (finalizeModuleName fn) ++ "_F"
     putStrLn $ "#include <stdlib.h>"
     putStrLn $ "\n" ++ splitBegin ++ "/" ++ fmfnf ++ "\n"
     writeSplitHeader imps fmfnf
     mapM (onefunc' tymap (inclname fn)) tus
     putStrLn $ "\n" ++ splitEnd ++ "\n"
     return ()

-- One TransUnitDecl may contain multiple InitDeclarator's.
-- Take care on those assuming they all have the same return
-- type and attributes.

onefunc' tymap ifn (TransUnitDecl (Declaration dss ids ats)) = 
  do mapM (onefunc tymap ifn (typeonly dss) ats) ids
     return ()

onefunc' _ _ _ = return ()

-- Keep only primitive types and type aliases.

typeonly dss = filter typeonly' dss where
  typeonly' (DeclSpecType _) = True
  typeonly' _ = False

-- Retrieve name from an InitDeclarator. This is taken from (Left s)
-- of the last unnested declarator.


dcl2name (Declarator _ (Left s) _) = s
dcl2name (Declarator _ (Right d') _) = dcl2name d'

id2name (InitDeclarator d _) = dcl2name d

-- Retrieve the return type from a declaration as a list of type name
-- strings. Enums are represented as integers.

ds2rtn [] = [] 
ds2rtn (d:ds) = (ds2str d):(ds2rtn ds)
ds2str (DeclSpecType (TypeSpecPrim s)) = s
ds2str (DeclSpecType (TypeSpecAlias s)) = s
ds2str (DeclSpecType (TypeSpecStruct (StructSpec s1 "" _))) = 
  error $ "unfixed declaration of anonymous " ++ s1
ds2str (DeclSpecType (TypeSpecStruct (StructSpec s1 s2 _))) = s1 ++ "_" ++ s2
ds2str (DeclSpecType (TypeSpecEnum (EnumSpec "" _))) = 
  error $ "unfixed declaration of anonymous enum"
ds2str (DeclSpecType (TypeSpecEnum (EnumSpec s _))) = "int"
ds2str z = error $ "ds2str: " ++ (show z)

-- Simplify a Declaration by keeping only type declarations, Declarators
-- without initializers, etc.

data DeclarationS = DeclarationS [String]            -- remains of [DeclSpec]
                                 DeclaratorS         -- remains of InitDeclarator
                                 deriving (Show)

data DeclaratorS = DeclaratorS Int                   -- pointer depth
                               (Maybe DeclaratorS)   -- if "Right Declarator"
                               DeclType              -- remains of CPI
                               deriving (Show)

data DeclType = DeclTypeVar                          -- for variables
              | DeclTypeVariadic                     -- to signal a variadic function
              | DeclTypeFixed [DeclarationS]         -- converted CPIs for regular functions
              | DeclTypeUnknown String               -- not implemented yet
              deriving (Show)

simplifydecl dss id@(InitDeclarator decl mbi) = 
  DeclarationS dsn' (simplifyid $ InitDeclarator decl' mbi) where
    dsn = ds2rtn $ typeonly dss
    (dsn', decl') = convarray (dsn, decl)

-- Convert array types with empty brackets to pointers, i. e. a[][] into int **a.
-- Arrays with nonempty dimension information keep that information in their
-- typestrings.

convarray (tss, d@(Declarator ps esd [])) = (tss, d)

convarray (tss, d@(Declarator ps esd (cpi:cpis))) = 
  case cpi of
    CPICon Nothing -> convarray (tss, Declarator (ps ++ [Pointer []]) esd cpis)
    CPICon (Just con) -> convarray (con2num con : tss, Declarator ps esd cpis)
    other -> (tss, d)

-- When dealing with array dimensions (which may be C constant expressions),
-- wrap them in the #const macro for hsc2hs to process. Replace underscores
-- (_) with backquotes (`) temporarily not to confuse the further processing
-- of the type because underscores serve as separators between parts of the type
-- definition.

con2num con = "(#const(" ++ map un2bq (show con) ++ "))@" where
  un2bq '_' = '`'
  un2bq z = z

convdims d@(Declarator ps esd []) = d

convdims d@(Declarator ps esd (cpi:cpis)) = 
  case cpi of
    (CPICon Nothing) -> convdims (Declarator (ps ++ [Pointer []]) esd cpis)
    other      -> d

-- simplify InitDeclarator (convert to DeclaratorS).

simplifyid (InitDeclarator dd _) = 
  mkdecls (convdims dd) where
    mkdecls (Declarator ps esd cpis) = 
      DeclaratorS (length ps) (convesd esd) (convcpis cpis)
    convesd (Left _) = Nothing
    convesd (Right dd') = 
      Just (mkdecls (convdims dd'))
    convcpis [] = DeclTypeVar
    convcpis [CPIEmpty] = DeclTypeFixed []
    convcpis [CPIPar _ Variadic] = DeclTypeVariadic
    convcpis [CPIPar pds Fixed] = DeclTypeFixed (map convpdecl pds)
    convcpis z = DeclTypeUnknown (show z)
    convpdecl (ParamDecl pdds mbd) = simplifydecl pdds (mbd2id mbd)
    mbd2id (Just d) = InitDeclarator d Nothing
    mbd2id Nothing = InitDeclarator (Declarator [] (Left "") []) Nothing

-- Connect type aliases to the declarations that use them.

-- Apply connection with type aliases to every function parameter type

ctad tymap (DeclaratorS ps mbds dds) = 
  DeclaratorS ps mbds' dds'
    where mbds' = case mbds of
            Just d -> Just $ ctad tymap d 
            other -> mbds
          dds' = case dds of
            DeclTypeFixed dtds -> DeclTypeFixed $ map (connectta tymap) dtds
            other -> dds

-- Follow the type alias' chain of declarators, and when Nothing is found,
-- connect the target declarator (taken from the target declaration).

cncd tymap (DeclarationS rts decl) dclc = DeclarationS rts (cncd' (ctad tymap decl) dclc) where
  cncd' (DeclaratorS ps (Just d) dt) dclc = DeclaratorS ps (Just (cncd' d dclc)) (tpmap dt)
  cncd' (DeclaratorS ps Nothing dt) dclc = DeclaratorS ps (Just dclc) (tpmap dt)
  tpmap dtt = case dtt of
    DeclTypeFixed dtds -> DeclTypeFixed $ map (connectta tymap) dtds
    other -> dtt

-- When mapping type aliases, take care on possible array dimensions:
-- strip them before mapping (they look like n@ where n is a natural number),
-- and prepend them back after the mapping is done.

connectta tymap (DeclarationS rts decl) = 
  restoredims rtsdims $ connectta' tymap (DeclarationS rts' decl) where
    rtsdims = takeWhile ("@" `isSuffixOf`) rts
    rts' = dropWhile ("@" `isSuffixOf`) rts
    restoredims dms (DeclarationS r d) = DeclarationS (dms ++ r) d
    connectta' tymap (DeclarationS rts decl) = 
      let decl' = ctad tymap decl in
        case (length rts) of
          1 -> case (Map.lookup (head rts) tymap) of
                 Nothing -> DeclarationS rts decl'
                 Just (DictDecl (Declaration adss [aid] at)) -> 
                   cncd tymap (connectta tymap $ simplifydecl adss aid) decl'
                 other -> error $ (head rts) ++ " is not a type alias"
          other -> DeclarationS rts decl'

-- Type conversion state machine. The chain of DeclaratorS's starting at the first
-- DeclarationS is followed. Each DeclaratorS acts as an instruction modifying
-- the state.

data TypeString = TString String
                | TString' String	  -- same as TString but no more type mappings
                | TApply  [TypeString]
                | PtrF    Int TypeString
                | PtrV    Int TypeString
                | Mnd     String TypeString
                deriving (Show)

data TCMState = TCMState TypeString        -- current type string
                         Bool              -- True if function (to produce proper Ptr)
                         deriving (Show)

-- String representation of a TypeString

ts2ts (TString s) = s
ts2ts (TString' s) = s
ts2ts (TApply tss) = intlv (map ts2ts tss) " -> "
ts2ts (PtrV ps ts) = nptrs ps (ts2ts ts) "Ptr"
ts2ts (PtrF ps ts) = nptrs ps (ts2ts ts) "FunPtr"
ts2ts (Mnd  ms (TString ts)) = ms ++ " " ++ ts
ts2ts (Mnd  ms z) = ms ++ " (" ++ ts2ts z ++ ")"

nptrs 0 s _  = s
nptrs n s p = p ++ " (" ++ (nptrs (n - 1) s p) ++ ")"

-- Map C types in the type string to Haskell types (if available).

mapc2hs (TString' z) = TString' z

-- Function application: map type of each parameter and return type.
-- Take arguments-arrays into consideration.

mapc2hs (TApply tss) = TApply $ map mapc2hs_arr tss

-- Special cases.

-- Pointer types: map the target type.
-- Nested pointers: increase pointer depth. 
-- Pointers to void are represented as Ptr CChar.
-- Pointers to variadic functions are represented as pointers to nullary
-- functions.
-- Structures, unions: only pointers are valid.

mapc2hs (PtrF ps ts) = case ts of 
  PtrF ps' ts' -> mapc2hs (PtrF (ps + ps') ts')
  (TApply [TString "@@variadic@@"]) -> PtrF ps (TApply [TString "()"])
  other -> PtrF ps (mapc2hs ts) 

mapc2hs (PtrV ps ts) = case ts of
  PtrV ps' ts' -> mapc2hs (PtrV (ps + ps') ts')
  TString "void" -> mapc2hs (PtrV ps (TString "char"))
  TString ('s':'t':'r':'u':'c':'t':'_':strname) -> PtrV ps (TString ("S_" ++ strname))
  TString ('u':'n':'i':'o':'n':'_':strname) -> PtrV ps (TString ("U_" ++ strname))
  other -> PtrV ps (mapc2hs ts) 

-- Manually hardcoded type mapping, based on Page 32 of the FFI Addendum.

mapc2hs (TString "int") = TString' "CInt"
mapc2hs (TString "signed_int") = TString' "CInt"
mapc2hs (TString "unsigned_int") = TString' "CUInt"
mapc2hs (TString "signed") = TString' "CInt"
mapc2hs (TString "unsigned") = TString' "CUInt"
mapc2hs (TString "short") = TString' "CShort"
mapc2hs (TString "unsigned_short") = TString' "CUShort"
mapc2hs (TString "short_int") = TString' "CShort"
mapc2hs (TString "signed_short") = TString' "CShort"
mapc2hs (TString "signed_short_int") = TString' "CShort"
mapc2hs (TString "unsigned_short_int") = TString' "CUShort"
mapc2hs (TString "char") = TString' "CChar"
mapc2hs (TString "signed_char") = TString' "CSChar"
mapc2hs (TString "unsigned_char") = TString' "CUChar"
mapc2hs (TString "signed_long") = TString' "CLong"
mapc2hs (TString "long") = TString' "CLong"
mapc2hs (TString "unsigned_long") = TString' "CULong"
mapc2hs (TString "long_int") = TString' "CLong"
mapc2hs (TString "unsigned_long_int") = TString' "CULong"
mapc2hs (TString "long_long") = TString' "CLLong"
mapc2hs (TString "unsigned_long_long") = TString' "CULLong"
mapc2hs (TString "long_long_int") = TString' "CLLong" 
mapc2hs (TString "signed_long_long_int") = TString' "CLLong"
mapc2hs (TString "unsigned_long_long_int") = TString' "CULLong"
mapc2hs (TString "float") = TString' "CFloat"
mapc2hs (TString "double") = TString' "CDouble"
mapc2hs (TString "long_double") = TString' "CLDouble"
mapc2hs (TString "@@ptrdiff_t@@") = TString' "CPtrdiff"
mapc2hs (TString "@@size_t@@") = TString' "CSize"
mapc2hs (TString "@@wchar_t@@") = TString' "CWchar"
mapc2hs (TString "@@sig_atomic_t@@") = TString' "CSigAtomic"
mapc2hs (TString "@@clock_t@@") = TString' "CClock"
mapc2hs (TString "@@time_t@@") = TString' "CTime"
mapc2hs (TString "@@FILE@@") = TString' "CFile"
mapc2hs (TString "@@fpos_t@@") = TString' "CFpos"
mapc2hs (TString "@@jmp_buf@@") = TString' "CJmpBuf"
mapc2hs (TString "@@void@@") = TString' "()"
mapc2hs (TString "void") = TString' "()"
mapc2hs (TString "@@variadic@@") = TString' "WrongVariadicFunction"

-- Direct structures/unions: valid in some circumstances, but
-- require special treatment.

mapc2hs (TString ('s':'t':'r':'u':'c':'t':'_':strname)) =
  TString' ("@S_" ++ strname)

mapc2hs (TString ('u':'n':'i':'o':'n':'_':strname)) =
  TString' ("@U_" ++ strname)

-- Special pseudo-type for bit fields.

mapc2hs (TString ('$':'B':'F':'$':'_':s)) = TString' (s ++ "|" ++ ts2ts (mapc2hs (TString s)))

-- Array types: represented as pointers to the type of array element.
-- Array type string starts with a digit and contains @-sign at non-head
-- position.
-- The rest, will be converted into unknown types, and will cause
-- compilation error.

mapc2hs (TString at)
  | isarrt at = mapc2hs (TString $ snd (splitarrt at))
  | otherwise = unmapped at

-- Check whether the typestring represents an array type.

isarrt at = let firstdim = takeWhile (/= '@') at in
            (length firstdim > 0) && ("(#const" `isPrefixOf` firstdim)

-- Special version of the type map function considering conversion of arrays
-- into pointers.

mapc2hs_arr t@(TString at)
  | isarrt at = PtrV 1 (mapc2hs (TString $ snd (splitarrt at)))
  | otherwise = mapc2hs t

mapc2hs_arr z = mapc2hs z

unmapped z = TString' ("Unmapped_C_Type_" ++ z)

-- Split array type string into dimensions ([Int]) and the base type string
-- (String).

splitarrt arts = (dims,basetype)
  where notat = (/= '@')
        basetype = (drop 1 . reverse . takeWhile notat . reverse) arts
        dimtxt = (reverse . dropWhile notat . reverse) arts
        dimpts = parts (== '_') dimtxt
        dims = map bq2un dims'
        bq2un '`' = '_'
        bq2un z = z
        dims' = "[" ++ intlv (reverse $ map (readdim . filter notat) dimpts) ", " ++ "]"
        readdim "*" = "-1"
        readdim s = s
        

-- Monadify the type string. All functions must return
-- monadic (usually IO) types.

monadify m (TApply tss) = 
  TApply (reverse $ (Mnd m (monadify m (head rtss))):(map (monadify m) $ tail rtss))
    where rtss = reverse tss

monadify m (PtrF ps ts) = PtrF ps (monadify m ts)

monadify m (PtrV ps ts) = PtrV ps (monadify m ts)

monadify m z = z

-- Pointer application. First pointer uses the prefix string, the rest
-- just add Ptr to the type string. A pointer of zero depth acts as a pair
-- of parentheses. The prefix string is cleared is at least one pointer was applied.

ptrapply 0 st = st

ptrapply 1 (TCMState curts pfx) = TCMState (ptrapply' curts pfx) False where
  ptrapply' t@(PtrF ps ts)   False = PtrV 1 t
  ptrapply'   (PtrV ps ts)   False = PtrV (ps + 1) ts
  ptrapply' t@(TString ts)   False = PtrV 1 t
  ptrapply' t                True  = PtrF 1 t
  ptrapply' x y = error $ "ptrapply' " ++ show x ++ " " ++ show y

ptrapply ps st = ptrapply (ps - 1) $ clrpfx $ ptrapply 1 st where
  clrpfx (TCMState curts _) = TCMState curts False

-- Parameters application. List of declarations is converted into
-- type strings, and they are interleaved with an arrow. All this
-- is appended along with an arrow to the left of the type string.

parmsapply dcls (TCMState curts _) = 
  TCMState (tsconcat (map dcl2ts' dcls) curts) True where
    dcl2ts' dcl = state2ts $ dcl2ts dcl
    tsconcat [TString "void"] tr = TApply [tr]
    tsconcat tp tr = TApply (tp ++ [tr])

-- Applies a pointer to the current type string.

tcmtrans st (DeclaratorS ps _ DeclTypeVar) = ptrapply ps st

-- Inserts function parameters' type signatures (return type was in DeclarationS)
-- If pc is not zero, applies a pointer ps times to what is in the type string.

tcmtrans st (DeclaratorS ps _ (DeclTypeFixed dcls)) = parmsapply dcls $ ptrapply ps st

-- Variadic functions are not supported. Yet error cannot be declared here
-- as they shouldn't harm others.

tcmtrans st (DeclaratorS _ _ DeclTypeVariadic) = 
  TCMState (TApply [TString "@@variadic@@"]) True

tcmtrans st (DeclaratorS _ _ (DeclTypeUnknown t)) = 
  TCMState (TApply [TString $ "@@unknown" ++ t ++ "@@"]) True

-- Initializes a type string from a DeclarationS

dcl2state (DeclarationS ds _) = TCMState (TString (intlv ds "_")) False

-- Retrieves type string from a state.

state2ts (TCMState ts _) = ts

-- Determines whether the state represents a function.

state2isf (TCMState _ pfx) = pfx

-- Follows the chain of declarators.

dclfollow st d@(DeclaratorS _ mbds _) = 
  let st' = tcmtrans st d in
    case mbds of
      Nothing -> st'
      Just d' -> dclfollow st' d'

-- Converts a declaration into a type string.

dcl2ts d@(DeclarationS ds decl) = dclfollow (dcl2state d) decl

-- Check recursively a TypeString for a predicate. Return True if at least
-- one element of the TypeString satisfies.

checktsrec pred ta@(TApply tss) = (pred ta) || (foldl (||) False (map (checktsrec pred) tss)) 
checktsrec pred pv@(PtrV _ ts) = (pred pv) || (checktsrec pred ts)
checktsrec pred pf@(PtrF _ ts) = (pred pf) || (checktsrec pred ts)
checktsrec pred mn@(Mnd _ ts) = (pred mn) || (checktsrec pred ts)
checktsrec pred z = pred z

-- True if a type is of a variadic function.

isvariadic ts = checktsrec isvr ts where
  isvr (TString  "WrongVariadicFunction") = True
  isvr (TString' "WrongVariadicFunction") = True
  isvr _ = False

-- True if a type is of a function taking/returning direct structures

isdirstruct ts = checktsrec isd ts where
  isd (TString  ('@':_)) = True
  isd (TString' ('@':_)) = True
  isd (TString' "CJmpBuf") = True
  isd _ = False

-- Output a FFI declaration of a function or a variable.
-- There may be following kinds of things declared:
--  - variables (&-import)
--  - regular functions (static import)
--  - pointers to functions: dynamic import for declaration, wrapper import for arguments
--  - pointers to variables
--  - pointers to pointers to functions
-- These things may be declared in two ways:
--  - type/typealias id (variables, functions, pointers)
--  - rettype/retalias id args (functions, function pointers)
-- In the first case, typealias resolution is necessary to determine whether
-- a function or a variable is declared. In the second case, presence of parameters
-- in the declaration shows whether this is a function (maybe nullary) or a variable.

onefunc tymap ifn dss ats id = 
  do let cta = connectta tymap $ simplifydecl dss id
         tcm = dcl2ts cta
         isf = state2isf tcm
         isv = isvariadic tsp
         drs = isdirstruct tsp
         dyn = case (state2ts tcm) of
               PtrF _ _ -> True
               other -> False
         tsp = mapc2hs $ state2ts tcm
         tsi = monadify "IO" tsp
         tsf = ts2ts tsi
         tsg = ts2ts tsp
         sym = id2name id
         intern = case (Map.lookup ("ID " ++ sym) tymap) of
                  Just (DictId n) -> "___" ++ (show n) ++ "___"
                  other -> ""
         arity (TApply ts) = (length ts) - 1
         arity (PtrF _ t) = arity t
         arity _ = 0
     case (isv || drs,isf,dyn) of
       (False,True, False) -> do statimport ifn sym tsf
                                 cbckimport sym tsi
       (_,    True, True)  -> skipimport ifn sym
       (False,False,True)  -> do dynimport  ifn sym tsf (arity tsp) intern
                                 cbckimport sym tsi
       (False,False,False) -> varimport  ifn sym tsg True intern
       (True, _,    _)     -> excludeimport (sym ++ " :: " ++ tsg) isv drs

     putStrLn ""
     putStrLn "--"
     putStrLn ""
     return ()

-- Exclude an import and explain the reason.

excludeimport sym isv drs = do
  putStrLn   "--"
  putStrLn $ "-- import of function/variable/structure member(s) " ++ sym ++ " is not possible"
  putStrLn $ "-- because of the following reason(s):"
  when isv $ putStrLn "-- function is variadic"
  when drs $ putStrLn "-- function takes/returns structure(s) directly"
  putStrLn   "--"
                  

-- Write an import statement for a function. The `alloca' function if not really a function,
-- so import is not written for it.

statimport _ "alloca" _ = return ()

statimport ifn sym tsg = do
  putStrLn $ "foreign import ccall \"static " ++ ifn ++ " " ++ sym ++ "\""
  putStrLn $ "  f_" ++ sym ++ " :: " ++ tsg

-- For a dynamic import, 3 declarations are made: the first for the variable
-- holding a function pointer, the second for the stub factory, and the third
-- is application of the factory to the pointer variable (this one has name
-- of the imported entity.

-- For convenience, the function pointer to call will be imported
-- under the pointer variable's name, such as (for a function pointer
-- double (*pdf)(double,double)):

-- foreign import ccall "example.h &pdf" 
--   pdf' :: Ptr (FunPtr (CDouble -> CDouble -> IO CDouble))
-- foreign import ccall "dynamic"
--   mk_pdf' :: FunPtr (CDouble -> CDouble -> IO CDouble) -> (CDouble -> CDouble -> IO CDouble)
-- pdf _1 _2 = peek pdf' >>= \s -> mk_pdf' s _1 _2

unfptr ('F':'u':'n':'P':'t':'r':' ':s) = s
unfptr z = z

dynimport ifn sym tsg arity intern = do
  let csym = "x_" ++ sym
      vsym = "v_" ++ sym
      ssym = "s_" ++ sym
      msym = intern ++ "mk___"
      wsym = intern ++ "wr___"
      gsym = "peek " ++ intern
      arglist = intlv (take arity $ map (('_' :) . show) [1..]) " "
  varimport ifn sym tsg False intern
  putStrLn $ "foreign import ccall \"dynamic\""
  putStrLn $ "  " ++ msym ++ " :: " ++ tsg ++ " -> " ++ (unfptr tsg)
  putStrLn $ "foreign import ccall \"wrapper\""
  putStrLn $ "  " ++ wsym ++ " :: " ++ (unfptr tsg) ++ " -> " ++ "IO (" ++ tsg ++ ")"
  putStrLn $ csym ++ " " ++ arglist ++ " = " ++ gsym ++ " >>= \\s -> " ++ msym ++ " s " ++ arglist
  putStrLn $ vsym ++ " = " ++ gsym ++ " >>= (return . " ++ msym ++ ")"
  putStrLn $ ssym ++ " = \\s -> " ++ wsym ++ " s >>= poke " ++ intern
  return ()

-- Write a comment about ambiguously defined import entity.

skipimport ifn sym = do
  putStrLn $ "-- Import generation ERROR"
  putStrLn $ "-- Import of " ++ sym ++ " defined in " ++ ifn ++ " is skipped:"
  putStrLn $ "-- it is ambiguously declared as a dynamically imported function"
  putStrLn $ "-- rather than a variable pointing to a function."

-- Write an import statement for a variable. For convenience,
-- for each variable, a function will be defined to access variable's
-- value, such as (for an integer variable `a'):

-- Thus the pointer to the variable is imported under the name composed of
-- variable name and a prime.

varimport ifn sym tsg getset intern = do
  putStrLn $ "foreign import ccall \"" ++ ifn ++ " &" ++ sym ++ "\" "
  putStrLn $ "  " ++ intern ++ " :: Ptr (" ++ tsg ++ ")"
  putStrLn $ "p_" ++ sym ++ " = " ++ intern
  case getset of
    True -> do putStrLn $ "v_" ++ sym ++ " = peek " ++ intern
               putStrLn $ "s_" ++ sym ++ " = poke " ++ intern
    False -> return ()
  return ()

-- Recursively scan the type for funciton pointers passed as arguments
-- and write import statements to define wrappers.


findcbcks (TApply tss) = concat $ map findcbcks (reverse $ tail $ reverse tss)

findcbcks (PtrF 1 ts) = [ts2ts ts] ++ (findcbcks ts)

findcbcks z = []

cbckimport sym tsp = do
  let cbl = zip [1..] (nub $ findcbcks $ ftsg tsp)
      ftsg t@(TApply _) = t
      ftsg (PtrF _ t@(TApply _)) = t
      ftsg _ = TApply []
  mapM (wrapimport sym) cbl
  return ()

-- Write one wrapper import statement. Each wrapper gets a name
-- derived from sym and its position in the list.

wrapimport sym cb = when ((length $ snd cb) > 0) $ do
  putStrLn $ "foreign import ccall \"wrapper\""
  putStrLn $ "  " ++ "w_" ++ sym ++ "_" ++ (show $ fst cb) ++ " :: " 
                  ++ "(" ++ (snd cb) ++ ") -> IO (FunPtr (" ++ (snd cb) ++ "))"
  return ()

