{-# LANGUAGE TemplateHaskell #-} 
-----------------------------------------------------------------------------
-- |
-- Module      :  ForSyDe.Process.ProcFun
-- Copyright   :  (c) SAM Group, KTH/ICT/ECS 2007-2008
-- License     :  BSD-style (see the file LICENSE)
-- 
-- Maintainer  :  forsyde-dev@ict.kth.se
-- Stability   :  experimental
-- Portability :  non-portable (Template Haskell)
--
-- This module provides a type ('ProcFun') to store the functions  passed
-- to process constructors.
--
----------------------------------------------------------------------------- 
module ForSyDe.Process.ProcFun 
 (ProcFun(..),
  ProcFunAST(..),
  newProcFun, 
  defArgVal,
  defArgPF,
  TypedProcFun(..),
  TypedProcFunAST(..),
  procFun2Dyn,
  contProcFun2Dyn,
) where

import ForSyDe.Process.ProcType
import ForSyDe.Process.ProcVal (ProcValAST, mkProcValAST)
import ForSyDe.ForSyDeErr

import Language.Haskell.TH
import Language.Haskell.TH.Syntax
import Language.Haskell.TH.LiftInstances ()
import Data.Dynamic
import Data.Maybe (fromJust)
import Data.Set (Set)
-----------
-- ProcFun
-----------

-- | A Process Function 
data ProcFun a = 
   ProcFun {val  :: a,          -- ^ Value of the function
            pfloc :: Loc, -- ^ where was it created
            ast  :: ProcFunAST} -- ^ AST of the function



-- | A process Function AST
data ProcFunAST = 
  ProcFunAST {name  :: Name,     -- ^ Function Name 
                                    -- (FIXME: maybe just a String?) 
              cls   :: [Clause], -- ^ Function clauses 
              pars  :: [DefArg]} -- ^ Default parameters


-- | A process Function default argument
--   Either a process function AST or a value AST
data DefArg = FunAST ProcFunAST | ValAST ProcValAST



-- | Sets a default value for an argument of the process function
defArgVal :: (Lift a, ProcType a) => ProcFun (a -> b) -> a -> ProcFun b
-- FIXME: inneficient, use a queue data structure
defArgVal pf v = 
   pf{ ast = astPF {pars = ((pars astPF) ++ [ValAST (mkProcValAST v)])}, 
       val = (val pf) v}                    
  where astPF = ast pf

-- | Sets a default value for an argument of the process function 
--   when the argument is a process function itself
defArgPF :: ProcFun (a -> b) -> ProcFun a -> ProcFun b
defArgPF pf v = pf{ ast = astPF {pars = ((pars astPF) ++ [FunAST (ast v)])}, 
                    val = (val pf) (val v)}                    
  where astPF = ast pf 
                   
-- | Template Haskell constructor for 'ProcFun', here is an example on how to use it
--
-- @
--  plus1Fun :: ProcFun (Int -> Int)
--  plus1Fun = $(newProcFun [d| plus1 :: Int -> Int
--                              plus1 n = n + 1     |])
-- @
newProcFun :: Q [Dec] -> ExpQ
newProcFun fDecQs = do 
      fDecs <- fDecQs   
      -- Check for the declarations to be correct
      (name, cls) <- recover (currErr $ IncorrProcFunDecs fDecs) 
                             (checkDecs fDecs)
      -- Generate the main expression
      errInfo <- qCurrentModule
      exp <-  [| let  fName    = name
                      fClauses = cls 
                 in ProcFun $(varE name)
                            errInfo 
                            (ProcFunAST fName fClauses []) |]
      -- Add the function declarations to the expression
      return $ LetE fDecs exp  
 where currErr = qError "newProcFun"

----------------
-- TypedProcFun
----------------


-- | A ProcFun bundled with its type representation. This type is not
--   exported to the end user. Only used internally.
data TypedProcFun a =    
   TypedProcFun {tval   :: a,          -- ^ Value of the function
                 tpfloc :: Loc,
                 tast   :: TypedProcFunAST} -- ^ AST of the function


-- | A ProcFunAST bundled with its type representation:
--   Why a TypeRep and not the Type provided by Template Haskell?
--    We could use the type signature provided by TH but ...
--     1) We don't want to force the user to provide a signature
--     2) We don't want to handle polymorphic types. We just want the
--        monomorphic type used by the process using the procfun.
--   Why not just including the 'TypeRep' in ProcFunAST?
--     We need the context of the process to know what monomorphic types are 
--     going to be used. Thus it is imposible to guess the TypeRep within
--     the code of newProcFun.
data TypedProcFunAST = 
     TypedProcFunAST {tptyp   :: TypeRep,        -- function type
                      tpEnums :: Set EnumAlgTy,  -- enumerated types associated 
                                                 -- with the function
                      tpast   :: ProcFunAST}

-- | transform a ProcFun into a Dynamic TypedProcFun
procFun2Dyn :: Typeable a => Set EnumAlgTy -> ProcFun a -> TypedProcFun Dynamic
procFun2Dyn s (ProcFun v l a) = 
  TypedProcFun (toDyn v) l (TypedProcFunAST (typeOf v) s a)

-- FIXME: probably not needed
-- | tranform the arguments and return value of
--   a ProcFun to dynamic
contProcFun2Dyn :: (Typeable1 container,
                    Typeable b,
                    Functor container, 
                    Typeable a) =>
                   Set EnumAlgTy ->
                   ProcFun (container a -> b) -> 
                   TypedProcFun (container Dynamic -> Dynamic)
contProcFun2Dyn s (ProcFun v l a) = 
     TypedProcFun (fmapDyn v) l (TypedProcFunAST (typeOf v) s a)
       where  fmapDyn f cont = toDyn (f (fmap (fromJust.fromDynamic) cont)) 


----------------------------
-- Internal Helper Functions
----------------------------

-- | Check the decarations passed to newProcFun to be correct
checkDecs :: [Dec] -> Q (Name, [Clause])
checkDecs [FunD name2 cls] = return (name2, cls) 
-- in case a signature is provided
checkDecs [SigD name1 _, FunD name2 cls] | name1 == name2 = 
  return (name1, cls) 
checkDecs _                  = qGiveUp name
  where name = "ForSyDe.Process.ProcFun.checkDecs"