{-|
  Copyright  :  (C) 2015-2016, University of Twente,
                    2017     , Myrtle Software Ltd, Google Inc.,
                    2021-2022, QBayLogic B.V.
                    2022     , Google Inc.
  License    :  BSD2 (see the file LICENSE)
  Maintainer :  QBayLogic B.V. <devops@qbaylogic.com>
-}

{-# LANGUAGE CPP               #-}
{-# LANGUAGE OverloadedStrings #-}

module Clash.Backend where

import Data.HashMap.Strict                  (HashMap, empty)
import Data.HashSet                         (HashSet)
import Control.Lens                         (Lens')
import Data.Monoid                          (Ap)
import Data.Text                            (Text)
import qualified Data.Text.Lazy             as LT
import Control.Monad.State                  (State)
import Data.Text.Prettyprint.Doc.Extra      (Doc)

import GHC.Types.SrcLoc (SrcSpan)

import Clash.Driver.Types (ClashOpts)
import {-# SOURCE #-} Clash.Netlist.Types
  (Component, Declaration, Expr, HWType, Identifier, IdentifierSet, HasIdentifierSet, UsageMap)
import Clash.Netlist.BlackBox.Types

import Clash.Signal.Internal                (VDomainConfiguration)
import Clash.Annotations.Primitive          (HDL)

#ifdef CABAL
import qualified Paths_clash_lib
import qualified Data.Version
#else
import qualified System.FilePath
#endif

primsRoot :: IO FilePath
#ifdef CABAL
primsRoot :: IO FilePath
primsRoot = FilePath -> IO FilePath
Paths_clash_lib.getDataFileName FilePath
"prims"
#else
primsRoot = return ("clash-lib" System.FilePath.</> "prims")
#endif

clashVer :: String
#ifdef CABAL
clashVer :: FilePath
clashVer = Version -> FilePath
Data.Version.showVersion Version
Paths_clash_lib.version
#else
clashVer = "development"
#endif

type ModName = Text

-- | Is a type used for internal or external use
data Usage
  = Internal
  -- ^ Internal use
  | External Text
  -- ^ External use, field indicates the library name

-- | Is '-fclash-aggresive-x-optimization-blackbox' set?
newtype AggressiveXOptBB = AggressiveXOptBB Bool

-- | Is '-fclash-render-enums' set?
newtype RenderEnums = RenderEnums Bool

-- | Kind of a HDL type. Used to determine whether types need conversions in
-- order to cross top entity boundaries.
data HWKind
  = PrimitiveType
  -- ^ A type defined in an HDL spec. Usually types such as: bool, bit, ..
  | SynonymType
  -- ^ A user defined type that's simply a synonym for another type, very much
  -- like a type synonym in Haskell. As long as two synonym types refer to the
  -- same type, they can be used interchangeably. E.g., a subtype in VHDL.
  | UserType
  -- ^ User defined type that's not interchangeable with any others, even if
  -- the underlying structures are the same. Similar to an ADT in Haskell.

type DomainMap = HashMap Text VDomainConfiguration

emptyDomainMap :: DomainMap
emptyDomainMap :: DomainMap
emptyDomainMap = DomainMap
forall k v. HashMap k v
empty

class HasUsageMap s where
  usageMap :: Lens' s UsageMap

class (HasUsageMap state, HasIdentifierSet state) => Backend state where
  -- | Initial state for state monad
  initBackend :: ClashOpts -> state

  -- | What HDL is the backend generating
  hdlKind :: state -> HDL

  -- | Location for the primitive definitions
  primDirs :: state -> IO [FilePath]

  -- | Name of backend, used for directory to put output files in. Should be
  --   constant function / ignore argument.
  name :: state -> String

  -- | File extension for target langauge
  extension :: state -> String

  -- | Get the set of types out of state
  extractTypes     :: state -> HashSet HWType

  -- | Generate HDL for a Netlist component
  genHDL           :: ClashOpts -> ModName -> SrcSpan -> IdentifierSet -> UsageMap -> Component -> Ap (State state) ((String, Doc),[(String,Doc)])
  -- | Generate a HDL package containing type definitions for the given HWTypes
  mkTyPackage      :: ModName -> [HWType] -> Ap (State state) [(String, Doc)]
  -- | Convert a Netlist HWType to a target HDL type
  hdlType          :: Usage -> HWType -> Ap (State state) Doc
  -- | Query what kind of type a given HDL type is
  hdlHWTypeKind :: HWType -> State state HWKind
  -- | Convert a Netlist HWType to an HDL error value for that type
  hdlTypeErrValue  :: HWType       -> Ap (State state) Doc
  -- | Convert a Netlist HWType to the root of a target HDL type
  hdlTypeMark      :: HWType       -> Ap (State state) Doc
  -- | Create a record selector
  hdlRecSel        :: HWType -> Int -> Ap (State state) Doc
  -- | Create a signal declaration from an identifier (Text) and Netlist HWType
  hdlSig           :: LT.Text -> HWType -> Ap (State state) Doc
  -- | Create a generative block statement marker
  genStmt          :: Bool -> State state Doc
  -- | Turn a Netlist Declaration to a HDL concurrent block
  inst             :: Declaration  -> Ap (State state) (Maybe Doc)
  -- | Turn a Netlist expression into a HDL expression
  expr             :: Bool -- ^ Enclose in parentheses?
                   -> Expr -- ^ Expr to convert
                   -> Ap (State state) Doc
  -- | Bit-width of Int,Word,Integer
  iwWidth          :: State state Int
  -- | Convert to a bit-vector
  toBV             :: HWType -> LT.Text -> Ap (State state) Doc
  -- | Convert from a bit-vector
  fromBV           :: HWType -> LT.Text -> Ap (State state) Doc
  -- | Synthesis tool we're generating HDL for
  hdlSyn           :: State state HdlSyn
  -- | setModName
  setModName       :: ModName -> state -> state
  -- | Set the name of the current top entity
  setTopName       :: Identifier -> state -> state
  -- | Get the name of the current top entity
  getTopName       :: State state Identifier
  -- | setSrcSpan
  setSrcSpan       :: SrcSpan -> State state ()
  -- | getSrcSpan
  getSrcSpan       :: State state SrcSpan
  -- | Block of declarations
  blockDecl        :: Identifier -> [Declaration] -> Ap (State state) Doc
  addIncludes      :: [(String, Doc)] -> State state ()
  addLibraries     :: [LT.Text] -> State state ()
  addImports       :: [LT.Text] -> State state ()
  addAndSetData    :: FilePath -> State state String
  getDataFiles     :: State state [(String,FilePath)]
  addMemoryDataFile  :: (String,String) -> State state ()
  getMemoryDataFiles :: State state [(String,String)]
  ifThenElseExpr :: state -> Bool
  -- | Whether -fclash-aggressive-x-optimization-blackboxes was set
  aggressiveXOptBB :: State state AggressiveXOptBB
  -- | Whether -fclash-no-render-enums was set
  renderEnums :: State state RenderEnums
  -- | All the domain configurations of design
  domainConfigurations :: State state DomainMap
  -- | Set the domain configurations
  setDomainConfigurations :: DomainMap -> state -> state