"""
Generation of common VHDL constructs
"""

from datetime import datetime
from io import TextIOWrapper
from typing import Any, Optional, Set, Tuple

from b_asic.codegen.vhdl import VHDL_TAB


def write_b_asic_vhdl_preamble(f: TextIOWrapper):
    """
    Write a standard BASIC VHDL preamble comment.

    Parameters
    ----------
    f : :class:`io.TextIOWrapper`
        The file object to write the header to.
    """
    f.write(f'--\n')
    f.write(f'-- This code was automatically generated by the B-ASIC toolbox.\n')
    f.write(f'-- Code generation timestamp: ({datetime.now()})\n')
    f.write(f'-- URL: https://gitlab.liu.se/da/B-ASIC\n')
    f.write(f'--\n\n')


def write_ieee_header(
    f: TextIOWrapper,
    std_logic_1164: bool = True,
    numeric_std: bool = True,
):
    """
    Write the standard IEEE VHDL use header with includes of std_logic_1164 and numeric_std.

    Parameters
    ----------
    f : :class:`io.TextIOWrapper`
        The TextIOWrapper object to write the IEEE header to.
    std_logic_1164 : bool, default: True
        Include the std_logic_1164 header.
    numeric_std : bool, default: True
        Include the numeric_std header.
    """
    f.write('library ieee;\n')
    if std_logic_1164:
        f.write('use ieee.std_logic_1164.all;\n')
    if numeric_std:
        f.write('use ieee.numeric_std.all;\n')
    f.write('\n')


def write_signal_decl(
    f: TextIOWrapper,
    name: str,
    type: str,
    default_value: Optional[str] = None,
    name_pad: Optional[int] = None,
):
    """
    Create a VHDL signal declaration: ::

        signal {name} : {type} [:= {default_value}];

    Parameters
    ----------
    f : :class:`io.TextIOWrapper`
        The TextIOWrapper object to write the IEEE header to.
    name : str
        Signal name.
    type : str
        Signal type.
    default_value : str, optional
        An optional default value to the signal.
    name_pad : int, optional
        An optional left padding value applied to the name.
    """
    # Spacing of VHDL signals declaration always with a single tab
    name_pad = 0 if name_pad is None else name_pad
    f.write(f'{VHDL_TAB}signal {name:<{name_pad}} : {type}')
    if default_value is not None:
        f.write(f' := {default_value}')
    f.write(f';\n')


def write_constant_decl(
    f: TextIOWrapper,
    name: str,
    type: str,
    value: Any,
    name_pad: Optional[int] = None,
    type_pad: Optional[int] = None,
):
    """
    Write a VHDL constant declaration with a name, a type and a value.

    Parameters
    ----------
    f : :class:`io.TextIOWrapper`
        The TextIOWrapper object to write the constant declaration to.
    name : str
        Signal name.
    type : str
        Signal type.
    value : anything convertable to str
        Default value to the signal.
    name_pad : int, optional
        An optional left padding value applied to the name.
    """
    name_pad = 0 if name_pad is None else name_pad
    f.write(f'{VHDL_TAB}constant {name:<{name_pad}} : {type} := {str(value)};\n')


def write_type_decl(
    f: TextIOWrapper,
    name: str,
    alias: str,
):
    """
    Write a VHDL type declaration with a name tied to an alias.

    Parameters
    ----------
    f : :class:`io.TextIOWrapper`
        The TextIOWrapper object to write the type declaration to.
    name : str
        Type name alias.
    alias : str
        The type to tie the new name to.
    """
    f.write(f'{VHDL_TAB}type {name} is {alias};\n')


def write_synchronous_process(
    f: TextIOWrapper,
    clk: str,
    body: str,
    indent: Optional[int] = 0,
    name: Optional[str] = None,
):
    """
    Write a regular VHDL synchronous process with a single clock object in the sensitivity list triggering
    a rising edge block by some body of VHDL code.

    Parameters
    ----------
    f : :class:`io.TextIOWrapper`
        The TextIOWrapper to write the VHDL code onto.
    clk : str
        Name of the clock.
    body : str
        Body of the `if rising_edge(clk) then` block.
    indent : Optional[int]
        Indent this process block with `indent` columns
    name : Optional[str]
        An optional name for the process
    """
    space = '' if indent is None else ' ' * indent
    write_synchronous_process_prologue(f, clk, indent, name)
    for line in body.split('\n'):
        if len(line):
            f.write(f'{space}{2*VHDL_TAB}{line}\n')
    write_synchronous_process_epilogue(f, clk, indent, name)


