#!/usr/bin/env python3
"""
Compare structure definitions between FrameL and frameCPP

This tool compares the output of framel_dump_definition and
framecpp_dump_definition to identify differences in structure definitions.
"""

import argparse
import subprocess
import sys
from typing import Dict, List, Tuple, Optional
from dataclasses import dataclass

@dataclass
class FrSE:
    """Represents a structure element (field definition)"""
    name: str
    type: str
    comment: str

@dataclass
class FrSH:
    """Represents a structure header"""
    name: str
    id: int
    comment: str
    fields: List[FrSE]

def parse_definition(output: str) -> Optional[FrSH]:
    """
    Parse the output from framel_dump_definition or framecpp_dump_definition

    Expected format:
    FrSH: StructureName (id=N) "Comment"
      FrSE: fieldName (TYPE) "Comment"
      FrSE: fieldName2 (TYPE) "Comment"
      ...
    """
    lines = output.strip().split('\n')
    if not lines:
        return None

    # Parse FrSH line
    frsh_line = lines[0]
    if not frsh_line.startswith('FrSH: '):
        raise ValueError(f"Expected FrSH line, got: {frsh_line}")

    # Extract: FrSH: Name (id=123) "Comment"
    rest = frsh_line[6:]  # Remove "FrSH: "

    # Find id
    id_start = rest.find('(id=')
    if id_start == -1:
        raise ValueError(f"Could not find id in: {frsh_line}")

    name = rest[:id_start].strip()

    id_end = rest.find(')', id_start)
    id_str = rest[id_start+4:id_end]
    struct_id = int(id_str)

    # Extract comment (between quotes)
    comment_start = rest.find('"', id_end)
    comment_end = rest.rfind('"')
    comment = rest[comment_start+1:comment_end] if comment_start != -1 else ""

    # Parse FrSE lines
    fields = []
    for line in lines[1:]:
        if not line.strip():
            continue
        if not line.startswith('  FrSE: '):
            raise ValueError(f"Expected FrSE line, got: {line}")

        # Extract: FrSE: fieldName (TYPE) "Comment"
        rest = line[8:]  # Remove "  FrSE: "

        # Find type (in parentheses)
        type_start = rest.find('(')
        type_end = rest.find(')')
        if type_start == -1 or type_end == -1:
            raise ValueError(f"Could not find type in: {line}")

        field_name = rest[:type_start].strip()
        field_type = rest[type_start+1:type_end]

        # Extract comment
        comment_start = rest.find('"', type_end)
        comment_end = rest.rfind('"')
        field_comment = rest[comment_start+1:comment_end] if comment_start != -1 else ""

        fields.append(FrSE(field_name, field_type, field_comment))

    return FrSH(name, struct_id, comment, fields)

def compare_definitions(framel_def: FrSH, framecpp_def: FrSH,
                       ignore_comments: bool = False) -> Tuple[bool, List[str]]:
    """
    Compare two structure definitions

    Returns:
        (is_identical, list_of_differences)
    """
    differences = []

    # Compare FrSH
    if framel_def.name != framecpp_def.name:
        differences.append(f"Structure name mismatch: FrameL='{framel_def.name}' vs frameCPP='{framecpp_def.name}'")

    if framel_def.id != framecpp_def.id:
        differences.append(f"Structure ID mismatch: FrameL={framel_def.id} vs frameCPP={framecpp_def.id}")

    if not ignore_comments and framel_def.comment != framecpp_def.comment:
        differences.append(f"Structure comment mismatch:")
        differences.append(f"  FrameL:   \"{framel_def.comment}\"")
        differences.append(f"  frameCPP: \"{framecpp_def.comment}\"")

    # Compare field counts
    if len(framel_def.fields) != len(framecpp_def.fields):
        differences.append(f"Field count mismatch: FrameL={len(framel_def.fields)} vs frameCPP={len(framecpp_def.fields)}")

    # Compare fields
    max_fields = max(len(framel_def.fields), len(framecpp_def.fields))
    for i in range(max_fields):
        framel_field = framel_def.fields[i] if i < len(framel_def.fields) else None
        framecpp_field = framecpp_def.fields[i] if i < len(framecpp_def.fields) else None

        if framel_field is None:
            differences.append(f"Field {i}: frameCPP has extra field '{framecpp_field.name}' ({framecpp_field.type})")
        elif framecpp_field is None:
            differences.append(f"Field {i}: FrameL has extra field '{framel_field.name}' ({framel_field.type})")
        else:
            if framel_field.name != framecpp_field.name:
                differences.append(f"Field {i} name mismatch: FrameL='{framel_field.name}' vs frameCPP='{framecpp_field.name}'")

            if framel_field.type != framecpp_field.type:
                differences.append(f"Field {i} ({framel_field.name}) type mismatch: FrameL='{framel_field.type}' vs frameCPP='{framecpp_field.type}'")

            if not ignore_comments and framel_field.comment != framecpp_field.comment:
                differences.append(f"Field {i} ({framel_field.name}) comment mismatch:")
                differences.append(f"  FrameL:   \"{framel_field.comment}\"")
                differences.append(f"  frameCPP: \"{framecpp_field.comment}\"")

    return (len(differences) == 0, differences)

