#!/usr/bin/env python3
# Copyright lowRISC contributors.
# Licensed under the Apache License, Version 2.0, see LICENSE for details.
# SPDX-License-Identifier: Apache-2.0
"""
Script to patch the OpenTitan tree for building english-breakfast-friendly
software.

This script and its associated mechanism will eventually be transitioned to
a pure-Bazel solution.
"""

import argparse
import shutil
import subprocess
from pathlib import Path

# This file is in /hw/top_.../util
REPO_TOP = Path(__file__).resolve().parents[3]

BINARIES = [
    'sw/device/lib/testing/test_rom/test_rom_export_fpga_cw305',
    'sw/device/sca/aes_serial_export_fpga_cw305',
    'sw/device/lib/testing/test_rom/test_rom_export_sim_verilator',
    'sw/device/tests/aes_smoketest_export_sim_verilator',
    'sw/device/examples/hello_world/hello_world_export_sim_verilator',
]

BAZEL_BINARIES = [
    '//sw/device/lib/testing/test_rom',
    '//sw/device/sca:aes_serial',
    '//sw/device/tests:aes_smoketest_prog',
    '//sw/device/examples/hello_world',
]


def find_dirs(root, names):
    """
    Finds all directories under `root` with the given name and
    yields them.
    """
    for p in root.iterdir():
        if not p.is_dir():
            continue
        if p.name in names:
            yield p
        else:
            find_dirs(p, names)


def delete_path(path):
    """
    Deletes a path; will delete directories recursively.
    """
    print(f'* Deleting: {path}')
    if not path.exists():
        return
    if path.is_dir():
        shutil.rmtree(str(path))
    else:
        path.unlink()


def shell_out(cmd):
    print(f"Running {cmd}")
    # Let the resulting exception from `check` propagate out.
    subprocess.run(cmd, check=True)


def main():
    parser = argparse.ArgumentParser(
        prog="prepare_sw",
        description="Script to prepare SW sources for English Breakfast",
        formatter_class=argparse.RawDescriptionHelpFormatter)
    parser.add_argument('--build',
                        '-b',
                        default=False,
                        action='store_true',
                        help='Build ROM based on reduced design')
    parser.add_argument(
        '--delete-only',
        '-d',
        default=False,
        action='store_true',
        help='Delete previously generated auto-gen files without running topgen'
    )
    parser.add_argument('--top',
                        '-t',
                        default='englishbreakfast',
                        type=str,
                        help='The alternative top to use')
    args = parser.parse_args()
    name = args.top
    topname = f'top_{name}'

    # We start by removing any previously generated auto-gen files for the
    # selected non-earlgrey top. These might be stale and confuse topgen.
    print('Purging previously generated auto-gen files')
    for d in find_dirs(REPO_TOP / 'hw' / topname, ['autogen', 'ip_autogen']):
        delete_path(d)

    delete_path(REPO_TOP / 'build' / str(topname + '-autogen'))
    delete_path(REPO_TOP / 'hw' / topname / 'data/autogen')
    delete_path(REPO_TOP / 'hw' / topname / 'dv/autogen')
    delete_path(REPO_TOP / 'hw' / topname / 'dv/env/autogen')
    delete_path(REPO_TOP / 'hw' / topname / 'ip/ast/rtl')
    delete_path(REPO_TOP / 'hw' / topname / 'ip/clkmgr')
    delete_path(REPO_TOP / 'hw' / topname / 'ip/flash_ctrl')
    delete_path(REPO_TOP / 'hw' / topname / 'ip/pinmux')
    delete_path(REPO_TOP / 'hw' / topname / 'ip/pwrmgr')
    delete_path(REPO_TOP / 'hw' / topname / 'ip/rstmgr')
    delete_path(REPO_TOP / 'hw' / topname / 'ip/sensor_ctrl/rtl')
    delete_path(REPO_TOP / 'hw' / topname / 'ip/xbar_main')
    delete_path(REPO_TOP / 'hw' / topname / 'ip/xbar_peri')
    delete_path(REPO_TOP / 'hw' / topname / 'rtl/autogen')
    delete_path(REPO_TOP / 'hw' / topname / 'sw/autogen')

    if args.delete_only:
        return

    # Next, we need to re-run topgen in order to create all auto-generated files.
    shell_out([
        REPO_TOP / 'util/topgen.py',
        '-t',
        REPO_TOP / 'hw' / topname / 'data' / f"{topname}.hjson",
        '-o',
        REPO_TOP / 'hw' / topname,
    ])

    # We need to patch some files:
    # 1. Build system files need to be pointed to the proper auto-gen files.
    #    Specifically, we overwrite the earlgrey autogen-ed files with the
    #    englishbreakfast equivalents where they differ (i.e. any needed ip in
    #    `hw/topname/{ip, ip_autogen}`).
    # 2. SW sources currently contain
    #    hardcoded references to top_earlgrey. We
    #    need to change some file and variable names in auto-generated files.
    # 3. The build system still uses some sources from the original top level.
    #    We thus need to replace those with the new sources patched in 2.

    # Patch hjson files for Bazel
    print("Transplanting autogen-ed hjson files")
    REG_FILES = [
        'ip_autogen/alert_handler/data/alert_handler.hjson',
        'ip/clkmgr/data/autogen/clkmgr.hjson',
        'ip/flash_ctrl/data/autogen/flash_ctrl.hjson',
        'ip/pwrmgr/data/autogen/pwrmgr.hjson',
        'ip/rstmgr/data/autogen/rstmgr.hjson',
        'ip/pinmux/data/autogen/pinmux.hjson',
        'ip_autogen/rv_plic/data/rv_plic.hjson',
        'ip/ast/data/ast.hjson',
        'ip/sensor_ctrl/data/sensor_ctrl.hjson',
    ]
    for reg_file in REG_FILES:
        src = REPO_TOP / 'hw' / topname / reg_file
        dst = REPO_TOP / 'hw' / 'top_earlgrey' / reg_file
        print(f"* Copying {src} -> {dst}")
        dst.write_text(src.read_text())

    for suffix in ['.c', '.h', '_memory.h', '_memory.ld']:
        old = REPO_TOP / 'hw' / topname / 'sw/autogen' / (topname + suffix)
        new = REPO_TOP / 'hw/top_earlgrey/sw/autogen' / ('top_earlgrey' +
                                                         suffix)
        print(f"* {old} -> {new}")

        text = old.read_text()
        text = text.replace(name, 'earlgrey')
        text = text.replace(name.capitalize(), 'Earlgrey')
        text = text.replace(name.upper(), 'EARLGREY')

        # The SW build expects to find this file both in the top_earlgrey dir AND
        # in the top_englishbreakfast dir.
        new.write_text(text)
        (old.parent / new.name).write_text(text)

    if not args.build:
        return

    # Build the software including test_rom to enable the FPGA build.
    shell_out([
        REPO_TOP / 'bazelisk.sh',
        'build',
        '--features=-rv32_bitmanip',
        '--copt=-DOT_IS_ENGLISH_BREAKFAST_REDUCED_SUPPORT_FOR_INTERNAL_USE_ONLY_',
    ] + BAZEL_BINARIES)


if __name__ == "__main__":
    main()
