from copy import copy
from itertools import chain

from hwt.hdl.constants import INTF_DIRECTION, DIRECTION
from hwt.hdl.types.bits import Bits
from hwt.hdl.types.defs import BIT
from hwt.hdl.types.struct import HStruct
from hwt.pyUtils.arrayQuery import single
from hwt.synthesizer.exceptions import IntfLvlConfErr
from hwt.synthesizer.interfaceLevel.interfaceUtils.utils import walkPhysInterfaces
from hwt.doc_markers import internal

def getClk(unit):
    try:
        return unit.clk
    except AttributeError:
        pass

    raise IntfLvlConfErr("Can not find clock on unit %r" % (unit,))

def getRst(unit):
    try:
        return unit.rst
    except AttributeError:
        pass

    try:
        return unit.rst_n
    except AttributeError:
        pass

    raise IntfLvlConfErr("Can not find clock on unit %r" % (unit,))

def getSignalName(sig):
    try:
        return sig._name
    except AttributeError:
        pass
    return sig.name

@internal
def _default_param_updater(self, myP, otherP):
    myP.set(otherP)

class UnitImplHelpers(object):
    def _reg(self, name, dtype=BIT, defVal=None, clk=None, rst=None):
        """
        Create register in this unit

        :param defVal: default value of this register,
            if this value is specified reset of this component is used
            (unit has to have single interface of class Rst or Rst_n)
        :param clk: optional clok signal specification
        :param rst: optional reset signal specification
        :note: rst/rst_n resolution is done from signal type,
            if it is negated type it is rst_n
        :note: if clk or rst is not specifid default signal
            from parent unit will be used
        """
        if clk is None:
            clk = getClk(self)

        if defVal is None:
            # if no value is specified reset is not required
            rst = None
        else:
            rst = getRst(self)._sig

        if isinstance(dtype, HStruct):
            if defVal is not None:
                raise NotImplementedError()
            container = dtype.fromPy(None)
            for f in dtype.fields:
                if f.name is not None:
                    r = self._reg("%s_%s" % (name, f.name), f.dtype)
                    setattr(container, f.name, r)

            return container

        return self._ctx.sig(name,
                             dtype=dtype,
                             clk=clk._sig,
                             syncRst=rst,
                             defVal=defVal)

    def _sig(self, name, dtype=BIT, defVal=None):
        """
        Create signal in this unit
        """
        if isinstance(dtype, HStruct):
            if defVal is not None:
                raise NotImplementedError()
            container = dtype.fromPy(None)
            for f in dtype.fields:
                if f.name is not None:
                    r = self._sig("%s_%s" % (name, f.name), f.dtype)
                    setattr(container, f.name, r)

            return container

        return self._ctx.sig(name, dtype=dtype, defVal=defVal)

    @internal
    def _cleanAsSubunit(self):
        """Disconnect internal signals so unit can be reused by parent unit"""
        for pi in self._entity.ports:
            pi.connectInternSig()
        for i in chain(self._interfaces, self._private_interfaces):
            i._clean()

    @internal
    def _signalsForMyEntity(self, context, prefix):
        # generate for all ports of subunit signals in this context
        def lockTypeWidth(t):
            # [TODO] only read parameter instead of full evaluation
            # problem is that parametes should be theyr's values
            # (because this signals are for parent unit)
            if isinstance(t, Bits):
                t = copy(t)
                t.width = t.bit_length()
                return t
            else:
                return t

        for i in self._interfaces:
            if i._isExtern:
                i._signalsForInterface(context, prefix + i._NAME_SEPARATOR,
                                       typeTransform=lockTypeWidth)

    @internal
    def _boundInterfacesToEntity(self, interfaces):
        externSignals = []
        inftToPortDict = {}

        for p in self._entity.ports:
            inftToPortDict[p._interface] = p

        for intf in self._interfaces:
            if intf._isExtern:
                for s in walkPhysInterfaces(intf):
                    externSignals.append(s)

        assert len(externSignals) == len(inftToPortDict.keys())

        for s in externSignals:
            self._boundIntfSignalToEntity(s, inftToPortDict)

    @internal
    def _boundIntfSignalToEntity(self, interface, inftToPortDict):
        portItem = single(self._entity.ports,
                          lambda x: x._interface == interface)
        interface._boundedEntityPort = portItem
        d = INTF_DIRECTION.asDirection(interface._direction)

        if d == DIRECTION.INOUT:
            portItem.direction = DIRECTION.INOUT

        if portItem.direction != d:
            raise IntfLvlConfErr(
                ("Unit %s: Port %s does not have direction "
                 " defined by interface %s, is %s should be %s")
                % (self._name, portItem.name,
                   repr(interface), portItem.direction, d))


from hwt.hdl.types.array import HArray
from hwt.hdl.types.bits import Bits
from hwt.hdl.types.struct import HStructField, HStruct
from hwt.hdl.types.typeCast import toHVal
from hwt.hdl.types.union import HUnion

def HStruct_selectFields(structT, fieldsToUse):
    """
    Select fields from structure (rest will become spacing)

    :param structT: HStruct type instance
    :param fieldsToUse: dict {name:{...}} or set of names to select,
        dictionary is used to select nested fields
        in HStruct or HUnion fields
        (f.e. {"struct1": {"field1", "field2"}, "field3":{}}
        will select field1 and 2 from struct1 and field3 from root)
    """

    template = []
    fieldsToUse = fieldsToUse
    foundNames = set()

    for f in structT.fields:
        name = None
        subfields = []

        if f.name is not None:
            try:
                if isinstance(fieldsToUse, dict):
                    subfields = fieldsToUse[f.name]
                    name = f.name
                else:
                    if f.name in fieldsToUse:
                        name = f.name
            except KeyError:
                name = None

        if name is not None and subfields:
            fields = HStruct_selectFields(f.dtype, subfields)
            template.append(HStructField(fields, name))
        else:
            template.append(HStructField(f.dtype, name))

        if f.name is not None:
            foundNames.add(f.name)

    if isinstance(fieldsToUse, dict):
        fieldsToUse = set(fieldsToUse.keys())
    assert fieldsToUse.issubset(foundNames)

    return HStruct(*template)

def walkFlattenFields(sigOrVal, skipPadding=True):
    """
    Walk all simple values in HStruct or HArray
    """
    t = sigOrVal._dtype
    if isinstance(t, Bits):
        yield sigOrVal
    elif isinstance(t, HUnion):
        yield from walkFlattenFields(sigOrVal._val, skipPadding=skipPadding)
    elif isinstance(t, HStruct):
        for f in t.fields:
            isPadding = f.name is None
            if not isPadding or not skipPadding:
                if isPadding:
                    v = f.dtype.fromPy(None)
                else:
                    v = getattr(sigOrVal, f.name)

                yield from walkFlattenFields(v)

    elif isinstance(t, HArray):
        for item in sigOrVal:
            yield from walkFlattenFields(item)
    else:
        raise NotImplementedError(t)

def HStruct_unpack(structT, data, getDataFn=None, dataWidth=None):
    """
    opposite of packAxiSFrame
    """
    if getDataFn is None:
        assert dataWidth is not None

        def _getDataFn(x):
            return toHVal(x)._auto_cast(Bits(dataWidth))

        getDataFn = _getDataFn

    val = structT.fromPy(None)

    fData = iter(data)

    # actual is storage variable for items from frameData
    actualOffset = 0
    actual = None

    for v in walkFlattenFields(val, skipPadding=False):
        # walk flatten fields and take values from fData and parse them to
        # field
        required = v._dtype.bit_length()

        if actual is None:
            actualOffset = 0
            try:
                actual = getDataFn(next(fData))
            except StopIteration:
                raise Exception("Input data too short")

            if dataWidth is None:
                dataWidth = actual._dtype.bit_length()
            actuallyHave = dataWidth
        else:
            actuallyHave = actual._dtype.bit_length() - actualOffset

        while actuallyHave < required:
            # collect data for this field
            try:
                d = getDataFn(next(fData))
            except StopIteration:
                raise Exception("Input data too short")

            actual = d._concat(actual)
            actuallyHave += dataWidth

        if actuallyHave >= required:
            # parse value of actual to field
            # skip padding
            _v = actual[(required + actualOffset):actualOffset]
            _v = _v._auto_cast(v._dtype)
            v.val = _v.val
            v.vldMask = _v.vldMask
            v.updateTime = _v.updateTime

            # update slice out what was taken
            actuallyHave -= required
            actualOffset += required

        if actuallyHave == 0:
            actual = None

    if actual is not None:
        assert actual._dtype.bit_length(
        ) - actualOffset < dataWidth, "It should be just a padding at the end of frame"

    return val


from copy import copy
import enum
from operator import eq, ne, lt, gt, ge, le

from hwt.bitmask import mask, selectBit, selectBitRange, bitSetTo, \
    setBitRange
from hwt.hdl.operator import Operator
from hwt.hdl.operatorDefs import AllOps
from hwt.hdl.typeShortcuts import hInt
from hwt.hdl.types.bitValFunctions import bitsCmp__val, bitsCmp, \
    bitsBitOp__val, bitsBitOp, bitsArithOp__val, bitsArithOp, signFix
from hwt.hdl.types.bitVal_bitOpsVldMask import vldMaskForXor, \
    vldMaskForAnd, vldMaskForOr
from hwt.hdl.types.bitVal_opReduce import tryReduceOr, tryReduceAnd, \
    tryReduceXor
from hwt.hdl.types.bits import Bits
from hwt.hdl.types.defs import BOOL, INT, BIT, SLICE
from hwt.hdl.types.eventCapableVal import EventCapableVal
from hwt.hdl.types.integer import Integer
from hwt.hdl.types.integerVal import IntegerVal
from hwt.hdl.types.slice import Slice
from hwt.hdl.types.sliceUtils import slice_to_SLICE
from hwt.hdl.types.typeCast import toHVal
from hwt.hdl.value import Value, areValues
from hwt.synthesizer.interfaceLevel.mainBases import InterfaceBase
from hwt.synthesizer.rtlLevel.mainBases import RtlSignalBase
from hwt.synthesizer.rtlLevel.signalUtils.exceptions import MultipleDriversErr,\
    NoDriverErr
from hwt.doc_markers import internal

