"""
This type stub file was generated by pyright.
"""

from typing import Generic, Optional, TypeVar, overload
from collections.abc import Iterable

from amaranth.hdl import MemoryInstance, Value, ValueCastable

from amaranth_types.types import ShapeCastable, ShapeLike, FlatShapeLike, ValueLike, FlatValueLike
from ..hdl import MemoryData, Signal
from . import wiring

__all__ = ["Memory", "ReadPort", "WritePort"]


_T_ValueOrValueCastable = TypeVar("_T_ValueOrValueCastable", bound=Value | ValueCastable, covariant=True)
_T_ShapeLike = TypeVar("_T_ShapeLike", bound=ShapeLike, covariant=True)
_T_FlatShapeLike = TypeVar("_T_FlatShapeLike", bound=FlatShapeLike, covariant=True)


class Memory(wiring.Component, Generic[_T_ShapeLike, _T_ValueOrValueCastable]):
    """Addressable array of rows.

    This :ref:`component <wiring>` is used to construct a memory array by first specifying its
    dimensions and initial contents using the :py:`shape`, :py:`depth`, and :py:`init` parameters,
    and then adding memory ports using the :meth:`read_port` and :meth:`write_port` methods.
    Because it is mutable, it should be created and used locally within
    the :ref:`elaborate <lang-elaboration>` method. It is an error to add ports to or change
    initial contents of a memory after it has been elaborated.

    The :py:`init` parameter and assignment to the :py:`init` attribute have the same effect, with
    :class:`Memory.Init` converting elements of the iterable to match :py:`shape` and using
    a default value for rows that are not explicitly initialized.

    .. warning::

        Uninitialized memories (including ASIC memories and some FPGA memories) are
        `not yet supported <https://github.com/amaranth-lang/amaranth/issues/270>`_, and
        the :py:`init` parameter must be always provided, if only as :py:`init=[]`.

    Parameters
    ----------
    shape : :ref:`shape-like <lang-shapelike>` object
        Shape of each memory row.
    depth : :class:`int`
        Number of memory rows.
    init : iterable of initial values
        Initial values for memory rows.

    Platform overrides
    ------------------
    Define the :py:`get_memory()` platform method to override the implementation of
    :class:`Memory`, e.g. to instantiate library cells directly.
    """
    Init = MemoryData.Init

    @overload
    def __init__(self: Memory[ShapeCastable[_T_ValueOrValueCastable], _T_ValueOrValueCastable], data: None = ..., *, shape: ShapeCastable[_T_ValueOrValueCastable] = ..., depth: int = ..., init: Iterable[ValueLike] = ..., attrs: Optional[dict[str, str]] = ..., src_loc_at: int = ...) -> None:
        ...

    @overload
    def __init__(self: Memory[_T_FlatShapeLike, Value], data: None = ..., *, shape: _T_FlatShapeLike = ..., depth: int = ..., init: Iterable[ValueLike] = ..., attrs: Optional[dict[str, str]] = ..., src_loc_at: int = ...) -> None:
        ...

    @overload
    def __init__(self: Memory[_T_ShapeLike, Value], data: MemoryData = ..., *, shape: None = ..., depth: None = ..., init: None = ..., attrs: Optional[dict[str, str]] = ..., src_loc_at: int = ...) -> None:
        ...

    def __init__(self, data: Optional[MemoryData] = None, *, shape: Optional[ShapeLike] = None, depth: Optional[int] = None, init: Optional[Iterable[ValueLike]] = None, attrs: Optional[dict[str, str]] = ..., src_loc_at: int = ...) -> None:
        ...
    
    @property
    def data(self) -> MemoryData:
        ...
    
    @property
    def shape(self) -> ShapeLike:
        ...
    
    @property
    def depth(self) -> int:
        ...
    
    @property
    def init(self) -> Init:
        ...
    
    @init.setter
    def init(self, init: Init) -> None:
        ...
    
    @property
    def attrs(self) -> dict[str, str]:
        ...
    
    def read_port(self, *, domain: str = ..., transparent_for: Iterable[WritePort] = ..., src_loc_at: int = ...) -> ReadPort[_T_ShapeLike, _T_ValueOrValueCastable]:
        """Request a read port.

        If :py:`domain` is :py:`"comb"`, the created read port is asynchronous and always enabled
        (with its enable input is tied to :py:`Const(1)`), and its data output always reflects
        the contents of the selected row. Otherwise, the created read port is synchronous,
        and its data output is updated with the contents of the selected row at each
        :ref:`active edge <lang-sync>` of :py:`domain` where the enable input is asserted.

        The :py:`transparent_for` parameter specifies the *transparency set* of this port: zero or
        more :class:`WritePort`\\ s, all of which must belong to the same memory and clock domain.
        If another port writes to a memory row at the same time as this port reads from the same
        memory row, and that write port is a part of the transparency set, then this port retrieves
        the new contents of the row; otherwise, this port retrieves the old contents of the row.

        If another write port belonging to a different clock domain updates a memory row that this
        port is reading at the same time, the behavior is undefined.

        The signature of the returned port is
        :py:`ReadPort.Signature(shape=self.shape, addr_width=ceil_log2(self.depth))`.

        Returns
        -------
        :class:`ReadPort`
        """
        ...
    
    def write_port(self, *, domain: str = ..., granularity: Optional[int] = ..., src_loc_at: int = ...) -> WritePort[_T_ShapeLike, _T_ValueOrValueCastable]:
        """Request a write port.

        The created write port is synchronous, updating the contents of the selected row at each
        :ref:`active edge <lang-sync>` of :py:`domain` where the enable input is asserted.

        Specifying a *granularity* when :py:`shape` is :func:`unsigned(width) <.unsigned>` or
        :class:`data.ArrayLayout(_, width) <.data.ArrayLayout>` makes it possible to partially
        update a memory row. In this case, :py:`granularity` must be an integer that evenly divides
        :py:`width`, and the memory row is split into :py:`width // granularity` equally sized
        parts, each of which is updated if the corresponding bit of the enable input is asserted.

        The signature of the new port is
        :py:`WritePort.Signature(shape=self.shape, addr_width=ceil_log2(self.depth), granularity=granularity)`.

        Returns
        -------
        :class:`WritePort`
        """
        ...
    
    @property
    def read_ports(self) -> tuple[ReadPort, ...]:
        """All read ports defined so far.

        This property is provided for the :py:`platform.get_memory()` override.
        """
        ...
    
    @property
    def write_ports(self) -> tuple[WritePort, ...]:
        """All write ports defined so far.

        This property is provided for the :py:`platform.get_memory()` override.
        """
        ...
    
    def elaborate(self, platform) -> MemoryInstance:
        ...
    


