#!/usr/bin/env python3
import xml.etree.ElementTree as ET


class SVDExtractor:
    def __init__(self, filename):
        self.tree = ET.parse(filename)
        self.root = self.tree.getroot()
        self.peripherals = self.root.find("peripherals")

    def extract_register_collection(
        self,
        peripheral_to_print,
        namespace,
        generated_peripheral_name,
        imports=None,
        extra_subclasses=None,
    ):
        for peripheral in self.peripherals.iter("peripheral"):
            peripheral_name = peripheral.find("name").text
            if peripheral_name != peripheral_to_print:
                continue

            register_list = dict()

            peripheral_size = int(peripheral.find("addressBlock").find("size").text, 0)
            registers = peripheral.find("registers")

            if imports is not None:
                for i in imports:
                    print(f"using {i};")
                print()

            print(f"namespace {namespace}")
            print("{")
            print(f"    public class {generated_peripheral_name} : ", end="")
            if extra_subclasses is not None:
                for esc in extra_subclasses:
                    print(f"{esc}, ", end="")
            print(
                "IDoubleWordPeripheral, IProvidesRegisterCollection<DoubleWordRegisterCollection>, IKnownSize"
            )
            print("    {")

            print("        public GPIO IRQ { get; private set; }")
            print(
                "        public DoubleWordRegisterCollection RegistersCollection { get; }"
            )
            print(f"        public long Size => 0x1000; // {hex(peripheral_size)}")
            print("        private readonly Machine machine;")
            print()

            print(
                f"        public {generated_peripheral_name}(Machine machine) : base(machine)"
            )
            print("        {")
            print("            this.machine = machine;")
            print()
            print("            IRQ = new GPIO();")
            print()
            print(
                "            RegistersCollection = new DoubleWordRegisterCollection(this);"
            )
            print("            DefineRegisters();")
            print("            Reset();")
            print("        }")
            print()

            print("        private void DefineRegisters()")
            print("        {")

            for register in registers.iter("register"):
                register_name = register.find("name").text
                register_description = register.find("description").text
                register_offset = int(register.find("addressOffset").text, 0)
                register_default = 0
                register_reset_value_field = register.find("resetValue")
                if register_reset_value_field is not None:
                    register_default = int(register_reset_value_field.text, 0)
                register_list[register_offset] = register_name

                if "\n" in register_description:
                    raise Exception("Register description contains a newline")
                print(f"            // {register_description}")
                print(
                    f"            Registers.{register_name}.Define(this, {hex(register_default)})"
                )
                for field in register.find("fields").iter("field"):
                    field_offset = int(field.find("bitOffset").text, 0)
                    field_width = int(field.find("bitWidth").text, 0)
                    field_name = field.find("name").text
                    field_description = field.find("description").text
                    field_access = field.find("access").text

                    field_access_mask = ""
                    if field_access == "read-write":
                        field_access_mask = "FieldMode.Read | FieldMode.Write, "
                    elif field_access == "read-only":
                        field_access_mask = "FieldMode.Read, "
                    elif field_access == "write-only":
                        field_access_mask = "FieldMode.Write, "
                    else:
                        raise Exception(
                            f"Unrecognized field access type: {field_access}"
                        )

                    if "\n" in field_description:
                        raise Exception("Field description contains a newline")

                    if field_width == 1:
                        print(
                            f'                .WithFlag({field_offset}, {field_access_mask}name: "{field_name}") // {field_description}'
                        )
                    else:
                        print(
                            f'                .WithValueField({field_offset}, {field_width}, {field_access_mask}name: "{field_name}") // {field_description}'
                        )
                print("            ;")
                print()
            print("        }")
            print()

            print("        public override void Reset()")
            print("        {")
            print("            RegistersCollection.Reset();")
            print("            UpdateInterrupts();")
            print("        }")
            print()
            print("        public uint ReadDoubleWord(long offset)")
            print("        {")
            print("            return RegistersCollection.Read(offset);")
            print("        }")
            print()
            print("        public void WriteDoubleWord(long offset, uint value)")
            print("        {")
            print("            RegistersCollection.Write(offset, value);")
            print("        }")

            print("        private void UpdateInterrupts()")
            print("        {")
            print("            var status = false;")
            print('            this.Log(LogLevel.Noisy, "Setting IRQ to {0}", status);')
            print("            IRQ.Set(status);")
            print("        }")
            print()
            print("        private enum Registers")
            print("        {")
            for offset, name in register_list.items():
                print(f"            {name} = {hex(offset)},")
            print("        }")
            print("    }")
            print("}")

    def extract_register_collection(
        self,
        peripheral_to_print,
        namespace,
        generated_peripheral_name,
        imports=None,
        extra_subclasses=None,
    ):
        for peripheral in self.peripherals.iter("peripheral"):
            peripheral_name = peripheral.find("name").text
            if peripheral_name != peripheral_to_print:
                continue

            register_list = dict()

            peripheral_size = int(peripheral.find("addressBlock").find("size").text, 0)
            registers = peripheral.find("registers")

            if imports is not None:
                for i in imports:
                    print(f"using {i};")
                print()

            print(f"namespace {namespace}")
            print("{")
            print(f"    public class {generated_peripheral_name} : ", end="")
            if extra_subclasses is not None:
                for esc in extra_subclasses:
                    print(f"{esc}, ", end="")
            print(
                "IDoubleWordPeripheral, IKnownSize"
            )
            print("    {")

            print("        public GPIO IRQ { get; private set; }")
            print(
                "        private readonly DoubleWordRegisterCollection registers;"
            )
            print(f"        public long Size => 0x1000; // {hex(peripheral_size)}")
            print("        private readonly Machine machine;")
            print()

            print(
                f"        public {generated_peripheral_name}(Machine machine) : base(machine)"
            )
            print("        {")
            print("            this.machine = machine;")
            print()
            print("            IRQ = new GPIO();")
            print()
            print(
                "            RegistersCollection = new DoubleWordRegisterCollection(this);"
            )
            print("            DefineRegisters();")
            print("            Reset();")
            print("        }")
            print()

            print("        private void DefineRegisters()")
            print("        {")
            print("            var registersMap = new Dictionary<long, DoubleWordRegister>")
            print("            {")

            for register in registers.iter("register"):
                register_name = register.find("name").text
                register_description = register.find("description").text
                register_offset = int(register.find("addressOffset").text, 0)
                register_default = 0
                register_reset_value_field = register.find("resetValue")
                if register_reset_value_field is not None:
                    register_default = int(register_reset_value_field.text, 0)
                register_list[register_offset] = register_name

                if "\n" in register_description:
                    raise Exception("Register description contains a newline")
                print(f"                // {hex(register_offset)}: {register_description}")
                print(
                    f"                {{(long)Registers.{register_name}, new DoubleWordRegister(this)"
                )
                for field in register.find("fields").iter("field"):
                    field_offset = int(field.find("bitOffset").text, 0)
                    field_width = int(field.find("bitWidth").text, 0)
                    field_name = field.find("name").text
                    field_description = field.find("description").text
                    field_access = field.find("access").text

                    field_access_mask = ""
                    if field_access == "read-write":
                        field_access_mask = "FieldMode.Read | FieldMode.Write, "
                    elif field_access == "read-only":
                        field_access_mask = "FieldMode.Read, "
                    elif field_access == "write-only":
                        field_access_mask = "FieldMode.Write, "
                    else:
                        raise Exception(
                            f"Unrecognized field access type: {field_access}"
                        )

                    if "\n" in field_description:
                        raise Exception("Field description contains a newline")

                    if field_width == 1:
                        print(
                            f'                    .WithFlag({field_offset}, {field_access_mask}name: "{field_name}") // {field_description}'
                        )
                    else:
                        print(
                            f'                    .WithValueField({field_offset}, {field_width}, {field_access_mask}name: "{field_name}") // {field_description}'
                        )
                print("                },")
            print("            };")
            print("            registers = new DoubleWordRegisterCollection(this, registersMap);")
            print("        }")
            print()

            print("        public override void Reset()")
            print("        {")
            print("            RegistersCollection.Reset();")
            print("            UpdateInterrupts();")
            print("        }")
            print()
            print("        public uint ReadDoubleWord(long offset)")
            print("        {")
            print("            return RegistersCollection.Read(offset);")
            print("        }")
            print()
            print("        public void WriteDoubleWord(long offset, uint value)")
            print("        {")
            print("            RegistersCollection.Write(offset, value);")
            print("        }")

            print("        private void UpdateInterrupts()")
            print("        {")
            print("            var status = false;")
            print('            this.Log(LogLevel.Noisy, "Setting IRQ to {0}", status);')
            print("            IRQ.Set(status);")
            print("        }")
            print()
            print("        private enum Registers")
            print("        {")
            for offset, name in register_list.items():
                print(f"            {name} = {hex(offset)},")
            print("        }")
            print("    }")
            print("}")


