-- | Process a collection of declarations all the way.
module Language.Lustre.Driver where

import Data.Map (Map)
import qualified Data.Map as Map
import qualified Data.Set as Set
import Data.List(foldl',sortBy)
import Data.Text(Text)
import AlexTools(sourceFrom,sourceIndex)
import Text.PrettyPrint(Doc)

import Language.Lustre.Name
import qualified Language.Lustre.AST as P
import qualified Language.Lustre.Core as C
import Language.Lustre.Monad
import Language.Lustre.Pretty(pp,vcatSep)
import Language.Lustre.Phase
import Language.Lustre.Transform.OrderDecls
import Language.Lustre.TypeCheck
import Language.Lustre.Transform.NoStatic
import Language.Lustre.Transform.NoStruct
import Language.Lustre.Transform.Inline
import Language.Lustre.Transform.ToCore


-- | Export a given node to core.
-- Note that currently we process all declarations, even if some
-- of them are not needed to process the given module.
quickNodeToCore ::
  Maybe Text    {- ^ Node to translate -} ->
  [P.TopDecl]   {- ^ Source decls -} ->
  LustreM (ModelInfo, C.Node)
quickNodeToCore mb ds =
  do (info,ds1) <- quickDeclsSimp ds
     nodeToCore mb info ds1

data Env = Env
  { envNodes :: Map OrigName ModelFunInfo
  , envEnums :: EnumInfo
  }

-- | Process a bunch of declarations in preparation for translating to core.
-- This function works only on standalone declarations, not accounting
-- for a broader context.
quickDeclsSimp :: [P.TopDecl] ->
                  LustreM (Env, [P.TopDecl])
quickDeclsSimp ds =
  do ds1 <- quickOrderTopDecl ds
     let enums = getEnumInfo ds1

     dumpPhase PhaseRename $ vcatSep $ map pp ds1
     ds2 <- quickCheckDecls ds1
     dumpPhase PhaseTypecheck $ vcatSep $ map pp ds2

     (csMap,ds3) <- noConst ds2
     dumpPhase PhaseNoStatic $ vcatSep $ map pp ds3

     let nosIn = NosIn
                   { nosiStructs   = Map.empty
                   , nosiCallSites = csMap
                   }
     (nosOut,ds4) <- noStruct nosIn ds3
     dumpPhase PhaseNoStruct $ vcatSep $ map pp ds4

     (rens,ds5)   <- inlineCalls [] ds4
     dumpPhase PhaseInline $ vcatSep $ map pp ds5

     pure (Env { envNodes = mfiMap ds1 nosOut rens
               , envEnums = enums
               }
          , ds5)

dumpPhase :: LustrePhase -> Doc -> LustreM ()
dumpPhase ph doc =
  lustreIfDumpAfter ph $
     do let msg = show ph
        logMessage msg
        logMessage (replicate (length msg) '=')
        logMessage ""
        logMessage (show doc)
        logMessage ""

nodeToCore ::
  Maybe Text {- ^ Node to translate -} ->
  Env        {- ^ Info about the environment -} ->
  [P.TopDecl]                 {- ^ Simplified top decls -} ->
  LustreM (ModelInfo, C.Node)
nodeToCore mb env ds =
  do nd  <- findNode mb ds
     core <- evalNodeDecl (envEnums env) nd
     dumpPhase PhaseToCore (pp core)
     pure (ModelInfo { infoNodes = envNodes env
                     , infoTop   = identOrigName (P.nodeName nd)
                     , infoEnums = envEnums env
                     }
          , core)


findNode ::
  Maybe Text  {- ^ Name hint -} ->
  [P.TopDecl] {- ^ Simplified declarations -} ->
  LustreM P.NodeDecl
findNode mb ds =
  case [ nd | nd <- nodes, selected nd ] of
    [nd] -> pure nd
    [] | nd : _ <- sortBy later nodes -> pure nd
    nds  -> reportError $ BadEntryPoint
                                [ identOrigName (P.nodeName nd) | nd <- nds ]
  where
  nodes = [ nd | P.DeclareNode nd <- ds ]

  selected =
    case mb of
      Nothing -> hasMain
      Just t  -> \nd -> identText (P.nodeName nd) == t

  hasMain nd
     | Just b <- P.nodeDef nd = any isMain (P.nodeEqns b)
     | otherwise              = False


  isMain eqn = case eqn of
                 P.IsMain _ -> True
                 _          -> False

  -- XXX: assumes all declaration in the same file.
  locId = sourceIndex . sourceFrom . identRange . P.nodeName
  later x y = compare (locId y) (locId x)

--------------------------------------------------------------------------------
  | Information for mapping traces back to source 
data ModelInfo = ModelInfo
  { infoNodes   :: Map OrigName ModelFunInfo
    -- ^ Translation information for nodes.

  , infoTop     :: OrigName
    -- ^ Name for top node

  , infoEnums   :: !EnumInfo
    -- ^ Information about enums
  }



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



-- | Collected information about a translated node.
  Mostly stuff we need to map from Core models , back to original source .
data ModelFunInfo = ModelFunInfo
  { mfiCallSites :: Map CallSiteId [OrigName]
    {- ^ For each call site, rememebr the identifiers keeping the results
         of the call. -}

  , mfiStructs :: Map OrigName (StructData OrigName)
    {- ^ Identifiers of strucutred types (e.g., structs, arrays) are
         "exploded" into multiple variables.  This mapping remembers how
         we did that: the key is a value of a strucutred type, and
         the entry in the map is the value for it -}

  , mfiInlined :: Map [OrigName] (OrigName, Renaming)
    {- ^ Information about what we called, and how things got renamed
         when we inlined things.
         For each call site (identified by its return values),
         we have a map from the original names in the function, to the
         new names used in the inlined version. -}

  , mfiSource :: P.NodeDecl
    -- ^ The renamed, but otherwise unsimplified code for the node
    -- that implemnets this function.  See 'nodeSourceMap' for details.
  }



mfiMap :: [P.TopDecl] -> NosOut -> AllRenamings -> Map OrigName ModelFunInfo
mfiMap ordDs nos rens =
  Map.fromList $ map build
               $ Set.toList
               $ Set.unions [ Map.keysSet (nosoCallSites nos)
                            , Map.keysSet (nosoExpanded nos)
                            , Map.keysSet rens ]
  where
  build k = (k, ModelFunInfo { mfiCallSites = lkpMap k (nosoCallSites nos)
                             , mfiStructs   = lkpMap k (nosoExpanded nos)
                             , mfiInlined   = lkpMap k rens
                             , mfiSource    = srcMap Map.! k
                             })

  lkpMap = Map.findWithDefault Map.empty

  srcMap = nodeSourceMap ordDs



-- | Compute a mapping from node names to the actual source that implements
  them .   For example , consider the declaration @f = g < < 3>>@.   If we want to
  see how  works , we should really look for the code for @g@.
nodeSourceMap :: [P.TopDecl] -> Map OrigName P.NodeDecl
nodeSourceMap = foldl' add Map.empty
  where
  add mp tde =
    case tde of
      P.DeclareNode nd -> Map.insert (identOrigName (P.nodeName nd)) nd mp
      P.DeclareNodeInst nid ->
        case P.nodeInstDef nid of
          P.NodeInst (P.CallUser f) _
            | Just nd <- Map.lookup (nameOrigName f) mp ->
                         Map.insert (identOrigName (P.nodeInstName nid)) nd mp
          _ -> mp
      P.DeclareType {} -> mp
      P.DeclareConst {} -> mp



 | Process a collection of declarations all the way.
 | Export a given node to core.
 Note that currently we process all declarations, even if some
 of them are not needed to process the given module.
 ^ Node to translate 
 ^ Source decls 
 | Process a bunch of declarations in preparation for translating to core.
 This function works only on standalone declarations, not accounting
 for a broader context.
 ^ Node to translate 
 ^ Info about the environment 
 ^ Simplified top decls 
 ^ Name hint 
 ^ Simplified declarations 
 XXX: assumes all declaration in the same file.
------------------------------------------------------------------------------
 ^ Translation information for nodes.
 ^ Name for top node
 ^ Information about enums
------------------------------------------------------------------------------
 | Collected information about a translated node.
 ^ For each call site, rememebr the identifiers keeping the results
         of the call. 
 ^ Identifiers of strucutred types (e.g., structs, arrays) are
         "exploded" into multiple variables.  This mapping remembers how
         we did that: the key is a value of a strucutred type, and
         the entry in the map is the value for it 
 ^ Information about what we called, and how things got renamed
         when we inlined things.
         For each call site (identified by its return values),
         we have a map from the original names in the function, to the
         new names used in the inlined version. 
 ^ The renamed, but otherwise unsimplified code for the node
 that implemnets this function.  See 'nodeSourceMap' for details.
 | Compute a mapping from node names to the actual source that implements
module Language.Lustre.Driver where

import Data.Map (Map)
import qualified Data.Map as Map
import qualified Data.Set as Set
import Data.List(foldl',sortBy)
import Data.Text(Text)
import AlexTools(sourceFrom,sourceIndex)
import Text.PrettyPrint(Doc)

import Language.Lustre.Name
import qualified Language.Lustre.AST as P
import qualified Language.Lustre.Core as C
import Language.Lustre.Monad
import Language.Lustre.Pretty(pp,vcatSep)
import Language.Lustre.Phase
import Language.Lustre.Transform.OrderDecls
import Language.Lustre.TypeCheck
import Language.Lustre.Transform.NoStatic
import Language.Lustre.Transform.NoStruct
import Language.Lustre.Transform.Inline
import Language.Lustre.Transform.ToCore


quickNodeToCore ::
  LustreM (ModelInfo, C.Node)
quickNodeToCore mb ds =
  do (info,ds1) <- quickDeclsSimp ds
     nodeToCore mb info ds1

data Env = Env
  { envNodes :: Map OrigName ModelFunInfo
  , envEnums :: EnumInfo
  }

quickDeclsSimp :: [P.TopDecl] ->
                  LustreM (Env, [P.TopDecl])
quickDeclsSimp ds =
  do ds1 <- quickOrderTopDecl ds
     let enums = getEnumInfo ds1

     dumpPhase PhaseRename $ vcatSep $ map pp ds1
     ds2 <- quickCheckDecls ds1
     dumpPhase PhaseTypecheck $ vcatSep $ map pp ds2

     (csMap,ds3) <- noConst ds2
     dumpPhase PhaseNoStatic $ vcatSep $ map pp ds3

     let nosIn = NosIn
                   { nosiStructs   = Map.empty
                   , nosiCallSites = csMap
                   }
     (nosOut,ds4) <- noStruct nosIn ds3
     dumpPhase PhaseNoStruct $ vcatSep $ map pp ds4

     (rens,ds5)   <- inlineCalls [] ds4
     dumpPhase PhaseInline $ vcatSep $ map pp ds5

     pure (Env { envNodes = mfiMap ds1 nosOut rens
               , envEnums = enums
               }
          , ds5)

dumpPhase :: LustrePhase -> Doc -> LustreM ()
dumpPhase ph doc =
  lustreIfDumpAfter ph $
     do let msg = show ph
        logMessage msg
        logMessage (replicate (length msg) '=')
        logMessage ""
        logMessage (show doc)
        logMessage ""

nodeToCore ::
  LustreM (ModelInfo, C.Node)
nodeToCore mb env ds =
  do nd  <- findNode mb ds
     core <- evalNodeDecl (envEnums env) nd
     dumpPhase PhaseToCore (pp core)
     pure (ModelInfo { infoNodes = envNodes env
                     , infoTop   = identOrigName (P.nodeName nd)
                     , infoEnums = envEnums env
                     }
          , core)


findNode ::
  LustreM P.NodeDecl
findNode mb ds =
  case [ nd | nd <- nodes, selected nd ] of
    [nd] -> pure nd
    [] | nd : _ <- sortBy later nodes -> pure nd
    nds  -> reportError $ BadEntryPoint
                                [ identOrigName (P.nodeName nd) | nd <- nds ]
  where
  nodes = [ nd | P.DeclareNode nd <- ds ]

  selected =
    case mb of
      Nothing -> hasMain
      Just t  -> \nd -> identText (P.nodeName nd) == t

  hasMain nd
     | Just b <- P.nodeDef nd = any isMain (P.nodeEqns b)
     | otherwise              = False


  isMain eqn = case eqn of
                 P.IsMain _ -> True
                 _          -> False

  locId = sourceIndex . sourceFrom . identRange . P.nodeName
  later x y = compare (locId y) (locId x)

  | Information for mapping traces back to source 
data ModelInfo = ModelInfo
  { infoNodes   :: Map OrigName ModelFunInfo

  , infoTop     :: OrigName

  , infoEnums   :: !EnumInfo
  }






  Mostly stuff we need to map from Core models , back to original source .
data ModelFunInfo = ModelFunInfo
  { mfiCallSites :: Map CallSiteId [OrigName]

  , mfiStructs :: Map OrigName (StructData OrigName)

  , mfiInlined :: Map [OrigName] (OrigName, Renaming)

  , mfiSource :: P.NodeDecl
  }



mfiMap :: [P.TopDecl] -> NosOut -> AllRenamings -> Map OrigName ModelFunInfo
mfiMap ordDs nos rens =
  Map.fromList $ map build
               $ Set.toList
               $ Set.unions [ Map.keysSet (nosoCallSites nos)
                            , Map.keysSet (nosoExpanded nos)
                            , Map.keysSet rens ]
  where
  build k = (k, ModelFunInfo { mfiCallSites = lkpMap k (nosoCallSites nos)
                             , mfiStructs   = lkpMap k (nosoExpanded nos)
                             , mfiInlined   = lkpMap k rens
                             , mfiSource    = srcMap Map.! k
                             })

  lkpMap = Map.findWithDefault Map.empty

  srcMap = nodeSourceMap ordDs



  them .   For example , consider the declaration @f = g < < 3>>@.   If we want to
  see how  works , we should really look for the code for @g@.
nodeSourceMap :: [P.TopDecl] -> Map OrigName P.NodeDecl
nodeSourceMap = foldl' add Map.empty
  where
  add mp tde =
    case tde of
      P.DeclareNode nd -> Map.insert (identOrigName (P.nodeName nd)) nd mp
      P.DeclareNodeInst nid ->
        case P.nodeInstDef nid of
          P.NodeInst (P.CallUser f) _
            | Just nd <- Map.lookup (nameOrigName f) mp ->
                         Map.insert (identOrigName (P.nodeInstName nid)) nd mp
          _ -> mp
      P.DeclareType {} -> mp
      P.DeclareConst {} -> mp


