#!/usr/bin/env python3
"""Tooling to handle .xci files for Vivado IP cores."""
#
# Copyright 2020 Ettus Research, a National Instruments Company
#
# SPDX-License-Identifier: GPL-3.0-or-later
#

import argparse
import os
import re
import sys


# Parse command line options
def parse_args():
    """Parse command line arguments."""
    parser = argparse.ArgumentParser(
        description="Utility script to query and modify a Xilinx IP XCI file"
    )
    parser.add_argument(
        "action",
        type=str,
        default=None,
        help="Action to perform",
        choices=["read_target", "read_arch", "read_partid", "read_part", "retarget", "rename"],
    )
    parser.add_argument("xci_filepath", type=str, default=None, help="Name for the IP core")
    parser.add_argument(
        "--target",
        type=str,
        default=None,
        help="Input value for target. Must be of the form <arch>/<device>/<package>/<speedgrade>/<silicon revision>",
    )
    parser.add_argument("--name", type=str, default=None, help="Input value for new IP name")
    parser.add_argument("--output_dir", type=str, default=".", help="Build directory for IP")
    args = parser.parse_args()
    if not args.action:
        print("ERROR: Please specify an action to perform\n")
        parser.print_help()
        sys.exit(1)
    if not args.xci_filepath:
        print("ERROR: Please specify the location for the XCI file to operate on\n")
        parser.print_help()
        sys.exit(1)
    if not os.path.isfile(args.xci_filepath):
        print(f"ERROR: XCI File {args.xci_filepath} could not be accessed or is not a file.\n")
        parser.print_help()
        sys.exit(1)
    return args


def get_top_level_match_str(tag_text):
    """Get the regex string to match a top-level tag."""
    return r"(.*\<" + tag_text + r"\>)(.+)(\</" + tag_text + r"\>)"


def get_match_str(item):
    """Get the regex string to match a configurable element value."""
    return (
        r'(.*\<spirit:configurableElementValue spirit:referenceId=".*\.'
        + item
        + r'"\>)(.+)(\</spirit:configurableElementValue\>)'
    )


def get_empty_match_str(item):
    """Get the regex string to match an empty configurable element value."""
    return r'(.*\<spirit:configurableElementValue spirit:referenceId=".*\.' + item + r'")/\>'


