{- sv2v
 - Author: Zachary Snow <zach@zachjs.com>
 -
 - Conversion for any unpacked array which must be packed because it is: A) a
 - port; B) is bound to a port; C) is assigned a value in a single assignment;
 - or D) is assigned to an unpacked array which itself must be packed.
 -
 - The scoped nature of declarations makes this challenging. While scoping is
 - obeyed in general, if any of a set of *equivalent* declarations within a
 - module is packed, all of the declarations are packed. This is because we only
 - record the declaration that needs to be packed when a relevant usage is
 - encountered.
 -}

module Convert.UnpackedArray (convert) where

import Control.Monad.State.Strict
import qualified Data.Set as Set

import Convert.Scoper
import Convert.Traverse
import Language.SystemVerilog.AST

type Location = [Identifier]
type Locations = Set.Set Location
type ST = ScoperT Decl (State Locations)

convert :: [AST] -> [AST]
convert = map $ traverseDescriptions convertDescription

convertDescription :: Description -> Description
convertDescription (description @ (Part _ _ Module _ _ _ _)) =
    evalState (operation description) Set.empty
    where
        operation =
            partScoperT traverseDeclM traverseModuleItemM noop traverseStmtM >=>
            partScoperT rewriteDeclM noop noop noop
        noop = return
convertDescription other = other

-- tracks multi-dimensional unpacked array declarations
traverseDeclM :: Decl -> ST Decl
traverseDeclM (decl @ (Variable _ _ _ [] _)) = return decl
traverseDeclM (decl @ (Variable dir _ x _ e)) = do
    insertElem x decl
    if dir /= Local || e /= Nil
        then flatUsageM x
        else return ()
    return decl
traverseDeclM other = return other

-- pack decls marked for packing
rewriteDeclM :: Decl -> ST Decl
rewriteDeclM (decl @ (Variable _ _ _ [] _)) = return decl
rewriteDeclM (decl @ (Variable d t x a e)) = do
    insertElem x decl
    details <- lookupElemM x
    let Just (accesses, _, _) = details
    let location = map accessName accesses
    usedAsPacked <- lift $ gets $ Set.member location
    if usedAsPacked
        then do
            let (tf, rs) = typeRanges t
            let t' = tf $ a ++ rs
            return $ Variable d t' x [] e
        else return decl
rewriteDeclM other = return other

traverseModuleItemM :: ModuleItem -> ST ModuleItem
traverseModuleItemM =
    traverseModuleItemM'
    >=> traverseLHSsM  traverseLHSM
    >=> traverseExprsM traverseExprM
    >=> traverseAsgnsM traverseAsgnM

traverseModuleItemM' :: ModuleItem -> ST ModuleItem
traverseModuleItemM' (Instance a b c d bindings) = do
    bindings' <- mapM collectBinding bindings
    return $ Instance a b c d bindings'
    where
        collectBinding :: PortBinding -> ST PortBinding
        collectBinding (y, x) = do
            flatUsageM x
            return (y, x)
traverseModuleItemM' other = return other

traverseStmtM :: Stmt -> ST Stmt
traverseStmtM =
    traverseStmtLHSsM  traverseLHSM  >=>
    traverseStmtExprsM traverseExprM >=>
    traverseStmtAsgnsM traverseAsgnM

traverseExprM :: Expr -> ST Expr
traverseExprM (Range x mode i) =
    flatUsageM x >> return (Range x mode i)
traverseExprM other = return other

traverseLHSM :: LHS -> ST LHS
traverseLHSM x = flatUsageM x >> return x

traverseAsgnM :: (LHS, Expr) -> ST (LHS, Expr)
traverseAsgnM (x, Mux cond y z) = do
    flatUsageM x
    flatUsageM y
    flatUsageM z
    return (x, Mux cond y z)
traverseAsgnM (x, y) = do
    flatUsageM x
    flatUsageM y
    return (x, y)

flatUsageM :: ScopeKey e => e -> ST ()
flatUsageM x = do
    details <- lookupElemM x
    case details of
        Just (accesses, _, _) -> do
            let location = map accessName accesses
            lift $ modify $ Set.insert location
        Nothing -> return ()