{-# OPTIONS_GHC -fno-warn-missing-fields #-}

module Lava.Model where



import Control.Arrow ((***))
import Control.Monad.Writer
import Control.Monad.State
import Data.Char
import Data.List as List
import Data.Map (Map)
import qualified Data.Map as Map

import Data.Hardware.Internal

import qualified Lava2000 as L



-- | Identifies a driver in the circuit. A driver is either a primary input or
-- an output pin of a cell.
data Signal
       = PrimInpSig PrimInpId
       | CellSig CellId OutPin
     deriving (Eq, Show, Ord)

data Declaration lib
       = PrimInput PrimInpId
       | Cell CellId lib [Signal]
       | Label Tag Signal
     deriving (Eq, Show)

data DesignDB lib = DesignDB
       { cellDB   :: Map CellId (lib,[Signal])
       , fanoutDB :: Map Signal [(CellId,InPin)]
       , sigTagDB :: Map Signal [Tag]
       , tagSigDB :: Map Tag [Signal]
       , primIns  :: [Signal]
       }
     deriving (Eq, Show)
  -- sigTagDB and tagSigDB need only be defined for non-empty values. Use
  -- together with totalLookup. fanoutDB should be defined for all signals (even
  -- ones with no fanout), so that it can be used to list all signals. A
  -- database is valid if the corresponding list of declarations is (*** it
  -- should be possible to reconstruct a list of declarations from a database to
  -- make this well-defined).



class CellLibrary lib
  where
    numIns  :: lib -> InPin
    numOuts :: lib -> OutPin
      -- Number of inputs/outputs of the given cell

    inPinName :: lib -> InPin -> Name
    inPinId   :: lib -> Name  -> InPin

    outPinName :: lib -> OutPin -> Name
    outPinId   :: lib -> Name   -> OutPin

    isFlop :: lib -> Bool
      -- Tells whether or not the given cell is a flipflop.

    lava2000Interp :: Interpretation lib (L.Signal Bool)

  -- Requirements:
  --
  --   * The domain of (inPinName  c) is [0 .. numIns  c - 1]
  --   * The domain of (outPinName c) is [0 .. numOuts c - 1]
  --
  --   * (inPinName  c) is one-to-one, and its inverse is (inPinId  c).
  --   * (outPinName c) is one-to-one, and its inverse is (outPinId c).
  --
  --   * lava2000Interp is a valid interpretation (see definition of
  --     Interpretation). Moreover, the propagator method should constrain all
  --     and only all outputs of each cell.



cellInputs :: CellLibrary lib => CellId -> lib -> [(CellId,InPin)]
cellInputs cid ct = zip (repeat cid) [0 .. numIns ct-1]

cellOutputs :: CellLibrary lib => CellId -> lib -> [Signal]
cellOutputs cid ct = map (CellSig cid) [0 .. numOuts ct-1]



prop_uniquePrimInputs decls = iids == nub iids
  where
    iids = [iid | PrimInput iid <- decls]
  -- Each PrimInput has a uniqe PrimInpId.

prop_uniqueCells decls = cids == nub cids
  where
    cids = [cid | Cell cid _ _ <- decls]
  -- Each Cell has a uniqe CellId.

prop_correctCellInputs decls =
    and [numIns ct == genericLength ss | Cell _ ct ss <- decls]
  -- Each Cell has the correct number of inputs.



prop_validSignals :: CellLibrary lib => [Declaration lib] -> Bool
prop_validSignals decls = all (`elem` validSigs) referred
  where
    primInps  = [PrimInpSig iid | PrimInput iid <- decls]
    cellOuts  = [s | Cell cid ct _ <- decls, s <- cellOutputs cid ct]
    validSigs = primInps ++ cellOuts

    referred
        = concat [ss | Cell _ _ ss <- decls]
              ++ [s  | Label _ s   <- decls]
      -- All signals referred to in the declarations

  -- Checks that all signals referred to by a Cell or Label are valid. The valid
  -- signals are the declared primary inputs and the outputs of declared cells.



prop_validDecls :: CellLibrary lib => [Declaration lib] -> Bool
prop_validDecls decls
     = prop_uniquePrimInputs  decls
    && prop_uniqueCells       decls
    && prop_correctCellInputs decls  -- Correct number of inputs for each cell
    && prop_validSignals      decls  -- No reference to invalid signals

  -- A circuit is always defined in the presence of a cell library. This
  -- property defines what it means for a list of declarations to be valid with
  -- respect to a cell library.
  --
  -- The properties should be fulfilled as long as the requirements of the
  -- CellLibrary class are met and all signals are created using the methods:
  -- input, cell and label.
  --
  -- The only thing that can go wrong is if the number of inputs/outputs
  -- demanded by the type of a cell is different from what is specified by
  -- numIns/numOuts. Difference in the inputs won't be checked at all (and the
  -- only place where it might matter is in the propagator method of an
  -- interpretation). However, difference in the outputs is checked for by
  -- fromListFP (but the error is non-informative).