class BitsVal(EventCapableVal):
    """
    :attention: operator on signals are using value operator functions as well
    """

    def _isFullVld(self):
        return self.vldMask == self._dtype._allMask

    @internal
    def _convSign__val(self, signed):
        t = self._dtype
        if t.signed == signed:
            return self
        selfSign = t.signed
        v = self.clone()
        w = t._widthVal
        m = t._allMask
        _v = v.val

        if selfSign and not signed:
            if _v < 0:
                v.val = m + _v + 1
        elif not selfSign and signed:
            msbMask = 1 << (w - 1)
            if _v >= msbMask:
                v.val = -_v + msbMask + (m >> 1) - 1
        v._dtype = v._dtype.__class__(w, signed=signed)

        return v

    @internal
    def _convSign(self, signed):
        """
        Convert signum, no bit manipulation just data are represented
        differently

        :param signed: if True value will be signed,
            if False value will be unsigned,
            if None value will be vector without any sign specification
        """
        if isinstance(self, Value):
            return self._convSign__val(signed)
        else:
            if self._dtype.signed == signed:
                return self
            t = copy(self._dtype)
            t.signed = signed
            if signed is None:
                cnv = AllOps.BitsAsVec
            elif signed:
                cnv = AllOps.BitsAsSigned
            else:
                cnv = AllOps.BitsAsUnsigned

            return Operator.withRes(cnv, [self], t)

    def _signed(self):
        return self._convSign(True)

    def _unsigned(self):
        return self._convSign(False)

    def _vec(self):
        return self._convSign(None)

    @classmethod
    def fromPy(cls, val, typeObj, vldMask=None):
        """
        Construct value from pythonic value (int, bytes, enum.Enum member)
        """
        assert not isinstance(val, Value)
        if val is None:
            vld = 0
            val = 0
            assert vldMask is None or vldMask == 0
        else:
            allMask = typeObj.all_mask()
            w = typeObj.bit_length()
            if isinstance(val, bytes):
                val = int.from_bytes(
                    val, byteorder="little", signed=bool(typeObj.signed))
            else:
                try:
                    val = int(val)
                except TypeError as e:
                    if isinstance(val, enum.Enum):
                        val = int(val.value)
                    else:
                        raise e

            if vldMask is None:
                vld = allMask
            else:
                assert vldMask <= allMask and vldMask >= 0
                vld = vldMask

            if val < 0:
                assert typeObj.signed
                assert signFix(val & allMask, w) == val, (
                    val, signFix(val & allMask, w))
                val = signFix(val & vld, w)
            else:
                if typeObj.signed:
                    msb = 1 << (w - 1)
                    if msb & val:
                        assert val < 0, val

                if val & allMask != val:
                    raise ValueError(
                        "Not enought bits to represent value",
                        val, val & allMask)
                val = val & vld

        return cls(val, typeObj, vld)

    def toPy(self):
        return int(self)

    @internal
    def _concat__val(self, other):
        w = self._dtype.bit_length()
        other_w = other._dtype.bit_length()
        resWidth = w + other_w
        resT = Bits(resWidth)

        v = self.clone()
        v.val = (v.val << other_w) | other.val
        v.vldMask = (v.vldMask << other_w) | other.vldMask
        v.updateTime = max(self.updateTime, other.updateTime)
        v._dtype = resT

        return v

    def _concat(self, other):
        """
        Concatenate this with other to one wider value/signal
        """
        w = self._dtype.bit_length()
        try:
            other_bit_length = other._dtype.bit_length
        except AttributeError:
            raise TypeError("Can not concat bits and", other._dtype)

        other_w = other_bit_length()
        resWidth = w + other_w
        resT = Bits(resWidth)

        if areValues(self, other):
            return self._concat__val(other)
        else:
            w = self._dtype.bit_length()
            other_w = other._dtype.bit_length()
            resWidth = w + other_w
            resT = Bits(resWidth)
            # is instance of signal
            if isinstance(other, InterfaceBase):
                other = other._sig
            if isinstance(other._dtype, Bits):
                if other._dtype.signed is not None:
                    other = other._vec()
            elif other._dtype == BOOL:
                other = other._auto_cast(BIT)
            else:
                raise TypeError(other._dtype)

            if self._dtype.signed is not None:
                self = self._vec()

            return Operator.withRes(AllOps.CONCAT, [self, other], resT)\
                           ._auto_cast(Bits(resWidth,
                                            signed=self._dtype.signed))

    @internal
    def _getitem__val_int(self, key):
        updateTime = max(self.updateTime, key.updateTime)
        keyVld = key._isFullVld()

        if keyVld:
            val = selectBit(self.val, key.val)
            vld = selectBit(self.vldMask, key.val)
        else:
            val = 0
            vld = 0

        return self.__class__(val, BIT, vld, updateTime=updateTime)

    @internal
    def _getitem__val_slice(self, key):
        updateTime = max(self.updateTime, key.updateTime)
        assert key._isFullVld()
        size = key._size()

        firstBitNo = key.val[1].val
        val = selectBitRange(self.val, firstBitNo, size)
        vld = selectBitRange(self.vldMask, firstBitNo, size)

        retT = self._dtype.__class__(size, signed=self._dtype.signed)
        return self.__class__(val, retT, vld, updateTime=updateTime)

    @internal
    def _getitem__val(self, key):
        # using self.__class__ because in simulator this method is called
        # for SimBits and we do not want to work with Bits in sim
        if isinstance(key._dtype, Integer):
            return self._getitem__val_int(key)
        elif key._dtype == SLICE:
            return self._getitem__val_slice(key)
        else:
            raise TypeError(key)

    def __getitem__(self, key):
        """
        [] operator

        :attention: Table below is for litle endian bit order (MSB:LSB)
            which is default.
            This is **reversed** as it is in pure python
            where it is [0, len(self)].

        :attention: slice on slice f signal is automatically reduced
            to single slice

        +-----------------------------+----------------------------------------------------------------------------------+
        | a[up:low]                   | items low through up; a[16:8] selects upper byte from 16b vector a               |
        +-----------------------------+----------------------------------------------------------------------------------+
        | a[up:]                      | low is automatically substituted with 0; a[8:] will select lower 8 bits          |
        +-----------------------------+----------------------------------------------------------------------------------+
        | a[:end]                     | up is automatically substituted; a[:8] will select upper byte from 16b vector a  |
        +-----------------------------+----------------------------------------------------------------------------------+
        | a[:], a[-1], a[-2:], a[:-2] | raises NotImplementedError   (not implemented due to complicated support in hdl) |
        +-----------+----------------------------------------------------------------------------------------------------+
        """
        iamVal = isinstance(self, Value)
        st = self._dtype
        length = st.bit_length()
        if length == 1 and not st.forceVector:
            # assert not indexing on single bit
            raise TypeError("indexing on single bit")

        if isinstance(key, slice):
            key = slice_to_SLICE(key, length)
            isSLICE = True
        else:
            isSLICE = isinstance(key, Slice.getValueCls())

        if isSLICE:
            # :note: downto notation
            start = key.val[0]
            stop = key.val[1]
            startIsVal = isinstance(start, Value)
            stopIsVal = isinstance(stop, Value)
            indexesAreValues = startIsVal and stopIsVal
        else:
            key = toHVal(key)

        iAmResultOfIndexing = (not iamVal and
                               hasattr(self, "origin") and
                               len(self.drivers) == 1 and
                               isinstance(self.origin, Operator) and
                               self.origin.operator == AllOps.INDEX)
        if isSLICE:
            if indexesAreValues and start.val == length and stop.val == 0:
                # selecting all bits no conversion needed
                return self

            if iAmResultOfIndexing:
                # try reduce self and parent slice to one
                original, parentIndex = self.origin.operands
                if isinstance(parentIndex._dtype, Slice):
                    parentLower = parentIndex.val[1]
                    start = start + parentLower
                    stop = stop + parentLower
                    return original[start:stop]

            # check start boundaries
            if startIsVal:
                _start = int(start)
                if _start < 0 or _start > length:
                    raise IndexError(_start, length)

            # check end boundaries
            if stopIsVal:
                _stop = int(stop)
                if _stop < 0 or _stop > length:
                    raise IndexError(_stop, length)

            # check width of selected range
            if startIsVal and stopIsVal and _start - _stop <= 0:
                raise IndexError(_start, _stop)

            if iamVal:
                return self._getitem__val(key)
            else:
                key = start._downto(stop)
                _resWidth = start - stop
                resT = Bits(width=_resWidth, forceVector=True,
                            signed=st.signed)

        elif isinstance(key, IntegerVal):
            # check index range
            _v = int(key)
            if _v < 0 or _v > length - 1:
                raise IndexError(_v)

            resT = BIT
            if iamVal:
                return self._getitem__val(key)

        elif isinstance(key, RtlSignalBase):
            t = key._dtype
            if isinstance(t, Integer):
                resT = BIT
            elif isinstance(t, Slice):
                resT = Bits(width=key.staticEval()._size(),
                            forceVector=st.forceVector, signed=st.signed)
            elif isinstance(t, Bits):
                resT = BIT
                key = key._auto_cast(INT)
            else:
                raise TypeError(
                    "Index operation not implemented"
                    " for index of type %r" % (t))

        else:
            raise TypeError(
                "Index operation not implemented for index %r" % (key))

        return Operator.withRes(AllOps.INDEX, [self, key], resT)

    @internal
    def _setitem__val(self, index, value):
        if index._isFullVld():
            if index._dtype == SLICE:
                size = index._size()
                noOfFirstBit = index.val[1].val
                self.val = setBitRange(self.val, noOfFirstBit, size, value.val)
                self.vldMask = setBitRange(
                    self.vldMask, noOfFirstBit, size, value.vldMask)
            elif isinstance(index._dtype, Integer):
                self.val = bitSetTo(self.val, index.val, value.val)
                self.vldMask = bitSetTo(self.vldMask, index.val, value.vldMask)
            else:
                raise TypeError("Not implemented for index %r" % (index))
            self.updateTime = max(index.updateTime, value.updateTime)
        else:
            self.vldMask = 0

    def __setitem__(self, index, value):
        """this can not be called in desing description on non static values,
        only simulator can resolve this (in design use self[index] ** value
        instead of self[index] = value)
        """
        # convert index to hSlice or hInt
        indexConst = True
        if not isinstance(index, Value):
            if isinstance(index, RtlSignalBase):
                if index._const:
                    index = index.staticEval()
                else:
                    indexConst = False

            elif isinstance(index, slice):
                length = self._dtype.bit_length()
                index = slice_to_SLICE(index, length)
            else:
                index = hInt(index)
        if indexConst and not index._isFullVld():
            indexConst = False

        # convert value to bits of length specified by index
        if indexConst:
            if index._dtype == SLICE:
                itemT = Bits(index._size())
            else:
                itemT = BIT

            if not isinstance(value, Value):
                if isinstance(value, RtlSignalBase):
                    if value._const:
                        value = value.staticEval()._auto_cast(itemT)
                        valueConst = True
                    else:
                        valueConst = False
                else:
                    value = itemT.fromPy(value)
                    valueConst = True
            else:
                valueConst = True
                value = value._auto_cast(itemT)

        if indexConst and valueConst and isinstance(self, Value):
            return self._setitem__val(index, value)

        raise TypeError(
            "Only simulator can resolve []= for signals or invalid index")

    @internal
    def _invert__val(self):
        v = self.clone()
        v.val = ~v.val
        w = v._dtype.bit_length()
        v.val &= mask(w)
        if self._dtype.signed:
            v.val = signFix(v.val, w)
        return v

    def __invert__(self):
        if isinstance(self, Value):
            return self._invert__val()
        else:
            try:
                # double negation
                d = self.singleDriver()
                if isinstance(d, Operator) and d.operator == AllOps.NOT:
                    return d.operands[0]
            except (MultipleDriversErr, NoDriverErr):
                pass
            return Operator.withRes(AllOps.NOT, [self], self._dtype)

    # comparisons
    @internal
    def _eq__val(self, other):
        return bitsCmp__val(self, other, AllOps.EQ, eq)

    def _eq(self, other):
        return bitsCmp(self, other, AllOps.EQ, eq)

    @internal
    def _ne__val(self, other):
        return bitsCmp__val(self, other, AllOps.NEQ, ne)

    def __ne__(self, other):
        return bitsCmp(self, other, AllOps.NEQ)

    @internal
    def _lt__val(self, other):
        return bitsCmp__val(self, other, AllOps.LT, lt)

    def __lt__(self, other):
        return bitsCmp(self, other, AllOps.LT)

    @internal
    def _gt__val(self, other):
        return bitsCmp__val(self, other, AllOps.GT, gt)

    def __gt__(self, other):
        return bitsCmp(self, other, AllOps.GT)

    @internal
    def _ge__val(self, other):
        return bitsCmp__val(self, other, AllOps.GE, ge)

    def __ge__(self, other):
        return bitsCmp(self, other, AllOps.GE)

    @internal
    def _le__val(self, other):
        return bitsCmp__val(self, other, AllOps.LE, le)

    def __le__(self, other):
        return bitsCmp(self, other, AllOps.LE)

    @internal
    def _xor__val(self, other):
        return bitsBitOp__val(self, other, AllOps.XOR, vldMaskForXor)

    def __xor__(self, other):
        return bitsBitOp(self, other, AllOps.XOR, vldMaskForXor, tryReduceXor)

    @internal
    def _and__val(self, other):
        return bitsBitOp__val(self, other, AllOps.AND, vldMaskForAnd)

    def __and__(self, other):
        return bitsBitOp(self, other, AllOps.AND, vldMaskForAnd, tryReduceAnd)

    @internal
    def _or__val(self, other):
        return bitsBitOp__val(self, other, AllOps.OR, vldMaskForOr)

    def __or__(self, other):
        return bitsBitOp(self, other, AllOps.OR, vldMaskForOr, tryReduceOr)

    @internal
    def _sub__val(self, other):
        return bitsArithOp__val(self, other, AllOps.SUB)

    def __sub__(self, other):
        return bitsArithOp(self, other, AllOps.SUB)

    @internal
    def _add__val(self, other):
        return bitsArithOp__val(self, other, AllOps.ADD)

    def __add__(self, other):
        return bitsArithOp(self, other, AllOps.ADD)

    @internal
    def _mul__val(self, other):
        # [TODO] resT should be wider
        resT = self._dtype
        v = self.val * other.val
        v &= resT.all_mask()
        if resT.signed:
            v = signFix(v, resT.bit_length())

        result = resT.fromPy(v)
        if not self._isFullVld() or not other._isFullVld():
            result.vldMask = 0
        result.updateTime = max(self.updateTime, other.updateTime)
        return result

    def __mul__(self, other):
        other = toHVal(other)
        if not isinstance(other._dtype, (Integer, Bits)):
            raise TypeError(other)

        if areValues(self, other):
            return self._mul__val(other)
        else:
            myT = self._dtype
            if self._dtype.signed is None:
                self = self._unsigned()

            if isinstance(other._dtype, Bits):
                s = other._dtype.signed
                if s is None:
                    other = other._unsigned()

            elif isinstance(other._dtype, Integer):
                pass
            else:
                raise TypeError("%r %r %r" % (self, AllOps.MUL, other))

            if isinstance(other._dtype, Integer):
                res_w = myT.bit_length() * 2
                res_sign = self._dtype.signed
            else:
                res_w = myT.bit_length() + other._dtype.bit_length()
                res_sign = self._dtype.signed or other._dtype.signed

            subResT = Bits(res_w, signed=res_sign)
            o = Operator.withRes(AllOps.MUL, [self, other], subResT)
            resT = Bits(res_w, signed=myT.signed)
            return o._auto_cast(resT)