def main():
    """Main function."""
    args = parse_args()

    # Read XCI File
    with open(args.xci_filepath) as in_file:
        xci_lines = in_file.readlines()

    if args.action.startswith("read_"):
        # Extract info from XCI File
        xci_info = dict()
        for line in xci_lines:
            m = re.search(
                get_match_str(
                    "(ARCHITECTURE|DEVICE|PACKAGE|SPEEDGRADE|TEMPERATURE_GRADE|SILICON_REVISION)"
                ),
                line,
            )
            if m is not None:
                xci_info[m.group(2)] = m.group(3)
            else:
                m = re.search(
                    get_empty_match_str(
                        "(ARCHITECTURE|DEVICE|PACKAGE|SPEEDGRADE|TEMPERATURE_GRADE|SILICON_REVISION)"
                    ),
                    line,
                )
                if m is not None:
                    xci_info[m.group(2)] = ""
        if args.action == "read_target":
            print(
                xci_info["ARCHITECTURE"]
                + "/"
                + xci_info["DEVICE"]
                + "/"
                + xci_info["PACKAGE"]
                + "/"
                + xci_info["SPEEDGRADE"]
            )
        if args.action == "read_arch":
            print(xci_info["ARCHITECTURE"])
        if args.action == "read_partid":
            print(
                xci_info["DEVICE"]
                + "/"
                + xci_info["PACKAGE"]
                + "/"
                + xci_info["SPEEDGRADE"]
                + "/"
                + xci_info["TEMPERATURE_GRADE"]
                + "/"
                + xci_info["SILICON_REVISION"]
            )
        if args.action == "read_part":
            # The UltraScale+ RFSoC family IDs are expected differently in
            # Vivado, a '-' must separate each property.
            if xci_info["ARCHITECTURE"] == "zynquplusRFSOC":
                print(
                    xci_info["DEVICE"]
                    + "-"
                    + xci_info["PACKAGE"]
                    + xci_info["SPEEDGRADE"]
                    + "-"
                    + xci_info["TEMPERATURE_GRADE"]
                )
            else:
                print(xci_info["DEVICE"] + xci_info["PACKAGE"] + xci_info["SPEEDGRADE"])
    elif args.action == "retarget":
        # Write a new XCI file with modified target info
        if not os.path.isdir(args.output_dir):
            print(
                "ERROR: IP Build directory "
                + args.output_dir
                + " could not be accessed or is not a directory."
            )
            sys.exit(1)
        if not args.target:
            print("ERROR: No target specified.")
            sys.exit(1)
        target_tok = args.target.split("/")
        if len(target_tok) < 4:
            print(
                "ERROR: Invalid target format. "
                "Must be <arch>/<device>/<package>/<speedgrade> or "
                "<arch>/<device>/<package>/<speedgrade>/<tempgrade>/<silicon revision>"
            )
            sys.exit(1)

        replace_dict = {
            "ARCHITECTURE": target_tok[0],
            "DEVICE": target_tok[1],
            "PACKAGE": target_tok[2],
            "SPEEDGRADE": target_tok[3],
            "C_XDEVICEFAMILY": target_tok[0],
            "C_FAMILY": target_tok[0],
            "C_XDEVICE": target_tok[1],
        }
        if len(target_tok) > 4:
            replace_dict["TEMPERATURE_GRADE"] = target_tok[4]
        if len(target_tok) > 5:
            replace_dict["SILICON_REVISION"] = target_tok[5]
        out_xci_filename = os.path.join(
            os.path.abspath(args.output_dir), os.path.basename(args.xci_filepath)
        )

        with open(out_xci_filename, "w") as out_file:
            for r_line in xci_lines:
                w_line = r_line
                m = re.search(
                    get_match_str("(" + "|".join(list(replace_dict.keys())) + ")"), r_line
                )
                if m is not None:
                    w_line = m.group(1) + replace_dict[m.group(2)] + m.group(4) + "\n"
                else:
                    m = re.search(
                        get_empty_match_str("(" + "|".join(list(replace_dict.keys())) + ")"), r_line
                    )
                    if m is not None:
                        w_line = (
                            m.group(1)
                            + ">"
                            + replace_dict[m.group(2)]
                            + "</spirit:configurableElementValue>\n"
                        )
                out_file.write(w_line)
    elif args.action == "rename":
        # Write a new XCI file with a new name
        if not os.path.isdir(args.output_dir):
            print(
                "ERROR: IP Build directory "
                + args.output_dir
                + " could not be accessed or is not a directory."
            )
            sys.exit(1)
        if not args.name:
            print("ERROR: No name specified.")
            sys.exit(1)
        new_name = args.name

        replace_dict = {"Component_Name": new_name}
        replace_top_level = ("spirit:instanceName", new_name)
        out_xci_filename = os.path.join(os.path.abspath(args.output_dir), new_name + ".xci")

        with open(out_xci_filename, "w") as out_file:
            for r_line in xci_lines:
                w_line = r_line
                m = re.search(
                    get_match_str("(" + "|".join(list(replace_dict.keys())) + ")"), r_line
                )
                if m is not None:
                    w_line = m.group(1) + replace_dict[m.group(2)] + m.group(4) + "\n"
                else:
                    m = re.search(
                        get_empty_match_str("(" + "|".join(list(replace_dict.keys())) + ")"), r_line
                    )
                    if m is not None:
                        w_line = (
                            m.group(1)
                            + ">"
                            + replace_dict[m.group(2)]
                            + "</spirit:configurableElementValue>\n"
                        )
                    else:
                        m = re.search(get_top_level_match_str(replace_top_level[0]), r_line)
                        if m is not None:
                            w_line = m.group(1) + replace_top_level[1] + m.group(3) + "\n"
                out_file.write(w_line)


if __name__ == "__main__":
    main()
