#  This file is part of the myhdl library, a Python package for using
#  Python as a Hardware Description Language.
#
#  Copyright (C) 2003-2015 Jan Decaluwe
#
#  The myhdl library is free software; you can redistribute it and/or
#  modify it under the terms of the GNU Lesser General Public License as
#  published by the Free Software Foundation; either version 2.1 of the
#  License, or (at your option) any later version.
#
#  This library is distributed in the hope that it will be useful, but
#  WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
#  Lesser General Public License for more details.

#  You should have received a copy of the GNU Lesser General Public
#  License along with this library; if not, write to the Free Software
#  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA

""" 
    Module that provides the ShadowSignal classes
"""

import warnings
from copy import deepcopy

from myhdl._Signal import _Signal
from myhdl._Waiter import _SignalWaiter, _SignalTupleWaiter
from myhdl._intbv import intbv
from myhdl._simulator import _siglist
from myhdl._bin import bin

# shadow signals


class _ShadowSignal(_Signal):

    __slots__ = ('_waiter',)

    def __init__(self, val):
        _Signal.__init__(self, val)
        # self._driven = True # set this in conversion analyzer

    # remove next attribute assignment
    @_Signal.next.setter
    def next(self, val):
        raise AttributeError("ShadowSignals are readonly")


class _SliceSignal(_ShadowSignal):

    __slots__ = ('_sig', '_left', '_right')

    def __init__(self, sig, left, right=None):
        # XXX error checks
        if right is None:
            _ShadowSignal.__init__(self, sig[left])
        else:
            _ShadowSignal.__init__(self, sig[left:right])
        self._sig = sig
        sig._read = True
        self._left = left
        self._right = right
        if right is None:
            gen = self._genfuncIndex()
        else:
            gen = self._genfuncSlice()
        self._waiter = _SignalWaiter(gen)

    def __repr__(self):
        if self._right is None:
            return repr(self._sig) + '({})'.format(self._left)
        else:
            return repr(self._sig) + '({}, {})'.format(self._left, self._right)

    def _genfuncIndex(self):
        sig, index = self._sig, self._left
        set_next = _Signal.next.fset
        while 1:
            set_next(self, sig[index])
            yield sig

    def _genfuncSlice(self):
        sig, left, right = self._sig, self._left, self._right
        set_next = _Signal.next.fset
        while 1:
            set_next(self, sig[left:right])
            yield sig

    def _setName(self, hdl):
        # if we depend on a ShadowSignal ourselves
        # it would be nice if we 'resolve' the slicing chain
        # e.g. s[7:4][1:0][1] to s[5]
        if isinstance(self._sig, _ShadowSignal):
            # self._sig must have a _left and a _right
            if self._right is None:
                # we're a final index
                self._left = self._sig._right + self._left
            else:
                self._right = self._sig._right + self._right
                self._left = self._sig._right + self._left
            # step one back up
            self._sig = self._sig._sig

        else:
            # we're at the top of the chain
            pass

        if self._right is None:
            if hdl == 'Verilog':
                self._name = "%s[%s]" % (self._sig._name, self._left)
            else:
                self._name = "%s(%s)" % (self._sig._name, self._left)
        else:
            if hdl == 'Verilog':
                self._name = "%s[%s-1:%s]" % (self._sig._name, self._left, self._right)
            else:
                self._name = "%s(%s-1 downto %s)" % (self._sig._name, self._left, self._right)
        # we may have 'shadowed' as well (and further down ...)
        if self._slicesigs:
            for sl in self._slicesigs:
                sl._setName(hdl)

    def _markRead(self):
        self._read = True
        self._sig._read = True

    def _markUsed(self):
        self._used = True
        self._sig._used = True

    def toVerilog(self):
        if self._right is None:
            return "assign %s = %s[%s];" % (self._name, self._sig._name, self._left)
        else:
            return "assign %s = %s[%s-1:%s];" % (self._name, self._sig._name, self._left, self._right)

    def toVHDL(self):
        if self._right is None:
            return "%s <= %s(%s);" % (self._name, self._sig._name, self._left)
        else:
            return "%s <= %s(%s-1 downto %s);" % (self._name, self._sig._name, self._left, self._right)