from typing import Tuple

from hwt.doc_markers import internal
from hwt.hdl.constants import DIRECTION, SENSITIVITY
from hwt.hdl.process import HWProcess
from hwt.hdl.value import Value
from hwt.simulator.utils import valueHasChanged

@internal
def sensitivity(proc: HWProcess, *sensitiveTo):
    """
    register sensitivity for process
    """
    for s in sensitiveTo:
        if isinstance(s, tuple):
            sen, s = s
            if sen == SENSITIVITY.ANY:
                s.simSensProcs.add(proc)
            elif sen == SENSITIVITY.RISING:
                s.simRisingSensProcs.add(proc)
            elif sen == SENSITIVITY.FALLING:
                s.simFallingSensProcs.add(proc)
            else:
                raise AssertionError(sen)
        else:
            s.simSensProcs.add(proc)

@internal
def simEvalCond(simulator, *conds):
    """
    Evaluate list of values as condition
    """
    _cond = True
    _vld = True
    for v in conds:
        val = bool(v.val)
        fullVld = v.vldMask == 1
        if fullVld:
            if not val:
                return False, True
        else:
            return False, False

        _cond = _cond and val
        _vld = _vld and fullVld

    return _cond, _vld

@internal
class SimModel(object):
    """
    Base class for model in simulator
    """

@internal
def connectSimPort(simUnit, subSimUnit, srcName, dstName, direction):
    """
    Connect ports of simulation models by name
    """
    if direction == DIRECTION.OUT:
        origPort = getattr(subSimUnit, srcName)
        newPort = getattr(simUnit, dstName)
        setattr(subSimUnit, srcName, newPort)
    else:
        origPort = getattr(subSimUnit, dstName)
        newPort = getattr(simUnit, srcName)
        setattr(subSimUnit, dstName, newPort)

    subSimUnit._ctx.signals.remove(origPort)

@internal
def mkUpdater(nextVal: Value, invalidate: bool):
    """
    Create value updater for simulation

    :param nextVal: instance of Value which will be asssiggned to signal
    :param invalidate: flag which tells if value has been compromised
        and if it should be invaidated
    :return: function(value) -> tuple(valueHasChangedFlag, nextVal)
    """

    def updater(currentVal):
        _nextVal = nextVal.clone()
        if invalidate:
            _nextVal.vldMask = 0
        return (valueHasChanged(currentVal, _nextVal), _nextVal)
    return updater

@internal
def mkArrayUpdater(nextItemVal: Value, indexes: Tuple[Value],
                   invalidate: bool):
    """
    Create value updater for simulation for value of array type

    :param nextVal: instance of Value which will be asssiggned to signal
    :param indexes: tuple on indexes where value should be updated
        in target array

    :return: function(value) -> tuple(valueHasChangedFlag, nextVal)
    """
    def updater(currentVal):
        if len(indexes) > 1:
            raise NotImplementedError("[TODO] implement for more indexes")

        _nextItemVal = nextItemVal.clone()
        if invalidate:
            _nextItemVal.vldMask = 0

        index = indexes[0]
        change = valueHasChanged(currentVal._getitem__val(index), _nextItemVal)
        currentVal._setitem__val(index, _nextItemVal)
        return (change, currentVal)

    return updater


from hwt.hdl.operator import Operator
from hwt.hdl.operatorDefs import AllOps
from hwt.hdl.types.defs import BOOL, INT
from hwt.hdl.types.slice import Slice
from hwt.hdl.types.typeCast import toHVal
from hwt.hdl.value import Value
from hwt.synthesizer.param import evalParam
from hwt.synthesizer.rtlLevel.mainBases import RtlSignalBase
from hwt.doc_markers import internal

