import warnings

from ..._utils import deprecated, extend
from ...hdl.ast import *
from ...hdl.ir import Elaboratable
from ...hdl.mem import Memory as NativeMemory
from ...hdl.ir import Fragment, Instance
from ...hdl.dsl import Module
from .module import Module as CompatModule
from .structure import Signal
from ...lib.io import Pin


__all__ = ["TSTriple", "Instance", "Memory", "READ_FIRST", "WRITE_FIRST", "NO_CHANGE"]


class TSTriple:
    def __init__(self, bits_sign=None, min=None, max=None, reset_o=0, reset_oe=0, reset_i=0,
                 name=None):
        self.o  = Signal(bits_sign, min=min, max=max, reset=reset_o,
                         name=None if name is None else name + "_o")
        self.oe = Signal(reset=reset_oe,
                         name=None if name is None else name + "_oe")
        self.i  = Signal(bits_sign, min=min, max=max, reset=reset_i,
                         name=None if name is None else name + "_i")

    def __len__(self):
        return len(self.o)

    def get_tristate(self, io):
        return Tristate(io, self.o, self.oe, self.i)


class Tristate(Elaboratable):
    def __init__(self, target, o, oe, i=None):
        self.target = target
        self.o = o
        self.oe = oe
        self.i = i if i is not None else None

    def elaborate(self, platform):
        if hasattr(platform, "get_input_output"):
            pin = Pin(len(self.target), dir="oe" if self.i is None else "io")
            pin.o = self.o
            pin.oe = self.oe
            if self.i is not None:
                pin.i = self.i
            return platform.get_input_output(pin, self.target, attrs={}, invert=None)

        m = Module()
        m.d.comb += self.i.eq(self.target)
        m.submodules += Instance("$tribuf",
            p_WIDTH=len(self.target),
            i_EN=self.oe,
            i_A=self.o,
            o_Y=self.target,
        )

        f = m.elaborate(platform)
        f.flatten = True
        return f


(READ_FIRST, WRITE_FIRST, NO_CHANGE) = range(3)


class _MemoryPort(CompatModule):
    def __init__(self, adr, dat_r, we=None, dat_w=None, async_read=False, re=None,
                 we_granularity=0, mode=WRITE_FIRST, clock_domain="sync"):
        self.adr = adr
        self.dat_r = dat_r
        self.we = we
        self.dat_w = dat_w
        self.async_read = async_read
        self.re = re
        self.we_granularity = we_granularity
        self.mode = mode
        self.clock = ClockSignal(clock_domain)


@extend(NativeMemory)
@deprecated("it is not necessary or permitted to add Memory as a special or submodule")
def elaborate(self, platform):
    return Fragment()


class CompatMemory(NativeMemory, Elaboratable):
    def __init__(self, width, depth, init=None, name=None):
        super().__init__(width=width, depth=depth, init=init, name=name)

    @deprecated("instead of `get_port()`, use `read_port()` and `write_port()`")
    def get_port(self, write_capable=False, async_read=False, has_re=False, we_granularity=0,
                 mode=WRITE_FIRST, clock_domain="sync"):
        if we_granularity >= self.width:
            warnings.warn("do not specify `we_granularity` greater than memory width, as it "
                          "is a hard error in non-compatibility mode",
                          DeprecationWarning, stacklevel=1)
            we_granularity = 0
        if we_granularity == 0:
            warnings.warn("instead of `we_granularity=0`, use `we_granularity=None` or avoid "
                          "specifying it at all, as it is a hard error in non-compatibility mode",
                          DeprecationWarning, stacklevel=1)
            we_granularity = None
        assert mode != NO_CHANGE
        rdport = self.read_port(domain="comb" if async_read else clock_domain,
                                transparent=mode == WRITE_FIRST)
        rdport.addr.name = "{}_addr".format(self.name)
        adr = rdport.addr
        dat_r = rdport.data
        if write_capable:
            wrport = self.write_port(domain=clock_domain, granularity=we_granularity)
            wrport.addr = rdport.addr
            we = wrport.en
            dat_w = wrport.data
        else:
            we = None
            dat_w = None
        if has_re:
            if mode == READ_FIRST:
                re = rdport.en
            else:
                warnings.warn("the combination of `has_re=True` and `mode=WRITE_FIRST` has "
                              "surprising behavior: keeping `re` low would merely latch "
                              "the address, while the data will change with changing memory "
                              "contents; avoid using `re` with transparent ports as it is a hard "
                              "error in non-compatibility mode",
                              DeprecationWarning, stacklevel=1)
                re = Signal()
        else:
            re = None
        mp = _MemoryPort(adr, dat_r, we, dat_w,
          async_read, re, we_granularity, mode,
          clock_domain)
        mp.submodules.rdport = rdport
        if write_capable:
            mp.submodules.wrport = wrport
        return mp


Memory = CompatMemory
