{-# LANGUAGE TemplateHaskell #-}
-----------------------------------------------------------------------------
-- |
-- Module      :  ForSyDe.Process.ProcType
-- Copyright   :  (c) SAM Group, KTH/ICT/ECS 2008
-- License     :  BSD-style (see the file LICENSE)
-- 
-- Maintainer  :  forsyde-dev@ict.kth.se
-- Stability   :  experimental
-- Portability :  non-portable (non-standard instances)
--
-- This module includes and exports, the internal definition, instantiations
-- and related types of 'ProcType', a class used to constrain the arguments
-- taken by process constructors.
----------------------------------------------------------------------------- 
module ForSyDe.Process.ProcType (
 EnumAlgTy(..), 
 ProcType(..), 
 genTupInstances) where

import Control.Monad (replicateM)
import Data.List (intersperse)
import Data.Generics
import Data.Set (Set, union)
import Language.Haskell.TH
import Language.Haskell.TH.Syntax (Lift(..))
import Text.ParserCombinators.ReadP

-- | Data type describing an algebraic enumerated type (i.e. an algrebraic 
--   type whose data constructors have arity zero)
data EnumAlgTy = EnumAlgTy String [String] 
 deriving Show

instance Eq EnumAlgTy where
 (EnumAlgTy d1 _) == (EnumAlgTy d2 _) = d1 == d2

instance Ord EnumAlgTy where
 (EnumAlgTy d1 _) `compare` (EnumAlgTy d2 _) = d1 `compare` d2

-- | Class used to constrain the arguments (values and 'ProcFun's) taken by
--   process constructors
class (Data a, Lift a) => ProcType a where
 -- | Get the associated enumerated type-definitions of certain value, 
 --   taking nesting in account.  
 -- 
 --   For example:  
 --
 -- 
 -- >  module MyMod where
 -- >
 -- >  data Colour = Blue | Red
 -- >   deriving (Data, Typeable)
 -- >  data Shapes = Circle | Square
 -- >   deriving (Data, Typeable)
 -- >
 -- >  getEnums (Prst Blue, Circle) =                 
 -- >   fromList [EnumAlgTy "MyMod.Colour" ["Blue", "Red"],
 -- >             EnumAlgTy "MyMod.Shapes" ["Circle", "Square"]]
 getEnums :: a -> Set EnumAlgTy
 -- | Read a process type
 readProcType :: ReadP a

-- Function to automatically generate ProcType, Data, Lift and
-- Typeable instances for tuples (with 2 or more elements) with
-- Template Haskell. For example, in the case of 2 elements, the code
-- generated would be:
--
-- @
-- instance (ProcType o1, ProcType o2) => ProcType (o1, o2) where
--  getEnums _ = getEnums (undefined :: a) `union` getEnums (undefined :: b) 
--  readProcType = do
--            skipSpaces >> char '('
--            o1 <- readProcType
--            skipSpaces >> char ','
--            o2 <- readProcType
--            skipSpaces >> char ')'
--            return (o1,o2)
--
-- The next two are only necessary for tuples with more than 7 elements
--
-- instance (Typeable o1, Typeable o2) => Typeable (o1, o2) where
--  typeOf _ = mkTyCon "," `mkTyConApp` 
--               [typeOf (undefined :: o1), typeOf (undefined :: o2)]
--
-- instance (Data o1, Data o2) => Data (o1, o2) where
--  gfoldl k z (o1, o2) = z (,) `k` o1 `k` o2
--  gunfold k z _ = k (k (z (,) ))
--  toConstr a = mkConstr (dataTypeOf a) "(,)" [] Prefix
--  dataTypeOf a = mkDataType "Data.Tuple.(,)" [toConstr a]
--
-- FIXME: This won't be necessary once the Data a => Lift a instance is created
--
-- instance (Lift o1, Lift o2) => Lift (o1, o2) where
--  lift (o1, o2) = tupE [lift o1, lift o2]
-- @
genTupInstances :: Int -- ^ number of outputs to generate
             -> Q [Dec]
genTupInstances n = do
  -- Generate N o names
  outNames <- replicateM n (newName "o")
  let tupType = foldl accumApp (tupleT n) outNames
      accumApp accumT vName = accumT `appT` varT vName 
  if n <= 7 
     then sequence [genProcTypeIns outNames tupType]
     else sequence [genTypeableIns outNames tupType,
                    genDataIns outNames tupType]
                    -- genLiftIns outNames tupType,
                    --genProcTypeIns outNames tupType]

 where 
  undef t = sigE [| undefined |] (varT t)
  genProcTypeIns :: [Name] -> Q Type -> Q Dec
  genProcTypeIns names tupType = do
    let getEnumsExpr =  
            foldr1 (\e1 e2 -> infixE (Just e1) 
                                     (varE 'union)
                                     (Just e2) )
                   (map (\n -> varE  'getEnums `appE` undef n) names)     
        getEnumsD = funD 'getEnums [clause [wildP]  (normalB getEnumsExpr) []] 
        readProcTypeExpr = doE $ 
            noBindS [| skipSpaces >> char '(' |] : 
            (intersperse (noBindS [| skipSpaces >> char ',' |]) 
                        (map (\n -> bindS (varP n) [| readProcType |]) names) ++
             [noBindS [| skipSpaces >> char ')' |],
              noBindS [| return $(tupE $ map varE names) |] ] )
        readProcTypeD = funD 'readProcType 
                             [clause []  (normalB readProcTypeExpr) []]
        procTypeCxt = map (\vName -> conT ''ProcType `appT` varT vName) names ++
                      map (\vName -> conT ''Data `appT` varT vName) names ++
                      map (\vName -> conT ''Lift `appT` varT vName) names
    instanceD (cxt procTypeCxt) 
                     (conT ''ProcType `appT` tupType) 
                     [getEnumsD, readProcTypeD]
  genDataIns :: [Name] -> Q Type -> Q Dec
  genDataIns names tupType = do
   k <- newName "k"
   c <- newName "c"
   z <- newName "z"
   a <- newName "a"
   let tupCons = conE tupName
       tupName = tupleDataName n
       gfoldlExpr = foldl (\acum n -> infixE (Just acum)
                                             (varE k)
                                             (Just $ varE n))
                           (varE z`appE` tupCons) 
                           names                    
       gfoldlD = funD 'gfoldl 
                       [clause [varP k, varP z, tupP (map varP names)] 
                               (normalB gfoldlExpr) []] 
       gunfoldExpr = let nKs 0 = (varE z `appE` tupCons)
                         nKs n = varE k `appE` (nKs (n-1))
                     in nKs n
       gunfoldD = funD 'gunfold 
                      [clause [varP k, varP z, wildP] (normalB gunfoldExpr) []] 
       toConstrExpr = [| mkConstr (dataTypeOf $(varE a))
                                  $(litE $ stringL (nameBase tupName))
                                  [] 
                                  Prefix  |]
       toConstrD = funD 'toConstr
                        [clause [varP a] (normalB toConstrExpr) []]
       dataTypeOfExpr = [| mkDataType $(litE $ stringL (show tupName)) 
                                      [toConstr $(varE a)] |] 
       dataTypeOfD = funD 'dataTypeOf
                          [clause [varP a] (normalB dataTypeOfExpr) []]
       dataCxt = map (\vName -> conT ''Data `appT` varT vName) names 
   instanceD (cxt dataCxt) 
             (conT ''Data `appT` tupType) 
             [gfoldlD, gunfoldD, toConstrD, dataTypeOfD]
  genTypeableIns :: [Name] -> Q Type -> Q Dec
  genTypeableIns names tupType = do
   -- generate n-1 commas to be consistent with the (faulty) instances
   -- of tuples from 2 to 7 elements
   let strRep = replicate (n-1) ','
       typeOfExpr = [| mkTyCon 
                        $(litE $ stringL strRep)
                        `mkTyConApp`
                        $(listE $ map (\n -> varE 'typeOf `appE` undef n) names)
                     |]
       typeOfD = funD 'typeOf
                      [clause [wildP] (normalB typeOfExpr) []]
       typeableCxt = map (\vName -> conT ''Typeable `appT` varT vName) names
   instanceD (cxt typeableCxt) 
             (conT ''Typeable `appT` tupType) 
             [typeOfD]
  genLiftIns :: [Name] -> Q Type -> Q Dec
  genLiftIns names tupType = do
   let liftExpr = 
           varE 'tupE `appE` listE (map (\n -> varE 'lift `appE` varE n) names)
       liftD = funD 'lift 
                 [clause [tupP (map varP names)] (normalB liftExpr) []]
       liftCxt = map (\vName -> conT ''Lift `appT` varT vName) names
   instanceD (cxt liftCxt) 
             (conT ''Lift `appT` tupType) 
             [liftD]