"""
apply_glue.py - put the delay-chain behaviour into the skeletons vsiBuild generated.

    python apply_glue.py <workspace>\\PyDelayChain

vsiBuild emits one <name>.py per component with named "user custom code
regions". This script rewrites the content of specific regions, by name, and
leaves everything else alone, so it can be re-run after every
`generate -overwrite` (the regions themselves survive regeneration; this keeps
their content in one reviewable place instead of scattered across four files).
It also copies vsi_delay.py and delay_config.json into <twin>\\src so every
component imports the same module.

Exit status 1 if any expected region or component is missing, and it says which.
"""

import os
import re
import shutil
import sys

HERE = os.path.dirname(os.path.abspath(__file__))

IMPORT_GLUE = [
    "sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)), '..'))",
    "from vsi_delay import VariableDelay",
]

# component -> {region name -> lines}
GLUE = {
    "pySource": {
        "Global Variables & Definitions": IMPORT_GLUE,
        "Before sending the packet": [
            "self.mySignals.tick += 1",
        ],
    },
    "delay1": {
        "Global Variables & Definitions": IMPORT_GLUE,
        "Constructor": [
            "self.delay = VariableDelay.from_config('delay1')",
        ],
        "Before sending the packet": [
            "self.mySignals.tick_out = self.delay.step(self.mySignals.tick_in)",
        ],
    },
    "delay2": {
        "Global Variables & Definitions": IMPORT_GLUE,
        "Constructor": [
            "self.delay = VariableDelay.from_config('delay2')",
        ],
        "Before sending the packet": [
            "self.mySignals.tick_out = self.delay.step(self.mySignals.tick_in)",
        ],
    },
    "pySink": {
        "Global Variables & Definitions": IMPORT_GLUE,
        "After sending the packet": [
            "with open('sink_trace.csv', 'a', encoding='utf-8') as fh:",
            "    fh.write('{},{}\\n'.format(vsiCommonPythonApi.getSimulationTimeInNs(), self.mySignals.tick))",
        ],
    },
}

START = re.compile(r"^(\s*)# Start of user custom code region\. Please apply edits only within these regions:\s+(.*?)\s*$")
END = re.compile(r"^\s*# End of user custom code region\.")


def patch(path, regions):
    with open(path, encoding="utf-8") as fh:
        lines = fh.read().split("\n")
    out, i, seen = [], 0, set()
    while i < len(lines):
        m = START.match(lines[i])
        if not m or m.group(2) not in regions:
            out.append(lines[i])
            i += 1
            continue
        indent, name = m.group(1), m.group(2)
        out.append(lines[i])
        i += 1
        while i < len(lines) and not END.match(lines[i]):
            i += 1                       # drop the old region content
        if i >= len(lines):
            return "region {!r} has no end marker".format(name)
        out.append(indent + "# glue from apply_glue.py")
        for g in regions[name]:
            out.append(indent + g)
        out.append(lines[i])             # the end marker
        seen.add(name)
        i += 1
    missing = set(regions) - seen
    if missing:
        return "regions not found: {}".format(sorted(missing))
    with open(path, "w", encoding="utf-8", newline="\n") as fh:
        fh.write("\n".join(out))
    return None


def main():
    if len(sys.argv) != 2:
        sys.exit(__doc__)
    twin = sys.argv[1]
    src = os.path.join(twin, "src")
    if not os.path.isdir(src):
        sys.exit("no src dir under {}".format(twin))
    for f in ("vsi_delay.py", "delay_config.json"):
        shutil.copy(os.path.join(HERE, f), os.path.join(src, f))
        print("copied", f, "->", src)
    problems = []
    for comp, regions in GLUE.items():
        path = os.path.join(src, comp, comp + ".py")
        if not os.path.isfile(path):
            problems.append("{}: {} not found".format(comp, path))
            continue
        err = patch(path, regions)
        if err:
            problems.append("{}: {}".format(comp, err))
        else:
            print("patched", comp, "regions:", ", ".join(regions))
    if problems:
        print("PROBLEMS:")
        for p in problems:
            print("  " + p)
        sys.exit(1)


if __name__ == "__main__":
    main()
