content: module VFinalCleanup (finalCleanup) where
  
  import Data.List(nub)
  import qualified Data.Map as M
  import qualified Data.Set as S
  import Position(noPosition)
  import Flags(Flags, keepFires, removeUnusedMods, finalcleanup, keepInlined)
  import Id
  import FStringCompat(mkFString)
  import ASyntax
  import ASyntaxUtil
  import BackendNamingConventions(createVerilogNameMapForAVInst)
  
  --import Debug.Trace
  -- import Util(traces)
  
  
  -- ==============================
  
  -- Build a state  containing:
  --   * the names of the visited instances (for quick lookup)
  --   * a map from local signal names to their definitions
  --   * the instances in the module
  --   * the instances that have been reached (and are thus connected)
  
  -- Note: We make the assumption that all ports of a state element or
  -- sub module are connected.  If we enter a submodule port, we add
  -- all the submodule's ports to the connected list.
  
  data ConnState = ConnState {
                       visited_wires :: S.Set AId,  -- marking for visited nodes
                       visited_insts :: S.Set AId,
                       defs          :: M.Map AId ADef,
                       instances     :: M.Map AId AVInst,  -- instances from the package
                       flags         :: Flags
                   }
  
  -- ==============================
  
  -- Final cleanup of the ASyntax before Verilog generation.
  --  * removes unused state module if flag is set
  --  * removes unused variables introduced during veri quirks
  finalCleanup :: Flags -> ASPackage -> ASPackage
  finalCleanup flags pin = (unusedm) pin
      where
        unusedm = if (cleanup) then removeUnusedInsts flags else id
        -- only do this phase if the LSB of the flag is 1
        -- (this flag may be extended to have other functions for other bits)
        cleanup = odd $ finalcleanup flags
  
  
  -- Returns the package with only the wires and instances which are
  -- connected to an output.  (Optionally keeps all the instances, and
  -- their port connection, if the removeUnusedMods flag is off.)
  --   Note: This assumes that all foreign function calls are connected.
  --   Typically synthesizable modules will not have any foreign functions.
  --   Note: Removed instances also need to have their ports removed from
  --   the state output list in the ASPackage.  XXX hack used to do this
  removeUnusedInsts :: Flags -> ASPackage -> ASPackage
  removeUnusedInsts flags package =
      package { aspkg_state_instances = ss'',
                aspkg_state_outputs = sos',
                aspkg_values = ds' }
      where
            keepInlinedMods = keepInlined flags
            ds = aspkg_values package
            ss = aspkg_state_instances package
            sos = aspkg_state_outputs package
  
            os = aspkg_outputs package
            ios = aspkg_inouts package
            fs = aspkg_foreign_calls package
  
            (cdefs,cinsts) = -- traces("conn outputs is: " ++ ppReadable markedConn) $
                             -- traces("conn package: " ++ ppReadable package ) $
                             connectedNode flags markedConn ss ds
            ds' = filter isDefUsed ds
            isDefUsed :: ADef -> Bool
            isDefUsed def@(ADef i _ _ _) = S.member (adef_objid def) cdefs
            ss'' = if (removeUnusedMods flags)
                   then filter isModuleUsed ss
                   else ss
            isModuleUsed :: AVInst -> Bool
            isModuleUsed inst = S.member (avi_vname inst) cinsts
            sos' = if (removeUnusedMods flags)
                   then filter isPortOfConnectedInst sos
                   else sos
            isPortOfConnectedInst (i,_) =
                -- XXX This uses a hack (getRootName) to get the instance name
                (getRootName i) `S.member` (S.map (getIdString) cinsts)
  
            -- started with the outputs, any kept firing signals, any kept
            -- instances, and the wires used in foreign function calls
            avdefs = concat [afc_writes fc | (_,fcs) <- fs, fc <- fcs]
            markedConn :: [AId]
            markedConn = (map fst os) ++ (map fst ios) ++
                         fires ++ instconn ++
                         aVars fs ++ avdefs ++
                         inlinedports ++ keepEvenUnused
            instconn   = if (removeUnusedMods flags)
                         then []
                         else concatMap (getPortIdsFromInst flags) ss
            fires = if (keepFires flags)
                    then [i | def@(ADef i t e _) <- ds, isFire i ]
                    else []
            inlinedports = if (keepInlinedMods)
                           then aspkg_inlined_ports package
                           else []
            keepEvenUnused = [i | def@(ADef i _ _ _) <- ds, hasIdProp i IdP_keepEvenUnused]
  
  -- return a  set of connected nodes and connected instances
  connectedNode :: Flags -> [AId] -> [AVInst] ->  [ADef]  -> (S.Set AId, S.Set AId)
  connectedNode flags outputs ainsts din = (visited_wires cstate, visited_insts cstate)
      where
      defMap  = M.fromList (map (\d@(ADef i _ _ _) -> (i,d))  din)
      instMap = M.fromList (map (\inst -> (avi_vname inst,inst)) ainsts)
      initState = (ConnState S.empty S.empty defMap instMap flags)
      cstate = connected initState outputs
  
  -- traverse the design.
  connected :: ConnState -> [AId] -> ConnState
  connected cstate [] = cstate
  connected cstate (h:rest) |  S.member h (visited_wires cstate) = connected cstate rest
  connected cstate (h:rest) = connected cstate'  (newvars ++ rest )
      where
          (cstate', newvars) = case (M.lookup h (defs cstate)) of
                                 Nothing    -> handleInst cstate h
                                 Just def   -> handleExpr cstate h (adef_expr def)
  
  -- pull out var from def
  handleExpr :: ConnState -> Id -> AExpr -> (ConnState,[Id])
  handleExpr cstate thisId thisExpr = (cstate',newvars)
      where
        cstate' = cstate { visited_wires = S.insert thisId (visited_wires cstate)}
        newvars = aVars thisExpr
  
  handleInst :: ConnState -> Id -> (ConnState,[Id])
  handleInst cstate instId = handleInst2 rootId
      where
        rootName = getRootName instId
        rootId   = mkId noPosition (mkFString rootName)
        --
        handleInst2 :: Id -> (ConnState,[Id])
        handleInst2 rid | rid `S.member` (visited_insts cstate) = (cstate,[])
        handleInst2 rid = (cstate',newvars)
            where
              cstate' = cstate { visited_insts = S.insert rid (visited_insts cstate),
                                 visited_wires = S.insert instId (visited_wires cstate)}
              newvars = case (M.lookup rid (instances cstate)) of
                           Just i  -> getPortIdsFromInst (flags cstate) i
                           Nothing -> [] -- inputs have no defs
  
  -- Get AIds from an instance, ports plus clock and reset
  getPortIdsFromInst :: Flags -> AVInst -> [AId]
  getPortIdsFromInst flags inst =
          cr ++ portids
      where
          cr = aVars inst
          (_,ports) = unzip $ createVerilogNameMapForAVInst flags inst
          portids = map (\s -> mkId noPosition s) (nub (ports))
  
  -- XXX This is hack to get the instance name; need a better data model
  getRootName :: Id -> String
  getRootName id = takeWhile ((/=) '$') (getIdString id)
  