class HArrayVal(Value):
    """
    Class for values of array HDL type
    """

    @classmethod
    def fromPy(cls, val, typeObj, vldMask=None):
        """
        :param val: None or dictionary {index:value} or iterrable of values
        :param vldMask: if is None validity is resolved from val
            if is 0 value is invalidated
            if is 1 value has to be valid
        """
        size = evalParam(typeObj.size)
        if isinstance(size, Value):
            size = int(size)

        elements = {}
        if vldMask == 0:
            val = None

        if val is None:
            pass
        elif isinstance(val, dict):
            for k, v in val.items():
                if not isinstance(k, int):
                    k = int(k)
                elements[k] = typeObj.elmType.fromPy(v)
        else:
            for k, v in enumerate(val):
                if isinstance(v, RtlSignalBase):  # is signal
                    assert v._dtype == typeObj.elmType
                    e = v
                else:
                    e = typeObj.elmType.fromPy(v)
                elements[k] = e

        _mask = int(bool(val))
        if vldMask is None:
            vldMask = _mask
        else:
            assert (vldMask == _mask)

        return cls(elements, typeObj, vldMask)

    def toPy(self):
        if not self._isFullVld():
            raise ValueError("Value of %r is not fully defined" % self)
        return [v.toPy() for _, v in sorted(self.val.items())]

    @internal
    def __hash__(self):
        return hash((self._dtype, self.updateTime))
        # return hash((self._dtype, self.val, self.vldMask, self.updateTime))

    def _isFullVld(self):
        return self.vldMask == 1

    @internal
    def _getitem__val(self, key):
        """
        :atention: this will clone item from array, iterate over .val
            if you need to modify items
        """
        try:
            kv = key.val
            if not key._isFullVld():
                raise KeyError()
            else:
                if kv >= self._dtype.size:
                    raise KeyError()

            return self.val[kv].clone()
        except KeyError:
            return self._dtype.elmType.fromPy(None)

    def __getitem__(self, key):
        iamVal = isinstance(self, Value)
        key = toHVal(key)
        isSLICE = isinstance(key, Slice.getValueCls())

        if isSLICE:
            raise NotImplementedError()
        elif isinstance(key, RtlSignalBase):
            key = key._auto_cast(INT)
        elif isinstance(key, Value):
            pass
        else:
            raise NotImplementedError(
                "Index operation not implemented for index %r" % (key))

        if iamVal and isinstance(key, Value):
            return self._getitem__val(key)

        return Operator.withRes(AllOps.INDEX, [self, key], self._dtype.elmType)

    @internal
    def _setitem__val(self, index, value):
        self.updateTime = max(index.updateTime, value.updateTime)
        if index._isFullVld():
            self.val[index.val] = value.clone()
        else:
            self.val = {}

    def __setitem__(self, index, value):
        """
        Only syntax sugar for user, not used inside HWT

        * In HW design is not used (__getitem__ returns "reference"
            and it is used)

        * In simulator is used _setitem__val directly
        """
        if isinstance(index, int):
            index = INT.fromPy(index)
        else:
            assert isinstance(self, Value)
            assert index._dtype == INT, index._dtype

        if not isinstance(value, Value):
            value = self._dtype.elmType.fromPy(value)
        else:
            assert value._dtype == self._dtype.elmType, (
                value._dtype, self._dtype.elmType)

        return self._setitem__val(index, value)

    def __iter__(self):
        mySize = len(self)
        def it():
            for i in range(mySize):
                yield self[i]

        return it()

    def __len__(self):
        return int(self._dtype.size)

    @internal
    def _eq__val(self, other):
        assert self._dtype.elmType == other._dtype.elmType
        assert self._dtype.size == other._dtype.size

        eq = True
        vld = 1
        updateTime = -1
        keysA = set(self.val)
        keysB = set(other.val)
        sharedKeys = keysA.union(keysB)

        lsh = len(sharedKeys)
        if (lsh == int(self._dtype.size)
                and len(keysA) == lsh
                and len(keysB) == lsh):
            for k in sharedKeys:
                a = self.val[k]
                b = other.val[k]

                eq = eq and a == b
                if not eq:
                    break
                vld = vld & a.vldMask & b.vldMask
                updateTime = max(updateTime, a.updateTime, b.updateTime)
        else:
            eq = False
            vld = 0

        return BOOL.getValueCls()(eq, BOOL, vld, updateTime)

    def _eq(self, other):
        assert isinstance(other, HArrayVal)
        return self._eq__val(other)


from hwt.hdl.types.bits import Bits
from hwt.hdl.types.defs import INT, BOOL, STR, BIT

def hInt(pyVal):
    """ create hdl integer value (for example integer value in vhdl)"""
    return INT.fromPy(pyVal)

def hBool(pyVal):
    """ create hdl bool value (for example bool value in vhdl)"""
    return BOOL.fromPy(pyVal)

def hStr(pyVal):
    """create hdl string value (for example string value in vhdl)"""
    return STR.fromPy(pyVal)

def hBit(pyVal):
    """create hdl bit value (for example STD_LOGIC value in vhdl)"""
    return BIT.fromPy(pyVal)

def vec(val, width, signed=None):
    """create hdl vector value"""
    return Bits(width, signed, forceVector=True).fromPy(val)


from collections import deque

from hwt.hdl.constants import NOP
from hwt.simulator.agentBase import SyncAgentBase

class HandshakedAgent(SyncAgentBase):
    """
    Simulation/verification agent for :class:`hwt.interfaces.std.Handshaked`
    interface there is onMonitorReady(simulator)
    and onDriverWriteAck(simulator) unimplemented method
    which can be used for interfaces with bi-directional data streams

    :attention: requires clk and rst/rstn signal
        (if you do not have any create simulation wrapper with it)
    """

    def __init__(self, intf, allowNoReset=False):
        super().__init__(intf, allowNoReset=allowNoReset)
        self.actualData = NOP
        self.data = deque()
        # these signals are extracted like this to make
        # agent more configurable
        self._rd = self.getRd()
        self._vld = self.getVld()

        # tmp variables to keep track of last send values to simulation
        self._lastWritten = None
        self._lastRd = None
        self._lastVld = None
        # callbacks
        self._afterRead = None

    def setEnable_asDriver(self, en, sim):
        super(HandshakedAgent, self).setEnable_asDriver(en, sim)
        if not en:
            self.wrVld(sim.write, 0)
            self._lastVld = 0

    def setEnable_asMonitor(self, en, sim):
        super(HandshakedAgent, self).setEnable_asMonitor(en, sim)
        if not en:
            self.wrRd(sim.write, 0)
            self._lastRd = 0

    def getRd(self):
        """get "ready" signal"""
        return self.intf.rd._sigInside

    def isRd(self, readFn):
        """
        get value of "ready" signal
        """
        return readFn(self._rd)

    def wrRd(self, wrFn, val):
        wrFn(val, self._rd)

    def getVld(self):
        """get "valid" signal"""
        return self.intf.vld._sigInside

    def isVld(self, readFn):
        """
        get value of "valid" signal, override f.e. when you
        need to use signal with reversed polarity
        """
        return readFn(self._vld)

    def wrVld(self, wrFn, val):
        wrFn(val, self._vld)

    def monitor(self, sim):
        """
        Collect data from interface
        """
        r = sim.read
        if self.notReset(sim):
            # update rd signal only if required
            if self._lastRd is not 1:
                self.wrRd(sim.write, 1)
                self._lastRd = 1

                # try to run onMonitorReady if there is any
                try:
                    onMonitorReady = self.onMonitorReady
                except AttributeError:
                    onMonitorReady = None

                if onMonitorReady is not None:
                    onMonitorReady(sim)

            # wait for response of master
            yield sim.waitOnCombUpdate()
            vld = self.isVld(r)
            assert vld.vldMask, (sim.now, self.intf,
                                 "vld signal is in invalid state")

            if vld.val:
                # master responded with positive ack, do read data
                d = self.doRead(sim)
                if self._debugOutput is not None:
                    self._debugOutput.write(
                        "%s, read, %d: %r\n" % (
                            self.intf._getFullName(),
                            sim.now, d))
                self.data.append(d)
                if self._afterRead is not None:
                    self._afterRead(sim)
        else:
            if self._lastRd is not 0:
                # can not receive, say it to masters
                self.wrRd(sim.write, 0)
                self._lastRd = 0

    def doRead(self, sim):
        """extract data from interface"""
        return sim.read(self.intf.data)

    def doWrite(self, sim, data):
        """write data to interface"""
        sim.write(data, self.intf.data)

    def checkIfRdWillBeValid(self, sim):
        yield sim.waitOnCombUpdate()
        rd = self.isRd(sim.read)
        assert rd.vldMask, (sim.now, self.intf, "rd signal in invalid state")

    def driver(self, sim):
        """
        Push data to interface

        set vld high and wait on rd in high then pass new data
        """
        r = sim.read

        # pop new data if there are not any pending
        if self.actualData is NOP and self.data:
            self.actualData = self.data.popleft()

        doSend = self.actualData is not NOP

        # update data on signals if is required
        if self.actualData is not self._lastWritten:
            if doSend:
                self.doWrite(sim, self.actualData)
            else:
                self.doWrite(sim, None)
            self._lastWritten = self.actualData

        en = self.notReset(sim)
        vld = int(en and doSend)
        if self._lastVld is not vld:
            self.wrVld(sim.write, vld)
            self._lastVld = vld

        if not self._enabled:
            # we can not check rd it in this function because we can not wait
            # because we can be reactivated in this same time
            sim.add_process(self.checkIfRdWillBeValid(sim))
            return

        # wait of response of slave
        yield sim.waitOnCombUpdate()

        rd = self.isRd(r)
        assert rd.vldMask, (sim.now, self.intf,
                            "rd signal in invalid state")
        if not vld:
            return

        if rd.val:
            # slave did read data, take new one
            if self._debugOutput is not None:
                self._debugOutput.write("%s, wrote, %d: %r\n" % (
                    self.intf._getFullName(),
                    sim.now,
                    self.actualData))

            a = self.actualData
            # pop new data, because actual was read by slave
            if self.data:
                self.actualData = self.data.popleft()
            else:
                self.actualData = NOP

            # try to run onDriverWriteAck if there is any
            onDriverWriteAck = getattr(self, "onDriverWriteAck", None)
            if onDriverWriteAck is not None:
                onDriverWriteAck(sim)

            onDone = getattr(a, "onDone", None)
            if onDone is not None:
                onDone(sim)

class HandshakeSyncAgent(HandshakedAgent):
    """
    Simulation/verification agent for HandshakedSycn interface

    :attention: there is no data channel on this interface
        it is synchronization only and it actually does not have
        any meaningful data collected data in monitor
        mode are just values of simulation time when item was collected
    """

    def doWrite(self, sim, data):
        pass

    def doRead(self, sim):
        return sim.now

class HandshakedReadListener():
    def __init__(self, hsAgent: HandshakedAgent):
        self.original_afterRead = hsAgent._afterRead
        hsAgent._afterRead = self._afterReadWrap
        self.agent = hsAgent
        self.callbacks = {}

    def _afterReadWrap(self, sim):
        if self.original_afterRead is not None:
            self.original_afterRead(sim)
        try:
            cb = self.callbacks.pop(len(self.agent.data))
        except KeyError:
            return
        cb(sim)

    def register(self, transCnt, callback):
        self.callbacks[transCnt] = callback


from hwt.hdl.architecture import Architecture
from hwt.hdl.entity import Entity
from hwt.hdl.process import HWProcess
from hwt.serializer.generic.serializer import GenericSerializer
from hwt.serializer.resourceAnalyzer.utils import ResourceContext
from hwt.hdl.types.array import HArray
from hwt.hdl.statements import HdlStatement
from hwt.hdl.assignment import Assignment
from hwt.synthesizer.rtlLevel.rtlSignal import RtlSignal
from hwt.hdl.operator import Operator, isConst
from hwt.hdl.operatorDefs import AllOps
from hwt.hdl.value import Value
from hwt.synthesizer.rtlLevel.netlist import walk_assignments
from hwt.hdl.switchContainer import SwitchContainer
from itertools import chain
from hwt.doc_markers import internal

@internal
def _count_mux_inputs_for_outputs(stm: HdlStatement, cnt):
    if isinstance(stm, Assignment):
        cnt[stm.dst] += 1
    else:
        for _stm in stm._iter_stms():
            if isinstance(_stm, Assignment):
                cnt[_stm.dst] += 1
            else:
                _count_mux_inputs_for_outputs(_stm, cnt)