def write_synchronous_process_prologue(
    f: TextIOWrapper,
    clk: str,
    indent: Optional[int] = 0,
    name: Optional[str] = None,
):
    """
    Write only the prologue of a regular VHDL synchronous process with a single clock object in the sensitivity list
    triggering a rising edge block by some body of VHDL code.
    This method should almost always guarantely be followed by a write_synchronous_process_epilogue.

    Parameters
    ----------
    f : :class:`io.TextIOWrapper`
        The TextIOWrapper to write the VHDL code onto.
    clk : str
        Name of the clock.
    indent : Optional[int]
        Indent this process block with `indent` columns
    name : Optional[str]
        An optional name for the process
    """
    space = '' if indent is None else ' ' * indent
    if name is not None:
        f.write(f'{space}{name}: process({clk})\n')
    else:
        f.write(f'{space}process({clk})\n')
    f.write(f'{space}begin\n')
    f.write(f'{space}{VHDL_TAB}if rising_edge(clk) then\n')


def write_synchronous_process_epilogue(
    f: TextIOWrapper,
    clk: Optional[str],
    indent: Optional[int] = 0,
    name: Optional[str] = None,
):
    """
    Write only the epilogue of a regular VHDL synchronous process with a single clock object in the sensitivity list
    triggering a rising edge block by some body of VHDL code.
    This method should almost always guarantely be followed by a write_synchronous_process_epilogue.

    Parameters
    ----------
    f : :class:`io.TextIOWrapper`
        The TextIOWrapper to write the VHDL code onto.
    clk : str
        Name of the clock.
    indent : Optional[int]
        Indent this process block with `indent` columns
    name : Optional[str]
        An optional name for the process
    """
    _ = clk
    space = '' if indent is None else ' ' * indent
    f.write(f'{space}{VHDL_TAB}end if;\n')
    f.write(f'{space}end process')
    if name is not None:
        f.write(' ' + name)
    f.write(';\n')


def write_synchronous_memory(
    f: TextIOWrapper,
    clk: str,
    read_ports: Set[Tuple[str, str, str]],
    write_ports: Set[Tuple[str, str, str]],
    name: Optional[str] = None,
):
    """
    Infer a VHDL synchronous reads and writes.

    Parameters
    ----------
    f : :class:`io.TextIOWrapper`
        The TextIOWrapper to write the VHDL code onto.
    clk : str
        Name of clock identifier to the synchronous memory.
    read_ports : Set[Tuple[str,str]]
        A set of strings used as identifiers for the read ports of the memory.
    write_ports : Set[Tuple[str,str,str]]
        A set of strings used as identifiers for the write ports of the memory.
    name : Optional[str]
        An optional name for the memory process.
    """
    assert len(read_ports) >= 1
    assert len(write_ports) >= 1
    write_synchronous_process_prologue(f, clk=clk, name=name, indent=len(VHDL_TAB))
    for read_name, address, re in read_ports:
        f.write(f'{3*VHDL_TAB}if {re} = \'1\' then\n')
        f.write(f'{4*VHDL_TAB}{read_name} <= memory({address});\n')
        f.write(f'{3*VHDL_TAB}end if;\n')
    for write_name, address, we in write_ports:
        f.write(f'{3*VHDL_TAB}if {we} = \'1\' then\n')
        f.write(f'{4*VHDL_TAB}memory({address}) <= {write_name};\n')
        f.write(f'{3*VHDL_TAB}end if;\n')
    write_synchronous_process_epilogue(f, clk=clk, name=name, indent=len(VHDL_TAB))


def write_asynchronous_read_memory(
    f: TextIOWrapper,
    clk: str,
    read_ports: Set[Tuple[str, str, str]],
    write_ports: Set[Tuple[str, str, str]],
    name: Optional[str] = None,
):
    """
    Infer a VHDL synchronous reads and writes.

    Parameters
    ----------
    f : :class:`io.TextIOWrapper`
        The TextIOWrapper to write the VHDL code onto.
    clk : str
        Name of clock identifier to the synchronous memory.
    read_ports : Set[Tuple[str,str]]
        A set of strings used as identifiers for the read ports of the memory.
    write_ports : Set[Tuple[str,str,str]]
        A set of strings used as identifiers for the write ports of the memory.
    name : Optional[str]
        An optional name for the memory process.
    """
    assert len(read_ports) >= 1
    assert len(write_ports) >= 1
    write_synchronous_process_prologue(f, clk=clk, name=name, indent=len(VHDL_TAB))
    for write_name, address, we in write_ports:
        f.write(f'{3*VHDL_TAB}if {we} = \'1\' then\n')
        f.write(f'{4*VHDL_TAB}memory({address}) <= {write_name};\n')
        f.write(f'{3*VHDL_TAB}end if;\n')
    write_synchronous_process_epilogue(f, clk=clk, name=name, indent=len(VHDL_TAB))
    for read_name, address, _ in read_ports:
        f.write(f'{1*VHDL_TAB}{read_name} <= memory({address});\n')