{-# LANGUAGE FlexibleContexts #-}
-----------------------------------------------------------------------------
-- |
-- Module      :  ForSyDe.Backend.GraphML.Traverse.GraphMLM
-- 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 :  portable
--
-- 'GraphMLM' (GraphML Monad), related types and functions
--
-----------------------------------------------------------------------------
module ForSyDe.Backend.GraphML.Traverse.GraphMLM where

import ForSyDe.Backend.GraphML.AST

import ForSyDe.Ids
import ForSyDe.ForSyDeErr
import ForSyDe.System.SysDef (SysDefVal(..))
import ForSyDe.Netlist.Traverse (TravSEIO)

import Control.Monad.State


--------------
-- GraphMLM --
--------------

-- | GraphMLM backend monad
type GraphMLM a = TravSEIO GraphMLTravST ContextErr a


-------------------
-- GraphMLTravST --
-------------------

-- | GraphML traversing State. (see 'ForSyDe.Netlist.Traverse.traverseSIO')
data GraphMLTravST = GraphMLTravST
  {local  :: LocalGraphMLST,   -- Local State (related to the system currently 
                               -- compiled)
   global :: GlobalGraphMLST}  -- Global state (related to all systems being 
                               -- recursively compiled)   

data LocalGraphMLST = LocalGraphMLST
   {currSysDef :: SysDefVal, -- System definition which is currently 
                              -- being compiled
   context     :: Context,    -- Error Context
   localRes    :: LocalTravResult} -- Result accumulated during the 
                                   -- traversal of current System Definition 
                                   -- netlist




-- | initialize the local state
initLocalST :: SysDefVal -> LocalGraphMLST
initLocalST sysDefVal = 
 LocalGraphMLST sysDefVal (SysDefC (sid sysDefVal) (loc sysDefVal)) 
             emptyLocalTravResult

-- | Execute certain operation with a concrete local state.
--   The initial local state is restored after the operation is executed
withLocalST :: LocalGraphMLST -> GraphMLM a -> GraphMLM a
withLocalST l' action =  do
  -- get the initial local state
  st <- get
  let l = local st
  -- set the modified state
  put st{local=l'}
  -- execute the action
  res <- action
  -- restore the initial local state
  st' <- get 
  put st'{local=l}
  -- return the result
  return res

data GlobalGraphMLST = GlobalGraphMLST
  {globalSysDef :: SysDefVal,
   ops          :: GraphMLOps,   -- Compilation options
   globalRes    :: GlobalTravResult} -- Result accumulated during the 
                                     -- whole compilation
    



-- | Empty initial traversing state
initGlobalGraphMLST :: SysDefVal -> GlobalGraphMLST
initGlobalGraphMLST  sysDefVal = 
  GlobalGraphMLST sysDefVal defaultGraphMLOps emptyGlobalTravResult

-- | Empty initial traversing state 
initGraphMLTravST :: SysDefVal -> GraphMLTravST
initGraphMLTravST sysDefVal = 
  GraphMLTravST (initLocalST sysDefVal) (initGlobalGraphMLST sysDefVal)

-------------
-- TravResult
-------------

-- | Local result accumulated during the traversal of a netlist
data LocalTravResult = LocalTravResult 
  {nodes  :: [GraphMLNode], -- generated nodes
   edges  :: [GraphMLEdge]} -- generated edges



-- | empty local GraphML compilation result
emptyLocalTravResult :: LocalTravResult
emptyLocalTravResult = LocalTravResult [] []


-- | Global Results accumulated throughout the whole compilation
--   (empty right now)
type GlobalTravResult = ()


-- | empty global GraphML compilation result
emptyGlobalTravResult :: GlobalTravResult
emptyGlobalTravResult = ()


-------------
-- GraphMLOps
-------------

-- | GraphML Compilation options
data GraphMLOps = GraphMLOps 
   {debugGraphML :: GraphMLDebugLevel, 
    recursivityGraphML :: GraphMLRecursivity,
    yFilesMarkup :: Bool -- ^ Generate yFiles markup? 
                        }
 deriving (Eq, Show)

-- | Debug level
data GraphMLDebugLevel = GraphMLNormal | GraphMLVerbose
 deriving (Eq, Ord, Show)

-- | Print a message to stdout if in verbose mode
debugMsg :: String -> GraphMLM ()
debugMsg str = do
 debugLevel <- gets (debugGraphML.ops.global)
 when (debugLevel > GraphMLNormal) 
      (liftIO $ putStr ("DEBUG: " ++ str))

-- | Recursivity, should the parent systems of system instances be compiled as 
--   well?
data GraphMLRecursivity = GraphMLRecursive | GraphMLNonRecursive
 deriving (Eq, Show)

-- | Check if we are in recursive mode
isRecursiveSet :: GraphMLM Bool
isRecursiveSet = do 
  recOp <- gets (recursivityGraphML.ops.global)
  return $ recOp == GraphMLRecursive

-- | Check if we want to generate yFiles markup
genyFilesMarkup :: GraphMLM Bool
genyFilesMarkup = gets (yFilesMarkup.ops.global)


-- | Default traversing options
defaultGraphMLOps :: GraphMLOps
defaultGraphMLOps =  GraphMLOps GraphMLNormal GraphMLRecursive False


-- | Set GraphML options inside the GraphML monad
setGraphMLOps :: GraphMLOps -> GraphMLM ()
setGraphMLOps options =  modify (\st -> st{global=(global st){ops=options}})


----------------------------------------
-- Useful functions in the GraphML Monad
----------------------------------------

-- | Add a signal declaration to the 'LocalTravResult' in the State
addEdge :: GraphMLEdge -> GraphMLM ()
addEdge e = modify addFun 
 -- FIXME: use a queue for the declarations
  where addFun st = st{local=l{localRes=r{edges=edg ++ [e]}}}
         where l   = local st
               r   = localRes l
               edg = edges r 


-- | Add a statement to the 'LocalTravResult' in the State
addNode :: GraphMLNode -> GraphMLM ()
addNode node = modify addFun
 -- FIXME: use a queue for the statements
  where addFun st = st{local=l{localRes=r{nodes= nds ++ [node]}}}
         where l  = local st
               r  = localRes l
               nds = nodes r 



-- | Lift an 'EProne' value to the GraphML monad setting current error context
--   for the error
liftEProne :: EProne a -> GraphMLM a
liftEProne ep = do
 cxt <- gets (context.local)
 either (throwError.(ContextErr cxt)) return ep

-- | Throw a ForSyDe error, setting current error context
throwFError :: ForSyDeErr -> GraphMLM a
throwFError = liftEProne.Left



-- | Execute certain operation with a concrete process context.
--   The initial context is restored after the operation is executed
--   Note: the initial context must be a system context or 'InconsistenContexts'
--         will be raised.
withProcC :: ProcId -> GraphMLM a -> GraphMLM a
withProcC pid action = do
  -- get the initial context
  st <- get
  let l = local st
      c = context l
  -- set the modified name context
  put st{local=l{context=setProcC pid c}}
  -- execute the action
  res <- action
  -- restore the initial name context
  st' <- get
  let l' = local st'
  put st'{local=l'{context=c}}
  -- return the result
  return res




----------------
-- IntSignalInfo
----------------

-- | Intermediate edge information. Tag generated for each output of each
--   node found during the traversal. 
-- (see ForSyDe.Netlist.Traverse.traverseSIO).
--   It contains the GraphML node identifier 
--   and port identifier associated with the process output.
data IntSignalInfo = IntSignalInfo GraphMLNode   -- Source Node 
                                   GraphMLPortId -- Source Port Identifier