class ConcatSignal(_ShadowSignal):

    __slots__ = ('_args', '_sigargs', '_initval')

    def __init__(self, *args):
        assert len(args) >= 2
        self._args = args
        self._sigargs = sigargs = []

        nrbits = 0
        val = 0
        for a in args:
            if isinstance(a, intbv):
                w = a._nrbits
                v = a._val
            elif isinstance(a, _Signal):
                sigargs.append(a)
                w = a._nrbits
                if isinstance(a._val, intbv):
                    v = a._val._val
                else:
                    v = a._val
            elif isinstance(a, bool):
                w = 1
                v = a
            elif isinstance(a, str):
                w = len(a)
                v = int(a, 2)
            else:
                raise TypeError("ConcatSignal: inappropriate argument type: %s"
                                % type(a))
            nrbits += w
            val = val << w | v & (1 << w) - 1
        self._initval = val
        ini = intbv(val)[nrbits:]
        _ShadowSignal.__init__(self, ini)
        gen = self.genfunc()
        self._waiter = _SignalTupleWaiter(gen)

    def genfunc(self):
        set_next = _Signal.next.fset
        args = self._args
        sigargs = self._sigargs
        nrbits = self._nrbits
        newval = intbv(self._initval)[nrbits:]
        while 1:
            hi = nrbits
            for a in args:
                if isinstance(a, bool):
                    w = 1
                else:
                    w = len(a)
                lo = hi - w
                # note: 'a in sigargs' is equivalence check, not identity
                if isinstance(a, _Signal):
                    if isinstance(a._val, intbv):
                        newval[hi:lo] = a[w:]
                    else:
                        newval[hi:lo] = a
                hi = lo
            set_next(self, newval)
            yield sigargs

    def _markRead(self):
        self._read = True
        for s in self._sigargs:
            s._markRead()

    def _markUsed(self):
        self._used = True
        for s in self._sigargs:
            s._markUsed()

    def toVHDL(self):
        lines = []
        ini = intbv(self._initval)[self._nrbits:]
        hi = self._nrbits
        for a in self._args:
            if isinstance(a, bool):
                w = 1
            else:
                w = len(a)
            lo = hi - w

            if isinstance(a, _Signal) and a._name is None:
                # We have seen a bug when a concat signal is created in the
                # following way:
                #
                #     sig_list = [Signal(False) for n in range(32)]
                #     concat_sig = ConcatSignal(*reversed(sig_list))
                #
                # It seems that the _name attribute on the signals in sig_list
                # is only updated if an assignment is made to them. Otherwise
                # _name is left as None. We need to check for None and raise
                # a warning.
                from myhdl.conversion._misc import _error
                from myhdl import ToVHDLWarning

                if w == 1:
                    warnings.warn(
                        "%s: %s[%s]" % (_error.UndrivenSignal, self._name, lo),
                        category=ToVHDLWarning)
                else:
                    warnings.warn(
                        "%s: %s[%s:%s]" % (_error.UndrivenSignal, self._name, hi, lo),
                        category=ToVHDLWarning)

            if w == 1:
                if isinstance(a, _Signal) and a._name is not None:
                    # Check that a._name is not None as None should not be
                    # written into the converted code. If it is None then we
                    # assume no assignment has been made to the signal
                    # (otherwise the _name attribute would have been updated
                    # by the _analyzeSigs function). In this situation the
                    # signal should hold its init value (as handled in the
                    # else branch).
                    if a._type == bool:  # isinstance(a._type , bool): <- doesn't work
                        lines.append("%s(%s) <= %s;" % (self._name, lo, a._name))
                    else:
                        lines.append("%s(%s) <= %s(0);" % (self._name, lo, a._name))
                else:
                    lines.append("%s(%s) <= '%s';" % (self._name, lo, bin(ini[lo])))
            else:
                if isinstance(a, _Signal) and a._name is not None:
                    # Check that a._name is not None as None should not be
                    # written into the converted code
                    lines.append("%s(%s-1 downto %s) <= %s;" % (self._name, hi, lo, a._name))
                else:
                    lines.append('%s(%s-1 downto %s) <= "%s";' %
                                 (self._name, hi, lo, bin(ini[hi:lo], w)))
            hi = lo
        return "\n".join(lines)

    def toVerilog(self):
        lines = []
        ini = intbv(self._initval)[self._nrbits:]
        hi = self._nrbits
        for a in self._args:
            if isinstance(a, bool):
                w = 1
            else:
                w = len(a)
            lo = hi - w

            if isinstance(a, _Signal) and a._name is None:
                # We have seen a bug when a concat signal is created in the
                # following way:
                #
                #     sig_list = [Signal(False) for n in range(32)]
                #     concat_sig = ConcatSignal(*reversed(sig_list))
                #
                # It seems that the _name attribute on the signals in sig_list
                # is only updated if an assignment is made to them. Otherwise
                # _name is left as None. We need to check for None and raise
                # a warning.
                from myhdl.conversion._misc import _error
                from myhdl import ToVerilogWarning

                if w == 1:
                    warnings.warn(
                        "%s: %s[%s]" % (_error.UndrivenSignal, self._name, lo),
                        category=ToVerilogWarning)
                else:
                    warnings.warn(
                        "%s: %s[%s:%s]" % (_error.UndrivenSignal, self._name, hi, lo),
                        category=ToVerilogWarning)

            if w == 1:
                if isinstance(a, _Signal) and a._name is not None:
                    # Check that a._name is not None as None should not be
                    # written into the converted code. If it is None then we
                    # assume no assignment has been made to the signal
                    # (otherwise the _name attribute would have been updated
                    # by the _analyzeSigs function). In this situation the
                    # signal should hold its init value (as handled in the
                    # else branch).
                    if a._type == bool:
                        lines.append("assign %s[%s] = %s;" % (self._name, lo, a._name))
                    else:
                        lines.append("assign %s[%s] = %s[0];" % (self._name, lo, a._name))
                else:
                    lines.append("assign %s[%s] = 'b%s;" % (self._name, lo, bin(ini[lo])))
            else:
                if isinstance(a, _Signal) and a._name is not None:
                    # Check that a._name is not None as None should not be
                    # written into the converted code
                    lines.append("assign %s[%s-1:%s] = %s;" % (self._name, hi, lo, a._name))
                else:
                    lines.append("assign %s[%s-1:%s] = 'b%s;" %
                                 (self._name, hi, lo, bin(ini[hi:lo], w)))
            hi = lo
        return "\n".join(lines)