def run_tool(tool: str, version: int, object_type: str) -> str:
    """Run a dump_definition tool and return its output"""
    try:
        result = subprocess.run(
            [tool, '--version', str(version), '--object', object_type],
            capture_output=True,
            text=True,
            check=True
        )
        return result.stdout
    except subprocess.CalledProcessError as e:
        print(f"Error running {tool}:", file=sys.stderr)
        print(f"  Return code: {e.returncode}", file=sys.stderr)
        print(f"  stderr: {e.stderr}", file=sys.stderr)
        sys.exit(1)
    except FileNotFoundError:
        print(f"Error: {tool} not found in PATH", file=sys.stderr)
        sys.exit(1)

def main():
    parser = argparse.ArgumentParser(
        description='Compare structure definitions between FrameL and frameCPP'
    )
    parser.add_argument('--version', '-v', type=int, required=True,
                       help='Frame format version (3, 4, 6, 7, 8, 9)')
    parser.add_argument('--object', '-o', type=str, required=True,
                       help='Object type name (e.g., FrDetector, FrVect)')
    parser.add_argument('--ignore-comments', action='store_true',
                       help='Ignore differences in FrSH and FrSE comment fields')
    parser.add_argument('--framel-tool', default='framel_dump_definition',
                       help='Path to framel_dump_definition tool')
    parser.add_argument('--framecpp-tool', default='framecpp_dump_definition',
                       help='Path to framecpp_dump_definition tool')
    parser.add_argument('--verbose', '-V', action='store_true',
                       help='Show both definitions even if identical')

    args = parser.parse_args()

    # Validate version
    if args.version not in [3, 4, 6, 7, 8, 9]:
        print(f"Error: Invalid version {args.version} (must be 3, 4, 6, 7, 8, or 9)",
              file=sys.stderr)
        sys.exit(1)

    print(f"Comparing {args.object} definitions for version {args.version}")
    if args.ignore_comments:
        print("  (Ignoring comment differences)")
    print()

    # Run both tools
    print(f"Running {args.framel_tool}...")
    framel_output = run_tool(args.framel_tool, args.version, args.object)

    print(f"Running {args.framecpp_tool}...")
    framecpp_output = run_tool(args.framecpp_tool, args.version, args.object)
    print()

    # Parse outputs
    try:
        framel_def = parse_definition(framel_output)
        framecpp_def = parse_definition(framecpp_output)
    except ValueError as e:
        print(f"Error parsing output: {e}", file=sys.stderr)
        sys.exit(1)

    if framel_def is None or framecpp_def is None:
        print("Error: Could not parse definitions", file=sys.stderr)
        sys.exit(1)

    # Show definitions if verbose
    if args.verbose:
        print("=== FrameL Definition ===")
        print(framel_output)
        print()
        print("=== frameCPP Definition ===")
        print(framecpp_output)
        print()

    # Compare
    is_identical, differences = compare_definitions(
        framel_def, framecpp_def, args.ignore_comments
    )

    if is_identical:
        print("✓ Definitions are IDENTICAL")
        return 0
    else:
        print(f"✗ Found {len(differences)} difference(s):")
        print()
        for diff in differences:
            print(f"  • {diff}")
        return 1

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