@internal
def count_mux_inputs_for_outputs(stm):
    cnt = {o: 0 for o in stm._outputs}
    _count_mux_inputs_for_outputs(stm, cnt)
    return cnt

IGNORED_OPERATORS = {
    AllOps.BitsAsSigned,
    AllOps.BitsAsUnsigned,
    AllOps.BitsAsVec,
    AllOps.BitsToInt,
    AllOps.RISING_EDGE,
    AllOps.FALLING_EDGE,
}

class ResourceAnalyzer(GenericSerializer):
    """
    Serializer which does not products any output just collect informations
    about used resources

    :attention: Use instance of ResourceAnalyzer instead of class
    """
    _keywords_dict = {}

    def __init__(self):
        self.context = ResourceContext(None)

    @internal
    @classmethod
    def HWProcess_operators(cls, sig: RtlSignal, ctx: ResourceContext, synchronous):
        seen = ctx.seen
        for d in sig.drivers:
            if (not isinstance(d, Operator)
                    or d in seen):
                continue

            skip_op = d.operator in IGNORED_OPERATORS
            if not skip_op:
                if d.operator == AllOps.EQ:
                    o1 = d.operands[1]
                    if (isinstance(o1, Value)
                            and o1._dtype.bit_length() == 1
                            and o1.val):
                        # to bool conversion
                        skip_op = True
                elif d.operator == AllOps.INDEX:
                    o1 = d.operands[1]
                    skip_op = True
                    if isConst(o1):
                        # constant signal silice
                        pass
                    else:
                        o0 = d.operands[0]
                        if isinstance(o0._dtype, HArray):
                            ctx.registerRAM_read_port(o0, o1, synchronous)
                        else:
                            ctx.registerMUX(d, sig, 2)
                elif d.operator == AllOps.TERNARY:
                    o1 = d.operands[1]
                    o2 = d.operands[2]
                    if (isConst(o1)
                            and bool(o1)
                            and isConst(o2)
                            and not bool(o2)):
                        # to bit conversion
                        skip_op = True
                    else:
                        raise NotImplementedError("Ternary as mux")

            if not skip_op:
                ctx.registerOperator(d)

            for op in d.operands:
                if (not isinstance(op, RtlSignal)
                        or not op.hidden
                        or op in seen):
                    continue
                cls.HWProcess_operators(op, ctx, synchronous)

    @classmethod
    def HWProcess(cls, proc: HWProcess, ctx: ResourceContext) -> None:
        """
        Gues resource usage by HWProcess
        """
        seen = ctx.seen
        for stm in proc.statements:
            encl = stm._enclosed_for
            full_ev_dep = stm._is_completly_event_dependent
            now_ev_dep = stm._now_is_event_dependent
            ev_dep = full_ev_dep or now_ev_dep

            out_mux_dim = count_mux_inputs_for_outputs(stm)
            for o in stm._outputs:
                if o in seen:
                    continue

                i = out_mux_dim[o]
                if isinstance(o._dtype, HArray):
                    assert i == 1, (o, i, " only one ram port per HWProcess")
                    for a in walk_assignments(stm, o):
                        assert len(a.indexes) == 1, "one address per RAM port"
                        addr = a.indexes[0]
                    ctx.registerRAM_write_port(o, addr, ev_dep)
                elif ev_dep:
                    ctx.registerFF(o)
                    if i > 1:
                        ctx.registerMUX(stm, o, i)
                elif o not in encl:
                    ctx.registerLatch(o)
                    if i > 1:
                        ctx.registerMUX(stm, o, i)
                elif i > 1:
                    ctx.registerMUX(stm, o, i)
                else:
                    # just a connection
                    continue

            if isinstance(stm, SwitchContainer):
                caseEqs = set([stm.switchOn._eq(c[0]) for c in stm.cases])
                inputs = chain(
                    [sig for sig in stm._inputs if sig not in caseEqs], [stm.switchOn])
            else:
                inputs = stm._inputs

            for i in inputs:
                # discover only internal signals in this statements for
                # operators
                if not i.hidden or i in seen:
                    continue

                cls.HWProcess_operators(i, ctx, ev_dep)

    @classmethod
    def Entity(cls, ent: Entity, ctx: ResourceContext) -> None:
        """
        Entity is just header, we do not need to inspect it for resources
        """
        return

    def getBaseContext(self) -> ResourceContext:
        """
        Return context for collecting of resource informatins
        prepared on this instance
        """
        return self.context

    @classmethod
    def Architecture(cls, arch: Architecture, ctx: ResourceContext) -> None:
        for c in arch.componentInstances:
            raise NotImplementedError()

        for proc in arch.processes:
            cls.HWProcess(proc, ctx)

        # [TODO] constant to ROMs

        ctx.finalize()

    def report(self):
        return self.context.resources


from hwt.doc_markers import internal
from hwt.hdl.types.hdlType import HdlType
from hwt.synthesizer.rtlLevel.mainBases import RtlSignalBase

class HArray(HdlType):
    """
    HDL array type

    :ivar elmType: type of elements
    :ivar size: number of items
    """

    def __init__(self, elmType, size):
        super(HArray, self).__init__()
        self.elmType = elmType
        self.size = size

    def __eq__(self, other):
        return self is other or (
            type(self) is type(other) and
            self.size == other.size and
            self.elmType == other.elmType
        )

    @internal
    def __hash__(self):
        return hash(id(self))

    def bit_length(self):
        """
        :return: bit width for this type
        """
        try:
            itemSize = self.elmType.bit_length
        except AttributeError:
            itemSize = None
        if itemSize is None:
            raise TypeError(
                "Can not determine size of array because item has"
                " not determinable size")

        s = self.size
        if isinstance(s, RtlSignalBase):
            s = int(s.staticEval())
        return s * itemSize()

    @internal
    @classmethod
    def getValueCls(cls):
        try:
            return cls._valCls
        except AttributeError:
            from hwt.hdl.types.arrayVal import HArrayVal
            cls._valCls = HArrayVal
            return cls._valCls

    @internal
    @classmethod
    def get_reinterpret_cast_fn(cls):
        from hwt.hdl.types.arrayCast import reinterpret_cast_harray
        return reinterpret_cast_harray

    def __repr__(self, indent=0, withAddr=None, expandStructs=False):
        """
        :param indent: number of indentation
        :param withAddr: if is not None is used as a additional
            information about on which address this type is stored
            (used only by HStruct)
        :param expandStructs: expand HStructTypes (used by HStruct and Array)
        """
        return "%s[%r]" % (self.elmType.__repr__(indent=indent,
                                                 withAddr=withAddr,
                                                 expandStructs=expandStructs),
                           self.size)


#!/usr/bin/env python3
# -*- coding: utf-8 -*-

from typing import Generator

from hwt.doc_markers import internal
from hwt.hdl.sensitivityCtx import SensitivityCtx
from hwt.hdl.types.typeCast import toHVal
from hwt.synthesizer.rtlLevel.mainBases import RtlSignalBase
from hwt.synthesizer.rtlLevel.rtlSignal import RtlSignal

class Param(RtlSignal):
    """
    Class used in same way as generics in VHDL, it is wrapper around the value

    :ivar _val: actual value of paramter
    :ivar _scopes: name scopes for resolving of parameter name in specified unit
    """

    def __init__(self, initval):
        initval = toHVal(initval)
        super(Param, self).__init__(None, None, initval._dtype,
                                    defVal=initval)
        self._val = initval
        self.replacedWith = None
        self._parent = None
        self.__isReadOnly = False
        # {unit: (ctx, name)}
        self._scopes = {}
        self._const = True
        self.hidden = False

    @internal
    def _registerScope(self, name, unit):
        self._scopes[unit] = (unit._ctx, name)

    def getName(self, where):
        return self._scopes[where][1]

    def setReadOnly(self):
        self.__isReadOnly = True

    def get(self):
        if self.replacedWith is not None:
            raise Exception("Trying to read param '%r' which is already"
                            " replaced by '%r'" % (self, self.replacedWith))
        return self._val

    def set(self, val):
        """
        set value of this param
        """
        assert not self.__isReadOnly, \
            ("This parameter(%s) was locked"
             " and now it can not be changed" % self.name)
        assert self.replacedWith is None, \
            ("This param was replaced with new one and this "
             "should not exists")

        val = toHVal(val)
        self.defVal = val
        self._val = val.staticEval()
        self._dtype = self._val._dtype

    def __call__(self, val):
        raise ValueError("Use .set() to set value of parameter instances")

    def __repr__(self):
        val = "InvalidVal"
        try:
            val = self.get()
        except Exception:
            pass

        try:
            name = self.name
        except AttributeError:
            name = ""

        return "<%s, name=%s, val=%r>" % (self.__class__.__name__, name, val)

    def __bool__(self):
        v = evalParam(self)
        assert v._isFullVld()
        return bool(v.val)

    def __int__(self):
        v = evalParam(self)
        assert v._isFullVld()
        return int(v.val)

    @internal
    def _walk_sensitivity(self, casualSensitivity: set, seen: set, ctx: SensitivityCtx)\
            -> Generator["RtlSignal", None, None]:
        seen.add(self)
        yield from []

    @internal
    def _walk_public_drivers(self, seen: set) -> Generator["RtlSignal", None, None]:
        seen.add(self)
        yield from []

def evalParam(p):
    """
    Get value of parameter
    """
    while isinstance(p, Param):
        p = p.get()

    if isinstance(p, RtlSignalBase):
        return p.staticEval()
        # use rather param inheritance instead of param as param value
    return toHVal(p)

def getParam(p):
    """
    get param value or value
    getParam gets only item which is specified as param value,
    but evalParam also evaluates it if it is not value
    """
    if isinstance(p, Param):
        p = p.get()
        # use rather param inheritance instead of param as param value
    return p


from hwt.hdl.constants import DIRECTION
from hwt.hdl.types.bits import Bits
from hwt.hdl.types.hdlType import HdlType
from hwt.hdl.types.struct import HStruct, HStructField, HStructFieldMeta
from hwt.interfaces.agents.structIntf import StructIntfAgent
from hwt.interfaces.std import Signal, VldSynced, RegCntrl, BramPort_withoutClk
from hwt.synthesizer.interface import Interface
from hwt.synthesizer.interfaceLevel.mainBases import InterfaceBase
from hwt.synthesizer.interfaceLevel.unitImplHelpers import getSignalName
from hwt.synthesizer.rtlLevel.mainBases import RtlSignalBase
from hwt.doc_markers import internal

