#
# This file is part of LUNA.
#
# Copyright (c) 2023-2025 Great Scott Gadgets <info@greatscottgadgets.com>
# SPDX-License-Identifier: BSD-3-Clause

"""Generate a C library for SoC designs."""

import datetime

from amaranth             import unsigned

from amaranth_soc.memory  import MemoryMap, ResourceInfo
from amaranth_soc         import csr

from ..gateware.cpu.ic    import InterruptMap
from .                    import introspect


class LinkerScript():
    def __init__(self, memory_map: MemoryMap, reset_addr: int = 0x00000000):
        self.memory_map      = memory_map
        self.reset_addr      = reset_addr

    def generate(self, file=None, macro_name="SOC_RESOURCES", platform_name="Generic Platform"):
        """ Generates a ldscript that holds our primary RAM and ROM regions.

        Parameters:
            file       -- Optional. If provided, this will be treated as the file= argument to the print()
                          function. This can be used to generate file content instead of printing to the terminal.
        """

        def emit(content):
            """ Utility function that emits a string to the targeted file. """
            print(content, file=file)

        # TODO this should be determined by introspection
        memories = ["ram", "rom", "blockram", "spiflash",
                    "bootrom", "scratchpad", "mainram"]

        # Insert our automatically generated header.
        emit("/**")
        emit(" * Linker memory regions.")
        emit(" *")
        emit(" * Automatically generated by LUNA; edits will be discarded on rebuild.")
        emit(" * (Most header files phrase this 'Do not edit.'; be warned accordingly.)")
        emit(" *")
        emit(f" * Generated: {datetime.datetime.now()}.")
        emit(" */")
        emit("")

        # memory regions
        emit("MEMORY")
        emit("{")
        window: MemoryMap
        name:   MemoryMap.Name
        for window, name, (start, end, ratio) in self.memory_map.windows():
            name = name[0]
            if name not in memories:
                continue
            if self.reset_addr >= start and self.reset_addr < end:
                start = self.reset_addr
            emit(f"    {name} : ORIGIN = 0x{start:08x}, LENGTH = 0x{end-start:08x}")
        emit("}")
        emit("")


