{- sv2v
 - Author: Zachary Snow <zach@zachjs.com>
 -
 - Conversion for `typedef`
 -
 - Aliased types can appear in all data declarations, including modules, blocks,
 - and function parameters. They are also found in type cast expressions.
 -}

module Convert.Typedef (convert) where

import Control.Monad.Writer
import qualified Data.Map as Map

import Convert.Traverse
import Language.SystemVerilog.AST

type Types = Map.Map Identifier Type

convert :: [AST] -> [AST]
convert =
    traverseFiles
        (collectDescriptionsM getTypedef)
        (\a -> traverseDescriptions $ removeTypedef . convertDescription a)
    where
        getTypedef :: Description -> Writer Types ()
        getTypedef (PackageItem (Typedef a b)) = tell $ Map.singleton b a
        getTypedef (Part _ _ Interface _ x _ _) =
            tell $ Map.singleton x (InterfaceT x Nothing [])
        getTypedef _ = return ()
        removeTypedef :: Description -> Description
        removeTypedef (PackageItem (Typedef _ x)) =
            PackageItem $ Comment $ "removed typedef: " ++ x
        removeTypedef other = other

convertDescription :: Types -> Description -> Description
convertDescription globalTypes description =
    traverseModuleItems removeTypedef $
    traverseModuleItems convertModuleItem $
    traverseModuleItems (traverseExprs $ traverseNestedExprs $ convertExpr) $
    traverseModuleItems (traverseTypes $ resolveType types) $
    description
    where
        types = Map.union globalTypes $
            execWriter $ collectModuleItemsM getTypedef description
        getTypedef :: ModuleItem -> Writer Types ()
        getTypedef (MIPackageItem (Typedef a b)) = tell $ Map.singleton b a
        getTypedef _ = return ()
        removeTypedef :: ModuleItem -> ModuleItem
        removeTypedef (MIPackageItem (Typedef _ x)) =
            MIPackageItem $ Comment $ "removed typedef: " ++ x
        removeTypedef other = other
        convertTypeOrExpr :: TypeOrExpr -> TypeOrExpr
        convertTypeOrExpr (Right (Ident x)) =
            if Map.member x types
                then Left $ resolveType types (Alias Nothing x [])
                else Right $ Ident x
        convertTypeOrExpr other = other
        convertExpr :: Expr -> Expr
        convertExpr (Cast v e) = Cast (convertTypeOrExpr v) e
        convertExpr (DimsFn f v) = DimsFn f (convertTypeOrExpr v)
        convertExpr (DimFn f v e) = DimFn f (convertTypeOrExpr v) e
        convertExpr other = other
        convertModuleItem :: ModuleItem -> ModuleItem
        convertModuleItem (Instance m params x r p) =
            Instance m (map mapParam params) x r p
            where mapParam (i, v) = (i, convertTypeOrExpr v)
        convertModuleItem other = other

resolveItem :: Types -> (Type, Identifier) -> (Type, Identifier)
resolveItem types (t, x) = (resolveType types t, x)

resolveType :: Types -> Type -> Type
resolveType _ (Net           kw sg rs) = Net           kw sg rs
resolveType _ (Implicit         sg rs) = Implicit         sg rs
resolveType _ (IntegerVector kw sg rs) = IntegerVector kw sg rs
resolveType _ (IntegerAtom   kw sg   ) = IntegerAtom   kw sg
resolveType _ (NonInteger    kw      ) = NonInteger    kw
resolveType _ (InterfaceT     x my rs) = InterfaceT     x my rs
resolveType _ (Enum Nothing   vals rs) = Enum Nothing   vals rs
resolveType _ (Alias (Just ps)  st rs) = Alias (Just ps)  st rs
resolveType types (Enum (Just t) vals rs) = Enum (Just $ resolveType types t) vals rs
resolveType types (Struct p items rs) = Struct p (map (resolveItem types) items) rs
resolveType types (Union  p items rs) = Union  p (map (resolveItem types) items) rs
resolveType types (Alias Nothing st rs1) =
    if Map.notMember st types
    then Alias Nothing st rs1
    else case resolveType types $ types Map.! st of
        (Net           kw sg rs2) -> Net           kw sg $ rs1 ++ rs2
        (Implicit         sg rs2) -> Implicit         sg $ rs1 ++ rs2
        (IntegerVector kw sg rs2) -> IntegerVector kw sg $ rs1 ++ rs2
        (Enum            t v rs2) -> Enum            t v $ rs1 ++ rs2
        (Struct          p l rs2) -> Struct          p l $ rs1 ++ rs2
        (Union           p l rs2) -> Union           p l $ rs1 ++ rs2
        (InterfaceT     x my rs2) -> InterfaceT     x my $ rs1 ++ rs2
        (Alias          ps x rs2) -> Alias          ps x $ rs1 ++ rs2
        (IntegerAtom   kw sg    ) -> nullRange (IntegerAtom kw sg) rs1
        (NonInteger    kw       ) -> nullRange (NonInteger  kw   ) rs1