# Tristate signal


class BusContentionWarning(UserWarning):
    pass


warnings.filterwarnings('always', r".*", BusContentionWarning)

# def Tristate(val, delay=None):
#     """ Return a new Tristate(default or delay 0) or DelayedTristate """
#     if delay is not None:
#         if delay < 0:
#             raise TypeError("Signal: delay should be >= 0")
#         return _DelayedTristate(val, delay)
#     else:
#         return _Tristate(val)


def TristateSignal(val):
    return _TristateSignal(val)


class _TristateSignal(_ShadowSignal):

    __slots__ = ('_drivers', '_orival')

    def __init__(self, val):
        self._drivers = []
        # construct normally to set type / size info right
        _ShadowSignal.__init__(self, val)
        self._orival = deepcopy(val)  # keep for drivers
        # reset signal values to None
        self._next = self._val = self._init = None
        self._waiter = _SignalTupleWaiter(self._resolve())

    def driver(self):
        d = _TristateDriver(self)
        self._drivers.append(d)
        return d

    def _resolve(self):
        # set_next = _ShadowSignal._set_next
        senslist = self._drivers
        while 1:
            yield senslist
            res = None
            for d in senslist:
                if res is None:
                    res = d._val
                elif d._val is not None:
                    warnings.warn("Bus contention", category=BusContentionWarning)
                    res = None
                    break
            self._next = res
            _siglist.append(self)

    def toVerilog(self):
        lines = []
        for d in self._drivers:
            if d._driven:
                lines.append("assign %s = %s;" % (self._name, d._name))
        return "\n".join(lines)

    def toVHDL(self):
        lines = []
        for d in self._drivers:
            if d._driven:
                lines.append("%s <= %s;" % (self._name, d._name))
        return "\n".join(lines)

    def __repr__(self):
        return "_TristateSignal(" + repr(self._val) + ")"


class _TristateDriver(_Signal):

    __slots__ = ('_sig',)

    def __init__(self, sig):
        _Signal.__init__(self, sig._orival)
        # reset signal values to None
        self._next = self._val = self._init = None
        self._sig = sig

    @_Signal.next.setter
    def next(self, val):
        if isinstance(val, _Signal):
            val = val._val
        if val is None:
            self._next = None
        else:
            # restore original value to cater for intbv handler
            self._next = self._sig._orival
            self._setNextVal(val)
        _siglist.append(self)

    def __repr__(self):
        return "_TristateDriver(" + repr(self._val) + ")"