class Header():
    def __init__(self, memory_map: MemoryMap, interrupts: InterruptMap):
        self.memory_map      = memory_map
        self.interrupts      = interrupts

    def generate(self, file=None, macro_name="SOC_RESOURCES", platform_name="Generic Platform"):
        """ Generate a C header file that simplifies access to the platform's resources.

        Parameters:
            macro_name -- Optional. The name of the guard macro for the C header, as a string without spaces.
            file       -- Optional. If provided, this will be treated as the file= argument to the print()
                          function. This can be used to generate file content instead of printing to the terminal.
        """

        def emit(content):
            """ Utility function that emits a string to the targeted file. """
            print(content, file=file)

        # Create a mapping that maps our register sizes to C types.
        types_for_size = {
            8: 'uint64_t',
            4: 'uint32_t',
            2: 'uint16_t',
            1: 'uint8_t'
        }

        # Emit a warning header.
        emit("/*")
        emit(" * Automatically generated by LUNA; edits will be discarded on rebuild.")
        emit(" * (Most header files phrase this 'Do not edit.'; be warned accordingly.)")
        emit(" *")
        emit(f" * Generated: {datetime.datetime.now()}.")
        emit(" */")
        emit("\n")

        emit(f"#ifndef __{macro_name}_H__")
        emit(f"#define __{macro_name}_H__")
        emit("")
        emit("#include <stdint.h>\n")
        emit("#include <stdbool.h>")
        emit("")

        emit("//")
        emit("// Environment Information")
        emit("//")

        emit("")
        emit(f"#define PLATFORM_NAME \"{platform_name}\"")
        emit("")

        # Emit our constant data for all Minerva CPUs.
        # TODO Support vexriscv
        self._emit_minerva_basics(emit)

        emit("//")
        emit("// Peripherals")
        emit("//")

        window:      MemoryMap
        window_name: MemoryMap.Name
        for window, window_name, (start, end, ratio) in self.memory_map.windows():
            base_addr = start

            resource_info: ResourceInfo
            for resource_info in window.all_resources():
                # normalize path and generate name
                path = resource_info.path
                path = tuple([MemoryMap.Name(n) for n in path[0]] if len(path) == 1 else path)
                name = "_".join([str(s[0].lower()) for s in path])

                # get resource address & size
                start = resource_info.start
                addr  = base_addr + start
                size  = resource_info.end - start

                # always generate a macro for the resource's address and size
                emit(f"#define {name.upper()}_ADDRESS (0x{addr:08x}U)")
                emit(f"#define {name.upper()}_SIZE ({size})")

                # if the resource is not a register we're done
                register: csr.Register = resource_info.resource
                if not issubclass(register.__class__, csr.Register):
                    continue

                # generate convenience macros for reading and writing the register
                c_type = types_for_size[size]

                # Generate a read stub, if useful...
                if register._access.readable():
                    emit(f"static inline {c_type} {name}_read(void) {{")
                    emit(f"    volatile {c_type} *reg = ({c_type} *){name.upper()}_ADDRESS;")
                    emit(f"    return *reg;")
                    emit(f"}}")

                # ... and a write stub.
                if register._access.writable():
                    emit(f"static inline void {name}_write({c_type} value) {{")
                    emit(f"    volatile {c_type} *reg = ({c_type} *){name.upper()}_ADDRESS;")
                    emit(f"    *reg = value;")
                    emit(f"}}")

                emit("")

        emit("//")
        emit("// Interrupts")
        emit("//")
        for irq, (name, peripheral) in self.interrupts.items():

            # Function that determines if a given unit has an IRQ pending.
            emit(f"static inline bool {name}_interrupt_pending(void) {{")
            emit(f"    return pending_irqs() & (1 << {irq});")
            emit(f"}}")

            # IRQ masking
            emit(f"static inline void {name}_interrupt_enable(void) {{")
            emit(f"    irq_setmask(irq_getmask() | (1 << {irq}));")
            emit(f"}}")
            emit(f"static inline void {name}_interrupt_disable(void) {{")
            emit(f"    irq_setmask(irq_getmask() & ~(1 << {irq}));")
            emit(f"}}")

        emit("#endif")
        emit("")

    def _emit_minerva_basics(self, emit):
        """ Emits the standard Minerva RISC-V CSR functionality.

        Parameters
        ----------
        emit: callable(str)
            The function used to print the code lines to the output stream.
        """

        emit("#ifndef read_csr")
        emit("#define read_csr(reg) ({ unsigned long __tmp; \\")
        emit("  asm volatile (\"csrr %0, \" #reg : \"=r\"(__tmp)); \\")
        emit("  __tmp; })")
        emit("#endif")
        emit("")
        emit("#ifndef write_csr")
        emit("#define write_csr(reg, val) ({ \\")
        emit("  asm volatile (\"csrw \" #reg \", %0\" :: \"rK\"(val)); })")
        emit("#endif")
        emit("")
        emit("#ifndef set_csr")
        emit("#define set_csr(reg, bit) ({ unsigned long __tmp; \\")
        emit("  asm volatile (\"csrrs %0, \" #reg \", %1\" : \"=r\"(__tmp) : \"rK\"(bit)); \\")
        emit("  __tmp; })")
        emit("#endif")
        emit("")
        emit("#ifndef clear_csr")
        emit("#define clear_csr(reg, bit) ({ unsigned long __tmp; \\")
        emit("  asm volatile (\"csrrc %0, \" #reg \", %1\" : \"=r\"(__tmp) : \"rK\"(bit)); \\")
        emit("  __tmp; })")
        emit("#endif")
        emit("")

        emit("#ifndef MSTATUS_MIE")
        emit("#define MSTATUS_MIE         0x00000008")
        emit("#endif")
        emit("")

        emit("//")
        emit("// Minerva headers")
        emit("//")
        emit("")
        emit("static inline uint32_t irq_getie(void)")
        emit("{")
        emit("        return (read_csr(mstatus) & MSTATUS_MIE) != 0;")
        emit("}")
        emit("")
        emit("static inline void irq_setie(uint32_t ie)")
        emit("{")
        emit("        if (ie) {")
        emit("                set_csr(mstatus, MSTATUS_MIE);")
        emit("        } else {")
        emit("                clear_csr(mstatus, MSTATUS_MIE);")
        emit("        }")
        emit("}")
        emit("")
        emit("static inline uint32_t irq_getmask(void)")
        emit("{")
        emit("        return read_csr(0x330);")
        emit("}")
        emit("")
        emit("static inline void irq_setmask(uint32_t value)")
        emit("{")
        emit("        write_csr(0x330, value);")
        emit("}")
        emit("")
        emit("static inline uint32_t pending_irqs(void)")
        emit("{")
        emit("        return read_csr(0x360);")
        emit("}")
        emit("")