class ReadPort(Generic[_T_ShapeLike, _T_ValueOrValueCastable]):
    """A read memory port.

    Memory read ports, which are :ref:`interface objects <wiring>`, can be constructed by calling
    :meth:`Memory.read_port` or via :meth:`ReadPort.Signature.create() <.Signature.create>`.

    An asynchronous (:py:`"comb"` domain) memory read port is always enabled. The :py:`en` input of
    such a port is tied to :py:`Const(1)`.

    Attributes
    ----------
    signature : :class:`ReadPort.Signature`
        Signature of this memory port.
    memory : :class:`Memory` or :py:`None`
        Memory associated with this memory port.
    domain : :class:`str`
        Name of this memory port's clock domain. For asynchronous ports, :py:`"comb"`.
    transparent_for : :class:`tuple` of :class:`WritePort`
        Transparency set of this memory port.
    """
    class Signature(wiring.Signature):
        """Signature of a memory read port.

        Parameters
        ----------
        addr_width : :class:`int`
            Width of the address port.
        shape : :ref:`shape-like <lang-shapelike>` object
            Shape of the data port.

        Members
        -------
        en: :py:`In(1, init=1)`
            Enable input.
        addr: :py:`In(addr_width)`
            Address input.
        data: :py:`Out(shape)`
            Data output.
        """
        def __init__(self, *, addr_width: int, shape: _T_ShapeLike) -> None:
            ...
        
        def create(self, *, path: tuple[str | int, ...] = ..., src_loc_at: int =...) -> ReadPort[_T_ShapeLike, _T_ValueOrValueCastable]:
            """:meta private:"""
            ...
        
        @property
        def addr_width(self) -> int:
            ...
        
        @property
        def shape(self) -> _T_ShapeLike:
            ...
        
        def __eq__(self, other) -> bool:
            ...
        
        def __repr__(self) -> str:
            ...

    @property
    def en(self) -> Signal:
        ...

    @property
    def addr(self) -> Signal:
        ...

    @property
    def data(self) -> _T_ValueOrValueCastable:
        ...

    def __init__(self, signature: Signature, *, memory: Memory, domain: str, transparent_for: Iterable[WritePort] = ..., path: tuple[str | int, ...] = ..., src_loc_at: int = ...) -> None:
        ...
    
    @property
    def signature(self) -> Signature:
        ...
    
    @property
    def memory(self) -> Memory[_T_ShapeLike, _T_ValueOrValueCastable]:
        ...
    
    @property
    def domain(self) -> str:
        ...
    
    @property
    def transparent_for(self) -> tuple[WritePort, ...]:
        ...
    