class StructIntf(Interface):
    """
    Create dynamic interface based on HStruct or HUnion description

    :ivar _fieldsToInterfaces: dictionary {field from HStruct template:
        sub interface for it}
    :ivar _structT: HStruct instance used as template for this interface
    :param _instantiateFieldFn: function(FieldTemplateItem instance)
        return interface instance
    """

    def __init__(self, structT, instantiateFieldFn,
                 masterDir=DIRECTION.OUT,
                 loadConfig=True):
        Interface.__init__(self,
                           masterDir=masterDir,
                           loadConfig=loadConfig)
        self._structT = structT
        self._instantiateFieldFn = instantiateFieldFn
        self._fieldsToInterfaces = {}

    def _declr(self):
        _t = self._structT
        if isinstance(_t, HStruct):
            fields = _t.fields
        else:
            fields = _t.fields.values()

        self._fieldsToInterfaces[self._structT] = self

        for field in fields:
            # skip padding
            if field.name is not None:
                # generate interface based on struct field
                intf = self._instantiateFieldFn(self, field)
                assert  field not in self._fieldsToInterfaces
                self._fieldsToInterfaces[field] = intf
                setattr(self, field.name, intf)

                if isinstance(intf, StructIntf):
                    intf._fieldsToInterfaces = self._fieldsToInterfaces

    def _initSimAgent(self):
        self._ag = StructIntfAgent(self)

class IntfMap(list):
    """
    Container of interface map

    Items can be Interface/RtlSignal or (type/interface/None/IntfMap, name).
    None is used for padding.
    """
    pass

@internal
def _HTypeFromIntfMap(intf):
    name = getSignalName(intf)
    if isinstance(intf, (RtlSignalBase, Signal)):
        dtype = intf._dtype
    elif isinstance(intf, VldSynced):
        dtype = intf.data._dtype
    elif isinstance(intf, RegCntrl):
        dtype = intf.din._dtype
    elif isinstance(intf, BramPort_withoutClk):
        dtype = Bits(int(intf.DATA_WIDTH))[2 ** int(intf.ADDR_WIDTH)]
    else:
        dtype, name = intf
        assert isinstance(dtype, HdlType)
        assert isinstance(name, str)

    return (dtype, name)

@internal
def HTypeFromIntfMapItem(interfaceMapItem):
    isTerminal = False
    if isinstance(interfaceMapItem, (InterfaceBase, RtlSignalBase)):
        dtype, nameOrPrefix = _HTypeFromIntfMap(interfaceMapItem)
        isTerminal = True
    else:
        typeOrListOfInterfaces, nameOrPrefix = interfaceMapItem

        if isinstance(typeOrListOfInterfaces, list) and not isinstance(typeOrListOfInterfaces, IntfMap):
            # list of HType instances for array
            parts = []
            arrayItem_t = None

            for item in typeOrListOfInterfaces:
                if isinstance(item, IntfMap):
                    t = HTypeFromIntfMap(item)
                else:
                    t = HTypeFromIntfMapItem(item).dtype
                if arrayItem_t is None:
                    arrayItem_t = t
                else:
                    assert arrayItem_t == t, (
                        "all items in array has to have same type", arrayItem_t, t)
                parts.append(t)

            dtype = arrayItem_t[len(parts)]

        elif isinstance(typeOrListOfInterfaces, HdlType):
            dtype = typeOrListOfInterfaces
            isTerminal = True
        elif isinstance(typeOrListOfInterfaces,
                        (InterfaceBase, RtlSignalBase)):
            # renamed interface, ignore original name
            dtype = _HTypeFromIntfMap(typeOrListOfInterfaces)[0]
            isTerminal = True
        elif isinstance(typeOrListOfInterfaces, IntfMap):
            dtype = HTypeFromIntfMap(typeOrListOfInterfaces)
        else:
            # tuple (tuple of interfaces, prefix)
            assert isinstance(typeOrListOfInterfaces,
                              tuple), typeOrListOfInterfaces
            dtype = HTypeFromIntfMap(typeOrListOfInterfaces)

    assert isinstance(nameOrPrefix, str) or nameOrPrefix is None, nameOrPrefix

    f = HStructField(dtype, nameOrPrefix)

    if not isTerminal:
        f.meta = HStructFieldMeta(split=True)

    return f

def HTypeFromIntfMap(interfaceMap):
    """
    Generate flattened register map for HStruct

    :param interfaceMap: sequence of
        tuple (type, name) or (will create standard struct field member)
        interface or (will create a struct field from interface)
        instance of hdl type (is used as padding)
        tuple (list of interface, name)
    :param DATA_WIDTH: width of word
    :param terminalNodes: None or set whre are placed StructField instances
        which are derived directly from interface
    :return: generator of tuple (type, name, BusFieldInfo)
    """
    structFields = []

    for m in interfaceMap:
        f = HTypeFromIntfMapItem(m)
        structFields.append(f)

    return HStruct(*structFields)


from typing import Union

from hwt.hdl.operator import Operator
from hwt.serializer.resourceAnalyzer.resourceTypes import \
    ResourceFF, ResourceMUX, ResourceLatch, ResourceRAM
from hwt.synthesizer.unit import Unit
from hwt.hdl.statements import HdlStatement
from hwt.synthesizer.rtlLevel.rtlSignal import RtlSignal

class ResourceContext():
    """
    Container of informations about resources used in architecture

    :ivar unit: optional unit for which is this context build
    :ivar seen: set of seen objects
    :ivar resources: dictionary {type of resource: cnt}
    :ivar discoveredRamSignals: set of signals which seems to be some kind
        of RAM/ROM memory
    """

    def __init__(self, unit: Unit):
        self.unit = unit
        self.seen = set()
        self.resources = {}
        # {RtlSignal or Statement or Operator: HwResource or typle (HwResource, cnt)}
        self.resource_for_object = {}
        # {(mem, addr, syncFlag, r/w): cnt}
        self.memories = {}

    def registerOperator(self, op: Operator):
        w = op.operands[0]._dtype.bit_length()
        res = self.resources
        k = (op.operator, w)
        res[k] = res.get(k, 0) + 1

        self.resource_for_object[op] = k

    def registerMUX(self, stm: Union[HdlStatement, Operator], sig: RtlSignal,
                    inputs_cnt: int):
        """
        mux record is in format (self.MUX, n, m)
        where n is number of bits of this mux
        and m is number of possible inputs
        """
        assert inputs_cnt > 1
        res = self.resources
        w = sig._dtype.bit_length()
        k = (ResourceMUX, w, inputs_cnt)
        res[k] = res.get(k, 0) + 1

        self.resource_for_object[(stm, sig)] = k

    def registerFF(self, ff):
        res = self.resources
        w = ff._dtype.bit_length()
        ffs = res.get(ResourceFF, 0)
        res[ResourceFF] = ffs + w

        self.resource_for_object[ff] = (ResourceFF, w)

    def registerLatch(self, latch: RtlSignal):
        res = self.resources
        w = latch._dtype.bit_length()
        latches = res.get(ResourceLatch, 0)
        res[ResourceLatch] = latches + w

        self.resource_for_object[latch] = (ResourceLatch, w)

    def registerRAM_write_port(self, mem: RtlSignal, addr: RtlSignal,
                               synchronous: bool):
        res = self.memories
        addresses = res.get(mem, {})
        res[mem] = addresses

        # [rSycn, wSync, rAsync, wAsync]
        portCnts = addresses.get(addr, [0, 0, 0, 0])
        if synchronous:
            portCnts[1] += 1
        else:
            portCnts[3] += 1
        addresses[addr] = portCnts

    def registerRAM_read_port(self, mem: RtlSignal, addr: RtlSignal,
                              synchronous: bool):
        res = self.memories
        addresses = res.get(mem, {})
        res[mem] = addresses

        # [rSycn, wSync, rAsync, wAsync]
        portCnts = addresses.get(addr, [0, 0, 0, 0])
        if synchronous:
            portCnts[0] += 1
        else:
            portCnts[2] += 1
        addresses[addr] = portCnts

    def finalize(self):
        """
        Resolve ports of discovered memories
        """
        ff_to_remove = 0
        res = self.resources
        for m, addrDict in self.memories.items():
            rwSyncPorts, rSyncPorts, wSyncPorts = 0, 0, 0
            rwAsyncPorts, rAsyncPorts, wAsyncPorts = 0, 0, 0
            rSync_wAsyncPorts, rAsync_wSyncPorts = 0, 0

            for _, (rSync, wSync, rAsync, wAsync) in addrDict.items():
                if rSync:
                    ff_to_remove += rSync * m._dtype.elmType.bit_length()

                # resolve port count for this addr signal
                rwSync = min(rSync, wSync)
                rSync -= rwSync
                wSync -= rwSync

                rwAsync = min(rAsync, wAsync)
                rAsync -= rwAsync
                wAsync -= rwAsync

                rSync_wAsync = min(rSync, wAsync)
                rSync -= rSync_wAsync
                wAsync -= rSync_wAsync

                rAsync_wSync = min(rAsync, wSync)
                rAsync -= rAsync_wSync
                wSync -= rAsync_wSync

                # update port counts for mem
                rwSyncPorts += rwSync
                rSyncPorts += rSync
                wSyncPorts += wSync
                rwAsyncPorts += rwAsync
                rAsyncPorts += rAsync
                wAsyncPorts += wAsync

                rSync_wAsyncPorts += rSync_wAsync
                rAsync_wSyncPorts += rAsync_wSync
            k = ResourceRAM(m._dtype.elmType.bit_length(),
                            int(m._dtype.size),
                            rwSyncPorts, rSyncPorts, wSyncPorts,
                            rSync_wAsyncPorts,
                            rwAsyncPorts, rAsyncPorts, wAsyncPorts,
                            rAsync_wSyncPorts)
            res[k] = res.get(k, 0) + 1

        self.memories.clear()

        # remove register on read ports which will be merged into ram
        if ff_to_remove:
            ff_cnt = res[ResourceFF]
            ff_cnt -= ff_to_remove
            if ff_cnt:
                res[ResourceFF] = ff_cnt
            else:
                del res[ResourceFF]


