{-# LANGUAGE ExistentialQuantification #-}

-- | Represent information about externs needed in the generation of C99 code
-- for stream declarations and triggers.
module Copilot.Compile.C99.External
    ( External(..)
    , gatherExts
    )
  where

-- External imports
import Data.List  (unionBy)

-- Internal imports: Copilot
import Copilot.Core ( Expr (..), Stream (..), Trigger (..), Type, UExpr (..) )

-- Internal imports
import Copilot.Compile.C99.Name ( exCpyName )

-- | Representation of external variables.
data External = forall a. External
  { extName    :: String
  , extCpyName :: String
  , extType    :: Type a
  }

-- | Collect all external variables from the streams and triggers.
--
-- Although Copilot specifications can contain also properties and theorems,
-- the C99 backend currently only generates code for streams and triggers.
gatherExts :: [Stream] -> [Trigger] -> [External]
gatherExts streams triggers = streamsExts `extUnion` triggersExts
  where
    streamsExts  = foldr (extUnion . streamExts) mempty streams
    triggersExts = foldr (extUnion . triggerExts) mempty triggers

    streamExts :: Stream -> [External]
    streamExts (Stream _ _ expr _) = exprExts expr

    triggerExts :: Trigger -> [External]
    triggerExts (Trigger _ guard args) = guardExts `extUnion` argExts
      where
        guardExts = exprExts guard
        argExts   = concatMap uExprExts args

    uExprExts :: UExpr -> [External]
    uExprExts (UExpr _ expr) = exprExts expr

    exprExts :: Expr a -> [External]
    exprExts (Local _ _ _ e1 e2)   = exprExts e1 `extUnion` exprExts e2
    exprExts (ExternVar ty name _) = [External name (exCpyName name) ty]
    exprExts (Op1 _ e)             = exprExts e
    exprExts (Op2 _ e1 e2)         = exprExts e1 `extUnion` exprExts e2
    exprExts (Op3 _ e1 e2 e3)      = exprExts e1 `extUnion` exprExts e2
                                       `extUnion` exprExts e3
    exprExts (Label _ _ e)         = exprExts e
    exprExts _                     = []

    -- | Union over lists of External, we solely base the equality on the
    -- extName's.
    extUnion :: [External] -> [External] -> [External]
    extUnion = unionBy (\a b -> extName a == extName b)
