#!/usr/bin/env python3

# Copyright (C) 2020 Inria
#
# This file is subject to the terms and conditions of the GNU Lesser
# General Public License v2.1. See the file LICENSE in the top level
# directory for more details.

"""Generate the vectors table for a given STM32 CPU line."""

import os
import argparse
import re


CURRENT_DIR = os.path.dirname(os.path.abspath(__file__))
RIOTBASE = os.getenv(
    "RIOTBASE", os.path.abspath(os.path.join(CURRENT_DIR, "../../../..")))
STM32_VECTORS_DIR = os.path.join(RIOTBASE, "cpu/stm32/vectors")
STM32_VENDOR_DIR = os.path.join(RIOTBASE, "cpu/stm32/include/vendor")

VECTORS_FORMAT = """
/*
 * PLEASE DON'T EDIT
 *
 * This file was automatically generated by
 * ./cpu/stm32/dist/irqs/gen_vectors.py
 */

#include "vectors_cortexm.h"

/* define a local dummy handler as it needs to be in the same compilation unit
 * as the alias definition */
void dummy_handler(void) {{
    dummy_handler_default();
}}

/* {cpu_line} specific interrupt vectors */
{isr_lines}

/* CPU specific interrupt vector table */
ISR_VECTOR(1) const isr_t vector_cpu[CPU_IRQ_NUMOF] = {{
{irq_lines}
}};
"""


def parse_cmsis(cmsis_dir, cpu_line):
    """Parse the CMSIS to get the list IRQs."""
    if cpu_line == "STM32F030x4":
        # STM32F030x4 is provided in the RIOT codebase in a different location
        cpu_line_cmsis = os.path.join(
            STM32_VENDOR_DIR, "{}.h".format(cpu_line.lower()))
    elif cpu_line.startswith("STM32MP1"):
        # STM32MP157Cxx is provided in the RIOT codebase in a different location
        cpu_line_cmsis = os.path.join(
            STM32_VENDOR_DIR, "{}_cm4.h".format(cpu_line.lower()))
    else:
        cpu_line_cmsis = os.path.join(
            "{}/{}.h".format(cmsis_dir, cpu_line.lower())
        )

    with open(cpu_line_cmsis, 'rb') as cmsis:
        cmsis_content = cmsis.readlines()

    irq_lines = []
    use_line = False
    for line in cmsis_content:
        try:
            line = line.decode()
        except UnicodeDecodeError:
            # skip line that contains non unicode characters
            continue
        # start filling lines after interrupt Doxygen comment
        if "typedef enum" in line:
            irq_lines = []  # Cleanup any previous content
            use_line = True

        # use a regexp to get the available IRQs
        match = re.match(r"[ ]+([a-zA-Z0-9_]+_IRQn)[ ]+= \d+", line)

        # Skip lines that don't match
        if match is None:
            continue

        # Skip remapped USB interrupt
        # (set as alias for USBWakeUp_IRQn on stm32f3)
        if "USBWakeUp_RMP_IRQn" in line:
            continue

        # Stop at the end of the IRQn_Type enum definition
        if "IRQn_Type" in line:
            break

        # Only append IRQ line if it should be used and is not empty
        if use_line:
            irq_lines.append(match.group(1).strip())

    isrs = [
        {
            "irq": irq,
            "func": "exti" if "EXTI" in irq else irq.lower().rsplit("_", 1)[0]
        }
        for irq in irq_lines
    ]

    return {"isrs": isrs, "cpu_line": cpu_line}


def generate_vectors(context):
    """Use vector template string to generate the vectors C file."""
    isr_line_format = "WEAK_DEFAULT void isr_{func}(void);"
    irq_line_format = "    [{irq:<35}] = isr_{func},"

    isr_lines = []
    irq_lines = []
    for isr in context["isrs"]:
        isr_line = isr_line_format.format(**isr)
        if isr_line not in isr_lines:
            isr_lines.append(isr_line)
        irq_lines.append(irq_line_format.format(**isr))
    vectors_content = VECTORS_FORMAT.format(
        cpu_line=context["cpu_line"],
        isr_lines="\n".join(isr_lines),
        irq_lines="\n".join(irq_lines),
    )

    dest_file = os.path.join(
        STM32_VECTORS_DIR, "{}.c".format(context["cpu_line"])
    )

    with open(dest_file, "w") as f_dest:
        f_dest.write(vectors_content)


def main(args):
    """Main function."""
    context = parse_cmsis(args.cmsis_dir, args.cpu_line)
    generate_vectors(context)


PARSER = argparse.ArgumentParser()
PARSER.add_argument("cmsis_dir", help="CMSIS directory")
PARSER.add_argument("cpu_line", help="STM32 CPU line")

if __name__ == "__main__":
    main(PARSER.parse_args())