from hwt.doc_markers import internal
from hwt.hdl.assignment import Assignment
from hwt.hdl.operatorDefs import AllOps
from hwt.hdl.types.defs import BOOL
from hwt.hdl.types.sliceUtils import slice_to_SLICE
from hwt.hdl.types.typeCast import toHVal
from hwt.synthesizer.exceptions import TypeConversionErr
from hwt.synthesizer.interfaceLevel.mainBases import InterfaceBase
from hwt.synthesizer.rtlLevel.mainBases import RtlSignalBase
from hwt.synthesizer.rtlLevel.signalUtils.exceptions import MultipleDriversErr,\
    NoDriverErr

def tv(signal):
    """
    Value class for type of signal
    """
    return signal._dtype.getValueCls()

class RtlSignalOps():
    """
    Definitions of operators and other operator functions for RtlSignal

    :ivar _usedOps: cache for expressions with this signal
    """

    def _auto_cast(self, toT):
        return self._dtype.auto_cast(self, toT)

    def _reinterpret_cast(self, toT):
        return self._dtype.reinterpret_cast(self, toT)

    @internal
    def naryOp(self, operator, opCreateDelegate, *otherOps) -> RtlSignalBase:
        """
        Try lookup operator with this parameters in _usedOps
        if not found create new one and soter it in _usedOps

        :param operator: instance of OpDefinition
        :param opCreateDelegate: function (*ops) to create operator
        :param otherOps: other operands (ops = self + otherOps)

        :return: RtlSignal which is result of newly created operator
        """
        k = (operator, *otherOps)
        used = self._usedOps
        try:
            return used[k]
        except KeyError:
            pass

        o = opCreateDelegate(self, *otherOps)

        # input operads may be type converted,
        # search if this happend, and return always same result signal
        try:
            op_instanciated = (o.origin.operator == operator
                               and o.origin.operands[0] is self)
        except AttributeError:
            op_instanciated = False

        if op_instanciated:
            k_real = (operator, *o.origin.operands[1:])
            real_o = used.get(k_real, None)
            if real_o is not None:
                # destroy newly created operator and result, because it is same
                # as
                ctx = self.ctx
                if ctx is not None:
                    ctx.signals.remove(o)

                op = o.origin
                o.origin = None
                o.drivers.clear()
                for inp in op.operands:
                    if isinstance(inp, RtlSignalBase):
                        inp.endpoints.remove(op)

                o = real_o
            else:
                used[k_real] = o

        used[k] = o

        return o

    def __invert__(self):
        return self.naryOp(AllOps.NOT, tv(self).__invert__)

    def _onRisingEdge(self, now=None):
        return self.naryOp(AllOps.RISING_EDGE, tv(self)._onRisingEdge, now)

    def _onFallingEdge(self, now=None):
        return self.naryOp(AllOps.FALLING_EDGE, tv(self)._onFallingEdge, now)

    def _isOn(self):
        return self._auto_cast(BOOL)

    # conversions
    def _convSign(self, signed):
        return tv(self)._convSign(self, signed)

    def _signed(self):
        return tv(self)._signed(self)

    def _unsigned(self):
        return tv(self)._unsigned(self)

    def _vec(self):
        return tv(self)._vec(self)

    # logic
    def __and__(self, other):
        return self.naryOp(AllOps.AND, tv(self).__and__, other)

    def __xor__(self, other):
        return self.naryOp(AllOps.XOR, tv(self).__xor__, other)

    def __or__(self, other):
        return self.naryOp(AllOps.OR, tv(self).__or__, other)

    # cmp
    def _eq(self, other):
        """
        __eq__ is not overloaded because it will destroy hashability of object
        """
        return self.naryOp(AllOps.EQ, tv(self)._eq, other)

    def __ne__(self, other):
        return self.naryOp(AllOps.NEQ, tv(self).__ne__, other)

    def __ge__(self, other):
        return self.naryOp(AllOps.GE, tv(self).__ge__, other)

    def __gt__(self, other):
        return self.naryOp(AllOps.GT, tv(self).__gt__, other)

    def __lt__(self, other):
        return self.naryOp(AllOps.LT, tv(self).__lt__, other)

    def __le__(self, other):
        return self.naryOp(AllOps.LE, tv(self).__le__, other)

    # arithmetic
    def __add__(self, other):
        return self.naryOp(AllOps.ADD, tv(self).__add__, other)

    def __sub__(self, other):
        return self.naryOp(AllOps.SUB, tv(self).__sub__, other)

    def __mul__(self, other):
        return self.naryOp(AllOps.MUL, tv(self).__mul__, other)

    def __floordiv__(self, divider):
        return self.naryOp(AllOps.DIV, tv(self).__floordiv__, divider)

    # selections
    def _downto(self, to):
        return self.naryOp(AllOps.DOWNTO, tv(self)._downto, to)

    def __getitem__(self, key):
        if isinstance(key, slice):
            key = slice_to_SLICE(key, self._dtype.bit_length())
        return self.naryOp(AllOps.INDEX, tv(self).__getitem__, key)

    def _concat(self, *operands):
        return self.naryOp(AllOps.CONCAT, tv(self)._concat, *operands)

    def _ternary(self, ifTrue, ifFalse):
        return self.naryOp(AllOps.TERNARY, tv(self)._ternary, ifTrue, ifFalse)

    @internal
    def _getIndexCascade(self):
        """
        Find out if this signal is something indexed
        """
        try:
            # now I am result of the index  xxx[xx] <= source
            # get index op
            d = self.singleDriver()
            try:
                op = d.operator
            except AttributeError:
                return

            if op == AllOps.INDEX:
                # get signal on which is index applied
                indexedOn = d.operands[0]
                if isinstance(indexedOn, RtlSignalBase):
                    # [TODO] multidimensional indexing
                    return indexedOn, [d.operands[1]]
                else:
                    raise Exception(
                        "can not drive static value %r" % indexedOn)

        except (MultipleDriversErr, NoDriverErr):
            pass

    def __call__(self, source) -> Assignment:
        """
        Create assignment to this signal

        :attention: it is not call of function it is operator of assignment
        :return: list of assignments
        """
        if isinstance(source, InterfaceBase):
            assert source._isAccessible
            source = source._sig

        if source is None:
            source = self._dtype.fromPy(None)
        else:
            source = toHVal(source, suggestedType=self._dtype)
            err = False
            try:
                source = source._auto_cast(self._dtype)
            except TypeConversionErr:
                err = True
            if err:
                raise TypeConversionErr(
                    ("Can not connect %r (of type %r) to %r "
                     "(of type %r) due type incompatibility")
                    % (source, source._dtype, self, self._dtype))

        tmp = self._getIndexCascade()
        if tmp:
            mainSig, indexCascade = tmp
            self = mainSig
        else:
            indexCascade = None

        # self = self._tryMyIndexToEndpoint()
        return Assignment(source, self, indexCascade)

    def __int__(self):
        if not self._const:
            raise TypeError("Int value of signal can be evaluated"
                            " because it is not constant expression:", self)
        else:
            return int(self._val)

    def __bool__(self):
        if not self._const:
            raise TypeError("Bool value of signal can be evaluated"
                            " because it is not constant expression:", self)
        else:
            return bool(self._val)

    def _isFullVld(self):
        return self._const and self._val._isFullVld()


from hwt.synthesizer.exceptions import TypeConversionErr
from hwt.doc_markers import internal

@internal
def default_reinterpret_cast_fn(typeFrom, sigOrVal, toType):
    raise TypeConversionErr(
        "Reinterpretation of %r of type \n%r to type %r is not implemented",
        (sigOrVal, typeFrom, toType))

@internal
def default_auto_cast_fn(typeFrom, sigOrVal, toType):
    raise TypeConversionErr(
        "Conversion of %r of type \n%r to type %r is not implemented",
        (sigOrVal, typeFrom, toType))

class HdlType():
    """
    Base class for all hardware related types.

    :ivar _auto_cast_fn: convert function (attribute set on first convert
        function call)
    :ivar _reinterpret_cast_fn: reinterpret function (attribute set
        on first convert function call)
    """

    def fromPy(self, v, vldMask=None):
        """
        Construct value of this type.
        Delegated on value class for this type
        """
        return self.getValueCls().fromPy(v, self, vldMask=vldMask)

    def auto_cast(self, sigOrVal, toType):
        """
        Cast value or signal of this type to another compatible type.

        :param sigOrVal: instance of signal or value to cast
        :param toType: instance of HdlType to cast into
        """
        if sigOrVal._dtype == toType:
            return sigOrVal

        try:
            c = self._auto_cast_fn
        except AttributeError:
            c = self.get_auto_cast_fn()
            self._auto_cast_fn = c

        return c(self, sigOrVal, toType)

    def reinterpret_cast(self, sigOrVal, toType):
        """
        Cast value or signal of this type to another type of same size.

        :param sigOrVal: instance of signal or value to cast
        :param toType: instance of HdlType to cast into
        """
        try:
            return self.auto_cast(sigOrVal, toType)
        except TypeConversionErr:
            pass

        try:
            r = self._reinterpret_cast_fn
        except AttributeError:
            r = self.get_reinterpret_cast_fn()
            self._reinterpret_cast_fn = r

        return r(self, sigOrVal, toType)

    @internal
    @classmethod
    def get_auto_cast_fn(cls):
        """
        Get method for converting type
        """
        return default_auto_cast_fn

    @internal
    @classmethod
    def get_reinterpret_cast_fn(cls):
        """
        Get method for converting type
        """
        return default_reinterpret_cast_fn

    @internal
    @classmethod
    def getValueCls(cls):
        """
        :attention: Overrode in implementation of concrete HdlType.

        :return: class for value derived from this type
        """
        raise NotImplementedError()

    def __getitem__(self, key):
        """
        [] operator to create an array of this type.
        """
        assert int(key) > 0, key  # array has to have some items
        from hwt.hdl.types.array import HArray
        return HArray(self, key)

    def __repr__(self, indent=0, withAddr=None, expandStructs=False):
        """
        :param indent: number of indentation
        :param withAddr: if is not None is used as a additional
            information about on which address this type is stored
            (used only by HStruct)
        :param expandStructs: expand HStructTypes (used by HStruct and Array)
        """
        return "<%s>" % (self.__class__.__name__)


from hwt.hdl.constants import DIRECTION
from hwt.hdl.types.defs import BIT

class NotSpecified(Exception):
    """
    This error means that you need to implement this function to use this functionality

    f.e. you have to implement Simulation agent for interface when you create new one and you can not use existing
    """
    pass

def walkPhysInterfaces(intf):
    if intf._interfaces:
        for si in intf._interfaces:
            yield from walkPhysInterfaces(si)
    else:
        yield intf