if __name__ == "__main__":
    extractor = SVDExtractor("esp32s3.svd")
    # extractor.extract_register_collection(
    #     "SPI1",
    #     "Antmicro.Renode.Peripherals.SPI",
    #     "ESP32_SPIController",
    #     imports=[
    #         "System.Linq",
    #         "System.Collections.Generic",
    #         "Antmicro.Renode.Core",
    #         "Antmicro.Renode.Core.Structure",
    #         "Antmicro.Renode.Core.Structure.Registers",
    #         "Antmicro.Renode.Logging",
    #         "Antmicro.Renode.Peripherals.Bus",
    #         "Antmicro.Renode.Peripherals.Memory",
    #         "Antmicro.Renode.Peripherals.MTD",
    #     ],
    #     extra_subclasses=["NullRegistrationPointPeripheralContainer<ISPIPeripheral>"],
    # )
    # extractor.extract_register_collection(
    #     "INTERRUPT_CORE0",
    #     "Antmicro.Renode.Peripherals.IRQControllers",
    #     "ESP32S3",
    #     imports=[
    #         "System",
    #         "System.Collections.ObjectModel",
    #         "Antmicro.Renode.Peripherals.Bus",
    #         "Antmicro.Renode.Core",
    #         "Antmicro.Renode.Core.Structure.Registers",
    #         "Antmicro.Renode.Logging",
    #         "System.Collections.Generic",
    #         "Antmicro.Renode.Utilities",
    #     ],
    #     extra_subclasses=["IIRQController", "INumberedGPIOOutput"],
    # )
    extractor.extract_register_collection(
        "EXTMEM",
        "Antmicro.Renode.Peripherals.Miscellaneous",
        "ESP32S3",
        imports=[
            "System",
            "Antmicro.Renode.Peripherals.Bus",
            "Antmicro.Renode.Core",
            "Antmicro.Renode.Peripherals",
            "Antmicro.Renode.Utilities",
            "Antmicro.Renode.Logging",
            "System.Collections.Generic",
            "Antmicro.Renode.Core.Structure.Registers",
        ],
        extra_subclasses=[],
    )