newtype Lava lib a = Lava
          { unLava :: WriterT [Declaration lib] (State (PrimInpId,CellId)) a }
        deriving (Monad, MonadFix)



runLava :: CellLibrary lib => Lava lib a -> (a, DesignDB lib)
runLava (Lava lava) = (a, DesignDB cDB fDB stDB tsDB ins)
  where
    ((a,decls),_) = runState (runWriterT lava) (0,0)

    cDB  = Map.fromList [ (cid,(ct,ins)) | Cell cid ct ins <- decls ]
    stDB = fmap nub $ Map.fromListWith (++) [ (s,[t]) | Label t s <- decls ]
    tsDB = fmap nub $ Map.fromListWith (++) [ (t,[s]) | Label t s <- decls ]

    fDB = Map.fromListWith (++) $ concat
      [ zip ins (map return $ cellInputs cid ct)
          ++
        zip (cellOutputs cid ct) (repeat [])
          -- Just to make all outputs appear in the map.
        | Cell cid ct ins <- decls
      ]

    ins = [PrimInpSig iid | PrimInput iid <- decls]



class (Monad m, CellLibrary lib) => MonadLava lib m | m -> lib
  where
    newPrimInpId :: m PrimInpId
    newCellId    :: m CellId

    declare :: Declaration lib -> m ()

    listenDecls :: m a -> m (a, [Declaration lib])

    toLava :: m a -> Lava lib a

instance CellLibrary lib => MonadLava lib (Lava lib)
  where
    newPrimInpId = Lava $ do
        (iid,cid) <- get
        put (succ iid, cid)
        return iid

    newCellId = Lava $ do
        (iid,cid) <- get
        put (iid, succ cid)
        return cid

    declare = Lava . tell . return

    listenDecls = Lava . listen . unLava

    toLava = id



inputSig :: MonadLava lib m => m Signal
inputSig = do
    iid <- newPrimInpId
    declare (PrimInput iid)
    return (PrimInpSig iid)
  -- Declare a primary input

cellList :: MonadLava lib m => lib -> [Signal] -> m [Signal]
cellList ct ins = do
    cid <- newCellId
    declare $ Cell cid ct ins
    return (cellOutputs cid ct)
  -- Declare a cell

labelSig :: MonadLava lib m => Tag -> Signal -> m Signal
labelSig tag sig
    | not $ all isAlphaNum tag = error msg
    | otherwise                = declare (Label tag sig) >> return sig
  where
    msg =
      "label: Only alphanumeric characters allowed\n\
      \Offending label: " ++ tag

  -- Declare a label; only side-effect important



data Interpretation lib x = Interp
       { defaultVal  :: x
       , accumulator :: x -> x -> x
       , propagator  :: lib -> ([x] -> [Maybe x])
           -- The lists contain all outputs in pin order and then all inputs in
           -- pin order (pin 0 first).
       }

  -- The number of elements accepted/returned by the propagator should be
  -- (numOuts cell + numIns cell).



type InterpDesignDB lib x = (DesignDB lib, Map Signal x)



lookupTag :: Tag -> InterpDesignDB lib x -> [x]
lookupTag tag (db,sigMap) = map (sigMap Map.!) (tag `totalLookup` tagSigDB db)

depthInterp :: CellLibrary lib => Interpretation lib Int
depthInterp = Interp
    { defaultVal = 0  -- Inputs
    , propagator = prop
    }
  where
    prop ct vals
        | isFlop ct = genericReplicate no (Just 0)     ++ ins
        | otherwise = genericReplicate no (Just (d+1)) ++ ins
      where
        ni  = numIns  ct
        no  = numOuts ct
        ins = genericReplicate ni Nothing
        d   = maximum (0 : genericDrop no vals)
          -- Cells with no inputs will have depth 0+1.