#!/usr/bin/env python3
# By Claude on 07/04/2026
"""Migrate corr-xml configuration files to the current curt_* key format.

CuasRtParameters history:
  pre-07/2026   keys CLT_PARAMETERS._imp_curt_<name>   (curt_* fields inside IntersceneMatchParameters,
                then the extracted class still saved under imp with the same keys)
  07/2026 -     keys CLT_PARAMETERS._curt_<name>       (CuasRtParameters is a CLTParameters peer,
                like imp/ofp/ilp)

The Java reader keeps a legacy fallback (it reads _imp_curt_* when present, then
_curt_* overrides), so unmigrated files still load. This script rewrites the keys
so files are in the current format and future removal of the fallback is safe.

Usage:
  migrate_curt_config.py FILE [FILE ...]        # in-place, keeps FILE.bak
  migrate_curt_config.py --dry-run FILE ...     # report only, no changes
  migrate_curt_config.py --no-backup FILE ...   # in-place without .bak

Only the literal substring '_imp_curt_' inside key="..." attributes is renamed to
'_curt_'; everything else (values, formatting, comments) is byte-preserved. If a
file already contains a _curt_ key that a rename would duplicate, the old key is
dropped (the new one wins) and a warning is printed.

A provenance XML comment (absolute path of this script + timestamp) is inserted
right after the <properties> line of every modified file. Java's XML properties
loader ignores XML comments, but note it also DROPS them if the application
re-saves the file - the marker survives only until the next save from ImageJ.
"""

import argparse
import datetime
import os
import re
import shutil
import sys

KEY_RE = re.compile(r'(<entry key=")([^"]*)_imp_curt_([^"]*)(")')


def migrate_text(text):
    """Return (new_text, n_renamed, dropped_keys)."""
    existing_new = set(re.findall(r'<entry key="([^"]*_curt_[^"]*)"', text))
    existing_new = {k for k in existing_new if '_imp_curt_' not in k}
    renamed = [0]
    dropped = []
    out_lines = []
    for line in text.splitlines(keepends=True):
        m = KEY_RE.search(line)
        if m:
            new_key = m.group(2) + '_curt_' + m.group(3)
            if new_key in existing_new:
                dropped.append(m.group(2) + '_imp_curt_' + m.group(3))
                continue  # drop the whole legacy entry line - the new key already exists
            line = KEY_RE.sub(r'\g<1>\g<2>_curt_\g<3>\g<4>', line)
            renamed[0] += 1
        out_lines.append(line)
    return ''.join(out_lines), renamed[0], dropped


def add_provenance_comment(text):
    """Insert a provenance XML comment after the <properties> line."""
    stamp = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
    marker = ('<!-- curt_* keys migrated (_imp_curt_* to _curt_*) by %s on %s -->\n'
              % (os.path.abspath(__file__), stamp))
    lines = text.splitlines(keepends=True)
    for i, line in enumerate(lines):
        if '<properties>' in line:
            lines.insert(i + 1, marker)
            return ''.join(lines)
    return marker + text  # no <properties> line found - prepend (still valid for grep)


def main():
    ap = argparse.ArgumentParser(description=__doc__.splitlines()[0])
    ap.add_argument('files', nargs='+', help='corr-xml files to migrate')
    ap.add_argument('--dry-run', action='store_true', help='report only, do not modify')
    ap.add_argument('--no-backup', action='store_true', help='do not keep a .bak copy')
    args = ap.parse_args()

    status = 0
    for path in args.files:
        try:
            with open(path, encoding='utf-8') as f:
                text = f.read()
        except OSError as e:
            print(f"{path}: ERROR - {e}", file=sys.stderr)
            status = 1
            continue
        new_text, n, dropped = migrate_text(text)
        for k in dropped:
            print(f"{path}: WARNING - dropped legacy entry '{k}' (current-format key already present)")
        if n == 0 and not dropped:
            print(f"{path}: already current (no _imp_curt_ keys)")
            continue
        if args.dry_run:
            print(f"{path}: would rename {n} key(s)" +
                  (f", drop {len(dropped)} duplicate legacy entrie(s)" if dropped else ""))
            continue
        if not args.no_backup:
            shutil.copy2(path, path + '.bak')
        with open(path, 'w', encoding='utf-8') as f:
            f.write(add_provenance_comment(new_text))
        print(f"{path}: renamed {n} key(s)" +
              (f", dropped {len(dropped)} duplicate legacy entrie(s)" if dropped else "") +
              ("" if args.no_backup else f" (backup: {path}.bak)"))
    return status


if __name__ == '__main__':
    sys.exit(main())
