#!/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.

import os
import argparse
import datetime
import warnings

from openpyxl import load_workbook
from jinja2 import FileSystemLoader, Environment


CURRENT_DIR = os.path.dirname(os.path.abspath(__file__))
RIOTBASE = os.getenv(
    "RIOTBASE", os.path.abspath(os.path.join(CURRENT_DIR, "../../../..")))
STM32_KCONFIG_DIR = os.path.join(RIOTBASE, "cpu/stm32/kconfigs")
MODEL_COLUMN = 1


def parse_sheet(cpu_fam, sheets):
    """Parse the Excel sheet and return a dict."""
    models = []
    for sheet in sheets:
        # filter warning raised by openpyxl
        with warnings.catch_warnings(record=True):
            warnings.simplefilter("always")
            # Load the content of the xlsx sheet
            sheet = load_workbook(filename=sheet, data_only=True).active

        # Extract models from sheet
        for idx, row in enumerate(sheet.rows):
            # Row index starts at 1
            model = str(sheet.cell(row=idx + 1, column=MODEL_COLUMN).value)
            if not model.startswith("STM32"):
                continue
            models.append(model.replace("-", "_"))
    return sorted(models)


def parse_cpu_lines(cmsis_dir, cpu_fam):
    """Return the list of available CPU lines."""
    cpu_lines = [
        header[:-2].upper() for header in os.listdir(cmsis_dir)
        if (
            header.startswith("stm32") and
            header != "stm32{}xx.h".format(cpu_fam)
        )
    ]
    return sorted(cpu_lines)


def _match(model, line):
    """Return True if a cpu model matches a cpu line, False otherwise."""
    model = model.replace("_", "")
    family_model = model[6:9]
    family_model_letter1 = model[9]
    family_model_letter2 = model[10] if len(model) >= 11 else None
    family_model_letter3 = model[11] if len(model) == 12 else None

    family_line = line[6:9]
    family_line_letter1 = line[9]
    family_line_letter2 = line[10] if len(line) >= 11 else None
    family_line_letter3 = line[11] if len(line) == 12 else None

    if family_line_letter1 == "X" and family_line_letter2 == "X":
        letters_match = True
    elif family_line_letter1 == "X":
        if family_model_letter3 is not None or family_line_letter3 is not None:
            letters_match = (
                (family_line_letter2 == family_model_letter2) and
                (family_line_letter3 == family_model_letter3)
            )
        else:
            letters_match = family_line_letter2 == family_model_letter2
    elif family_line_letter2 == "X":
        letters_match = family_line_letter1 == family_model_letter1
    else:
        letters_match = False
    return family_model == family_line and letters_match


def get_context(cpu_fam, models, lines):
    """Return a dict where keys are the models and values are the lines/fam."""
    mapping = []
    for model in models:
        found_line = False
        for line in lines:
            if _match(model, line):
                mapping.append(
                    {"model": model, "line": "CPU_LINE_{}".format(line)}
                )
                found_line = True
        # if a model has no matching line, just match it to the upper cpu
        # fam level
        if not found_line:
            mapping.append(
                {"model": model, "line": "CPU_FAM_{}".format(cpu_fam)}
            )
    return {"models": mapping, "lines": lines, "fam": cpu_fam}


def generate_kconfig(kconfig, context, overwrite, verbose):
    """Generic kconfig file generator."""
    loader = FileSystemLoader(searchpath=CURRENT_DIR)
    env = Environment(
        loader=loader, trim_blocks=False, lstrip_blocks=True,
        keep_trailing_newline=True
    )
    template_file = os.path.join("Kconfig.{}.j2".format(kconfig))
    env.globals.update(zip=zip)
    template = env.get_template(template_file)
    context.update({"year": datetime.datetime.now().year})
    render = template.render(**context)

    kconfig_dir = os.path.join(STM32_KCONFIG_DIR, context["fam"])
    kconfig_file = os.path.join(kconfig_dir, "Kconfig.{}".format(kconfig))

    # Create family Kconfig dir if it doesn't exist yet
    if not os.path.exists(kconfig_dir):
        os.makedirs(kconfig_dir)

    if (not os.path.exists(kconfig_file) or
            (os.path.exists(kconfig_file) and
             overwrite is True and
             kconfig == "models")):
        with open(kconfig_file, "w") as f_dest:
            f_dest.write(render)
    if verbose:
        print("{}:".format(os.path.basename(kconfig_file)))
        print("-" * (len(os.path.basename(kconfig_file)) + 1) + "\n")
        print(render)


def main(args):
    """Main function."""
    models = parse_sheet(args.cpu_fam, args.sheets)
    lines = parse_cpu_lines(args.cmsis_dir, args.cpu_fam)
    context = get_context(args.cpu_fam, models, lines)
    if args.verbose:
        print("Generated kconfig files:\n")
    for kconfig in ["lines", "models"]:
        generate_kconfig(kconfig, context, args.overwrite, args.verbose)


PARSER = argparse.ArgumentParser()
PARSER.add_argument("cmsis_dir",
                    help="STM32 CMSIS directory")
PARSER.add_argument("cpu_fam",
                    help="STM32 CPU Family")
PARSER.add_argument("--sheets", "-s", nargs='+',
                    help="Excel sheet containing the list of products")
PARSER.add_argument("--overwrite", "-o", action="store_true",
                    help="Overwrite any existing Kconfig file")
PARSER.add_argument("--verbose", "-v", action="store_true",
                    help="Print generated file content")


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