def walkParams(intf, discovered):
    """
    walk parameter instances on this interface
    """
    for si in intf._interfaces:
        yield from walkParams(si, discovered)

    for p in intf._params:
        if p not in discovered:
            discovered.add(p)
            yield p

def connectPacked(srcPacked, dstInterface, exclude=None):
    """
    Connect 1D vector signal to this structuralized interface

    :param packedSrc: vector which should be connected
    :param dstInterface: structuralized interface where should
        packedSrc be connected to
    :param exclude: sub interfaces of self which should be excluded
    """
    offset = 0
    connections = []
    for i in reversed(list(walkPhysInterfaces(dstInterface))):
        if exclude is not None and i in exclude:
            continue
        sig = i._sig
        t = sig._dtype
        if t == BIT:
            s = srcPacked[offset]
            offset += 1
        else:
            w = t.bit_length()
            s = srcPacked[(w + offset): offset]
            offset += w
        connections.append(sig(s))

    return connections

def walkFlatten(interface, shouldEnterIntfFn):
    """
    :param shouldEnterIntfFn: function (actual interface)
        returns tuple (shouldEnter, shouldYield)
    """
    _shouldEnter, _shouldYield = shouldEnterIntfFn(interface)
    if _shouldYield:
        yield interface

    if shouldEnterIntfFn:
        for intf in interface._interfaces:
            yield from walkFlatten(intf, shouldEnterIntfFn)

def packIntf(intf, masterDirEqTo=DIRECTION.OUT, exclude=None):
    """
    Concatenate all signals to one big signal, recursively

    :param masterDirEqTo: only signals with this direction are packed
    :param exclude: sequence of signals/interfaces to exclude
    """
    if not intf._interfaces:
        if intf._masterDir == masterDirEqTo:
            return intf._sig
        return None

    res = None
    for i in intf._interfaces:
        if exclude is not None and i in exclude:
            continue

        if i._interfaces:
            if i._masterDir == DIRECTION.IN:
                d = DIRECTION.opposite(masterDirEqTo)
            else:
                d = masterDirEqTo
            s = i._pack(d, exclude=exclude)
        else:
            if i._masterDir == masterDirEqTo:
                s = i._sig
            else:
                s = None

        if s is not None:
            if res is None:
                res = s
            else:
                res = res._concat(s)

    return res


from hwt.hdl.entity import Entity
from hwt.hdl.operator import Operator
from hwt.hdl.operatorDefs import AllOps
from hwt.hdl.process import HWProcess
from hwt.hdl.switchContainer import SwitchContainer
from hwt.hdl.types.array import HArray
from hwt.hdl.types.typeCast import toHVal
from hwt.serializer.exceptions import SerializerException
from hwt.serializer.generic.indent import getIndent
from hwt.serializer.generic.mapExpr import MapExpr
from hwt.serializer.generic.nameScope import LangueKeyword
from hwt.serializer.generic.portMap import PortMap
from hwt.serializer.generic.serializer import GenericSerializer, CurrentUnitSwap
from hwt.serializer.utils import maxStmId
from hwt.serializer.verilog.context import VerilogSerializerCtx
from hwt.serializer.verilog.keywords import VERILOG_KEYWORDS
from hwt.serializer.verilog.ops import VerilogSerializer_ops
from hwt.serializer.verilog.statements import VerilogSerializer_statements
from hwt.serializer.verilog.tmplContainer import VerilogTmplContainer
from hwt.serializer.verilog.types import VerilogSerializer_types
from hwt.serializer.verilog.utils import SIGNAL_TYPE, verilogTypeOfSig
from hwt.serializer.verilog.value import VerilogSerializer_Value
from hwt.synthesizer.param import getParam

class VerilogSerializer(VerilogTmplContainer, VerilogSerializer_types,
                        VerilogSerializer_Value, VerilogSerializer_statements,
                        VerilogSerializer_ops, GenericSerializer):
    _keywords_dict = {kw: LangueKeyword() for kw in VERILOG_KEYWORDS}
    fileExtension = '.v'

    @classmethod
    def getBaseContext(cls):
        return VerilogSerializerCtx(cls.getBaseNameScope(), 0, None, None)

    @classmethod
    def Entity(cls, ent, ctx):
        generics, ports = cls.Entity_prepare(ent, ctx)

        entVerilog = cls.moduleHeadTmpl.render(
            indent=getIndent(ctx.indent),
            name=ent.name,
            ports=ports,
            generics=generics
        )

        doc = ent.__doc__
        if doc and id(doc) != id(Entity.__doc__):
            doc = cls.comment(doc) + "\n"
            return doc + entVerilog
        else:
            return entVerilog

    @classmethod
    def hardcodeRomIntoProcess(cls, rom):
        """
        Due to verilog restrictions it is not posible to use array constants
        and rom memories has to be hardcoded as process
        """
        processes = []
        signals = []
        for e in rom.endpoints:
            assert isinstance(e, Operator) and e.operator == AllOps.INDEX, e
            me, index = e.operands
            assert me is rom

            # construct output of the rom
            romValSig = rom.ctx.sig(rom.name, dtype=e.result._dtype)
            signals.append(romValSig)
            romValSig.hidden = False

            # construct process which will represent content of the rom
            cases = [(toHVal(i), [romValSig(v), ])
                     for i, v in enumerate(rom.defVal.val)]
            statements = [SwitchContainer(index, cases), ]

            for (_, (stm, )) in cases:
                stm.parentStm = statements[0]

            p = HWProcess(rom.name, statements, {index, },
                          {index, }, {romValSig, })
            processes.append(p)

            # override usage of original index operator on rom
            # to use signal generated from this process
            def replaceOrigRomIndexExpr(x):
                if x is e.result:
                    return romValSig
                else:
                    return x
            for _e in e.result.endpoints:
                _e.operands = tuple(map(replaceOrigRomIndexExpr, _e.operands))
                e.result = romValSig

        return processes, signals

    @classmethod
    def Architecture_var(cls, v, serializerVars, extraTypes,
                         extraTypes_serialized, ctx, childCtx):
        """
        :return: list of extra discovered processes
        """
        t = v._dtype
        # if type requires extra definition
        if isinstance(t, HArray) and v.defVal.vldMask:
            if v.drivers:
                raise SerializerException("Verilog does not support RAMs"
                                          " with initialized value")
            eProcs, eVars = cls.hardcodeRomIntoProcess(v)
            for _v in eVars:
                _procs = cls.Architecture_var(_v, serializerVars, extraTypes,
                                              extraTypes_serialized, ctx,
                                              childCtx)
                eProcs.extend(_procs)
            return eProcs

        v.name = ctx.scope.checkedName(v.name, v)
        serializedVar = cls.SignalItem(v, childCtx, declaration=True)
        serializerVars.append(serializedVar)

        return []

    @classmethod
    def Architecture(cls, arch, ctx):
        serializerVars = []
        procs = []
        extraTypes = set()
        extraTypes_serialized = []
        arch.variables.sort(key=lambda x: (x.name, x._instId))
        arch.componentInstances.sort(key=lambda x: x._name)

        childCtx = ctx.withIndent()
        extraProcesses = []
        for v in arch.variables:
            _eProc = cls.Architecture_var(v,
                                          serializerVars,
                                          extraTypes,
                                          extraTypes_serialized,
                                          ctx,
                                          childCtx)

            extraProcesses.extend(_eProc)

        arch.processes.extend(extraProcesses)
        arch.processes.sort(key=lambda x: (x.name, maxStmId(x)))
        for p in arch.processes:
            procs.append(cls.HWProcess(p, childCtx))

        # architecture names can be same for different entities
        # arch.name = scope.checkedName(arch.name, arch, isGlobal=True)
        componentInstances = list(
            map(lambda c: cls.ComponentInstance(c, childCtx),
                arch.componentInstances))

        return cls.moduleBodyTmpl.render(
            indent=getIndent(ctx.indent),
            entityName=arch.getEntityName(),
            name=arch.name,
            variables=serializerVars,
            extraTypes=extraTypes_serialized,
            processes=procs,
            componentInstances=componentInstances
        )

    @classmethod
    def ComponentInstance(cls, entity, ctx: VerilogSerializerCtx):
        with CurrentUnitSwap(ctx, entity.origin):
            portMaps = []
            for pi in entity.ports:
                pm = PortMap.fromPortItem(pi)
                portMaps.append(pm)

            genericMaps = []
            for g in entity.generics:
                gm = MapExpr(g, g._val)
                genericMaps.append(gm)

            if len(portMaps) == 0:
                raise SerializerException("Incomplete component instance")

            # [TODO] check component instance name
            return cls.componentInstanceTmpl.render(
                indent=getIndent(ctx.indent),
                instanceName=entity._name,
                entity=entity,
                portMaps=[cls.PortConnection(x, ctx) for x in portMaps],
                genericMaps=[cls.MapExpr(x, ctx) for x in genericMaps]
            )

    @classmethod
    def comment(cls, comentStr):
        return "\n".join(["/*", comentStr, "*/"])

    @classmethod
    def GenericItem(cls, g, ctx):
        s = "%s %s" % (cls.HdlType(g._dtype, ctx.forPort()),
                       cls.get_signal_name(g, ctx))
        if g.defVal is None:
            return s
        else:
            return ("parameter %s = %s"
                    % (s, cls.Value(getParam(g.defVal).staticEval(), ctx)))

    @classmethod
    def PortItem(cls, pi, ctx):
        t = cls.HdlType(pi._dtype, ctx.forPort())
        if verilogTypeOfSig(pi.getInternSig()) == SIGNAL_TYPE.REG:
            if t:
                f = "%s reg %s %s"
            else:
                f = "%s reg %s"
        else:
            if t:
                f = "%s %s %s"
            else:
                f = "%s %s"

        if t:
            return f % (cls.DIRECTION(pi.direction),
                        t, pi.name)
        else:
            return f % (cls.DIRECTION(pi.direction),
                        pi.name)

    @classmethod
    def PortConnection(cls, pc, ctx):
        if pc.portItem._dtype != pc.sig._dtype:
            raise SerializerException(
                "Port map %s is nod valid (types does not match)  (%s, %s)" % (
                    "%s => %s" % (pc.portItem.name, cls.asHdl(pc.sig, ctx)),
                    repr(pc.portItem._dtype), repr(pc.sig._dtype)))
        return ".%s(%s)" % (pc.portItem.name, cls.asHdl(pc.sig, ctx))

    @classmethod
    def MapExpr(cls, m, ctx):
        return ".%s(%s)" % (cls.get_signal_name(m.compSig, ctx),
                            cls.asHdl(m.value, ctx))