# Copyright lowRISC contributors.
# Licensed under the Apache License, Version 2.0, see LICENSE for details.
# SPDX-License-Identifier: Apache-2.0
"""Generate SystemVerilog designs from IpBlock object"""

import logging as log
import os
from typing import Dict, Optional, Tuple

from mako import exceptions  # type: ignore
from mako.template import Template  # type: ignore
from pkg_resources import resource_filename

from reggen.ip_block import IpBlock
from reggen.lib import check_int
from reggen.multi_register import MultiRegister
from reggen.reg_base import RegBase
from reggen.register import Register


def escape_name(name: str) -> str:
    return name.lower().replace(' ', '_')


def make_box_quote(msg: str, indent: str = '  ') -> str:
    hr = indent + ('/' * (len(msg) + 6))
    middle = indent + '// ' + msg + ' //'
    return '\n'.join([hr, middle, hr])


def _get_awparam_name(iface_name: Optional[str]) -> str:
    return (iface_name or 'Iface').capitalize() + 'Aw'


def get_addr_widths(block: IpBlock) -> Dict[Optional[str], Tuple[str, int]]:
    '''Return the address widths for the device interfaces

    Returns a dictionary keyed by interface name whose values are pairs:
    (paramname, width) where paramname is IfaceAw for an unnamed interface and
    FooAw for an interface called foo. This is constructed in the same order as
    block.reg_blocks.

    If there is a single device interface and that interface is unnamed, use
    the more general parameter name "BlockAw".

    '''
    assert block.reg_blocks
    if len(block.reg_blocks) == 1 and None in block.reg_blocks:
        return {None: ('BlockAw', block.reg_blocks[None].get_addr_width())}

    return {name: (_get_awparam_name(name), rb.get_addr_width())
            for name, rb in block.reg_blocks.items()}


def get_type_name_pfx(block: IpBlock, iface_name: Optional[str]) -> str:
    return block.name.lower() + ('' if iface_name is None
                                 else '_{}'.format(iface_name.lower()))


def get_r0(reg: RegBase) -> Register:
    '''Get a Register representing an entry in the RegBase'''
    if isinstance(reg, Register):
        return reg
    else:
        assert isinstance(reg, MultiRegister)
        return reg.reg


def get_iface_tx_type(block: IpBlock,
                      iface_name: Optional[str],
                      hw2reg: bool) -> str:
    x2x = 'hw2reg' if hw2reg else 'reg2hw'
    pfx = get_type_name_pfx(block, iface_name)
    return '_'.join([pfx, x2x, 't'])


def get_reg_tx_type(block: IpBlock, reg: RegBase, hw2reg: bool) -> str:
    '''Get the name of the hw2reg or reg2hw type for reg'''
    if isinstance(reg, Register):
        r0 = reg
        type_suff = 'reg_t'
    else:
        assert isinstance(reg, MultiRegister)
        r0 = reg.reg
        type_suff = 'mreg_t'

    x2x = 'hw2reg' if hw2reg else 'reg2hw'
    return '_'.join([block.name.lower(),
                     x2x,
                     r0.name.lower(),
                     type_suff])


def gen_rtl(block: IpBlock, outdir: str) -> int:
    # Read Register templates
    reg_top_tpl = Template(
        filename=resource_filename('reggen', 'reg_top.sv.tpl'))
    reg_pkg_tpl = Template(
        filename=resource_filename('reggen', 'reg_pkg.sv.tpl'))

    # In case the generated package contains alias definitions, we add
    # the alias implementation identifier to the package name so that it
    # becomes unique.
    alias_impl = "_" + block.alias_impl if block.alias_impl else ""

    # Generate <block>_reg_pkg.sv
    #
    # This defines the various types used to interface between the *_reg_top
    # module(s) and the block itself.
    reg_pkg_path = os.path.join(outdir, block.name.lower() + alias_impl +
                                "_reg_pkg.sv")
    with open(reg_pkg_path, 'w', encoding='UTF-8') as fout:
        try:
            fout.write(reg_pkg_tpl.render(block=block,
                                          alias_impl=alias_impl))
        except:  # noqa F722 for template Exception handling
            log.error(exceptions.text_error_template().render())
            return 1

    # Generate the register block implementation(s). For a device interface
    # with no name we generate the register module "<block>_reg_top"
    # (writing to <block>_reg_top.sv). In any other case, we also need the
    # interface name, giving <block>_<ifname>_reg_top.
    lblock = block.name.lower()
    for if_name, rb in block.reg_blocks.items():
        if if_name is None:
            mod_base = lblock
        else:
            mod_base = lblock + '_' + if_name.lower()

        mod_name = mod_base + alias_impl + '_reg_top'
        reg_top_path = os.path.join(outdir, mod_name + '.sv')
        with open(reg_top_path, 'w', encoding='UTF-8') as fout:
            try:
                fout.write(reg_top_tpl.render(block=block,
                                              mod_base=mod_base,
                                              mod_name=mod_name,
                                              if_name=if_name,
                                              rb=rb))
            except:  # noqa F722 for template Exception handling
                log.error(exceptions.text_error_template().render())
                return 1

    return 0


def render_param(dst_type: str, value: str) -> str:
    '''Render a parameter value as used for the destination type

    The value is itself a string but we have already checked that if dst_type
    happens to be "int" or "int unsigned" then it can be parsed as an integer.

    If dst_type is "int unsigned" and the value is larger than 2^31 then
    explicitly generate a 32-bit hex value. This allows 32-bit literals whose
    top bits are set (which can't be written as bare integers in SystemVerilog
    without warnings, because those are interpreted as ints).

    '''
    if dst_type == 'int unsigned':
        # This shouldn't fail because we've already checked it in
        # _parse_parameter in params.py
        int_val = check_int(value, "integer parameter")
        if int_val >= (1 << 31):
            return "32'h{:08x}".format(int_val)

    return value