class WritePort(Generic[_T_ShapeLike, _T_ValueOrValueCastable]):
    """A write memory port.

    Memory write ports, which are :ref:`interface objects <wiring>`, can be constructed by calling
    :meth:`Memory.write_port` or via :meth:`WritePort.Signature.create() <.Signature.create>`.

    Attributes
    ----------
    signature : :class:`WritePort.Signature`
        Signature of this memory port.
    memory : :class:`Memory` or :py:`None`
        Memory associated with this memory port.
    domain : :class:`str`
        Name of this memory port's clock domain. Never :py:`"comb"`.
    """
    class Signature(wiring.Signature):
        """Signature of a memory write port.

        Width of the enable input is determined as follows:

        * If :py:`granularity` is :py:`None`,
          then :py:`en_width == 1`.
        * If :py:`shape` is :func:`unsigned(data_width) <.unsigned>`,
          then :py:`en_width == data_width // granularity`.
        * If :py:`shape` is :class:`data.ArrayLayout(_, elem_count) <.data.ArrayLayout>`,
          then :py:`en_width == elem_count // granularity`.

        Parameters
        ----------
        addr_width : :class:`int`
            Width of the address port.
        shape : :ref:`shape-like <lang-shapelike>` object
            Shape of the data port.
        granularity : :class:`int` or :py:`None`
            Granularity of memory access.

        Members
        -------
        en: :py:`In(en_width)`
            Enable input.
        addr: :py:`In(addr_width)`
            Address input.
        data: :py:`In(shape)`
            Data input.
        """
        def __init__(self, *, addr_width: int, shape: _T_ShapeLike, granularity: Optional[int] = ...) -> None:
            ...
        
        def create(self, *, path: tuple[str | int, ...] = ..., src_loc_at: int = ...) -> WritePort[_T_ShapeLike, _T_ValueOrValueCastable]:
            """:meta private:"""
            ...
        
        @property
        def addr_width(self) -> int:
            ...
        
        @property
        def shape(self) -> _T_ShapeLike:
            ...
        
        @property
        def granularity(self) -> Optional[int]:
            ...
        
        def __eq__(self, other) -> bool:
            ...
        
        def __repr__(self) -> str:
            ...

    @property
    def en(self) -> Signal:
        ...

    @property
    def addr(self) -> Signal:
        ...

    @property
    def data(self) -> _T_ValueOrValueCastable:
        ...
    
    def __init__(self, signature, *, memory, domain, path=..., src_loc_at=...) -> None:
        ...
    
    @property
    def signature(self) -> Signature:
        ...
    
    @property
    def memory(self) -> Memory[_T_ShapeLike, _T_ValueOrValueCastable]:
        ...
    
    @property
    def domain(self) -> str:
        ...


