#!/usr/bin/env python3
"""
Reorder member functions in a .cpp file to match the order in corresponding .h file.

Known limitations:
- May break files with functions inside #ifdef blocks
- May break files with constructor/destructor ordering dependencies
- May break files with anonymous namespace variable dependencies

For such files, manual reordering is recommended.

Usage:
    reorder-functions-regex.py <file_stem>
"""

import re
import sys
from typing import List, Tuple, Dict

def extract_function_order_from_header(text: str) -> List[Tuple[str, str]]:
    """Extract ordered list of (class_name, function_name) from header."""
    functions = []

    # Remove comments first
    text = re.sub(r'//.*', '', text)
    text = re.sub(r'/\*.*?\*/', '', text, flags=re.DOTALL)

    # Find class definitions
    class_pattern = r'class\s+(\w+)(?:\s*:\s*[^{]+)?\s*\{(.*?)\};'

    for class_match in re.finditer(class_pattern, text, re.DOTALL):
        class_name = class_match.group(1)
        class_body = class_match.group(2)

        # Extract text between balanced braces for nested structures
        class_body_clean = extract_class_body(text, class_match.start(2))

        # Find non-inline member function declarations
        # Handle template return types like QVector<T>
        # Capture assignment part to detect pure virtual functions
        func_pattern = r'(?:virtual\s+|static\s+|inline\s+|explicit\s+|constexpr\s+)*[\w:]+(?:<[^>]+>)?[\s*&]+(\w+)\s*\([^)]*\)(?:\s+const)?(?:\s+noexcept)?(?:\s+override)?(?:\s*(=\s*(?:0|default|delete)))?\s*;'

        for func_match in re.finditer(func_pattern, class_body_clean):
            func_name = func_match.group(1)
            assignment = func_match.group(2)  # Captures "= 0", "= default", or "= delete"

            if func_name not in (class_name, '~' + class_name, 'operator'):
                # Skip pure virtual functions (= 0)
                if assignment and '0' in assignment:
                    continue
                functions.append((class_name, func_name))

    return functions

def extract_class_body(text: str, start: int) -> str:
    """Extract class body handling nested braces."""
    depth = 0
    in_str = False
    escape = False
    result = []

    i = start
    while i < len(text):
        c = text[i]

        if escape:
            escape = False
        elif c == '\\':
            escape = True
        elif c == '"' and not in_str:
            in_str = True
        elif c == '"' and in_str:
            in_str = False
        elif not in_str:
            if c == '{':
                depth += 1
            elif c == '}':
                if depth == 0:
                    break
                depth -= 1

        result.append(c)
        i += 1

    return ''.join(result)

def extract_function_definitions(text: str, debug=False) -> Dict[Tuple[str, str, int], Tuple[int, int, str]]:
    """
    Extract function definitions from cpp file.
    Returns dict mapping (class_name, func_name, occurrence) to (start_pos, end_pos, full_text)
    """
    functions = {}
    occurrence_count = {}

    # Find all member function definitions
    # Pattern: return_type Class::function_name(...) { ... }
    # Handle template return types like QVector<T>
    # Use DOTALL to handle multi-line function signatures
    pattern = r'(?:^|\n)((?:[\w:]+(?:<[^>]+>)?[\s*&]+)?(\w+)::(\w+)\s*\([^)]*\)(?:\s+const)?(?:\s*noexcept)?(?:\s*->[\w\s:*&<>]+)?\s*\{)'

    for match in re.finditer(pattern, text, re.MULTILINE | re.DOTALL):
        if debug:
            print(f"Found match: {match.group(0)[:100]}")
        class_name = match.group(2)
        func_name = match.group(3)

        if debug:
            print(f"  Class: {class_name}, Function: {func_name}")

        # Skip constructors, destructors, operators
        if func_name in (class_name, '~' + class_name) or func_name == 'operator':
            if debug:
                print(f"  Skipping (constructor/destructor/operator)")
            continue

        # Track occurrences for overloads
        key = (class_name, func_name)
        occ = occurrence_count.get(key, 0)
        occurrence_count[key] = occ + 1

        # Find function boundaries
        func_start_pos = match.start() + (1 if match.group(0)[0] == '\n' else 0)
        brace_start = match.end() - 1
        brace_end = find_matching_brace(text, brace_start)

        if brace_end == -1:
            if debug:
                print(f"  Skipping (failed to find matching brace)")
            continue

        if debug:
            print(f"  Adding to functions dict")

        # Include trailing newlines
        func_end_pos = brace_end + 1
        while func_end_pos < len(text) and text[func_end_pos] in '\n':
            func_end_pos += 1

        full_text = text[func_start_pos:func_end_pos]
        functions[(class_name, func_name, occ)] = (func_start_pos, func_end_pos, full_text)

    return functions

def find_matching_brace(text: str, start: int) -> int:
    """Find position of closing brace matching brace at start."""
    depth = 1
    i = start + 1
    in_string = False
    in_char = False
    in_line_comment = False
    in_block_comment = False
    escape = False

    while i < len(text) and depth > 0:
        c = text[i]
        next_c = text[i+1] if i+1 < len(text) else ''

        # Handle escape sequences in strings/chars
        if escape:
            escape = False
            i += 1
            continue

        if in_string or in_char:
            if c == '\\':
                escape = True
            elif c == '"' and in_string:
                in_string = False
            elif c == "'" and in_char:
                in_char = False
        elif in_line_comment:
            if c == '\n':
                in_line_comment = False
        elif in_block_comment:
            if c == '*' and next_c == '/':
                in_block_comment = False
                i += 1  # Skip the '/'
        else:
            # Not in string, char, or comment
            if c == '/' and next_c == '/':
                in_line_comment = True
                i += 1  # Skip the second '/'
            elif c == '/' and next_c == '*':
                in_block_comment = True
                i += 1  # Skip the '*'
            elif c == '"':
                in_string = True
            elif c == "'":
                in_char = True
            elif c == '{':
                depth += 1
            elif c == '}':
                depth -= 1

        i += 1

    return i - 1 if depth == 0 else -1

def check_for_problematic_patterns(cpp_text: str, cpp_file: str) -> List[str]:
    """Check for patterns that might cause reordering to fail."""
    warnings = []

    # Check for preprocessor directives around functions
    if re.search(r'#if(?:def|ndef)?\s+.*\n.*::', cpp_text):
        warnings.append("File contains preprocessor directives near function definitions")

    # Check for functions inside #ifdef blocks
    ifdef_pattern = r'#if(?:def|ndef)?\s+\w+.*?\n(.*?)#endif'
    for match in re.finditer(ifdef_pattern, cpp_text, re.DOTALL):
        block = match.group(1)
        if '::' in block:  # Likely contains function definitions
            warnings.append("File contains function definitions inside #ifdef blocks")
            break

    return warnings

def reorder_cpp_file(header_file: str, cpp_file: str) -> bool:
    """Reorder functions in cpp_file to match order in header_file."""
    with open(header_file, 'r') as f:
        header_text = f.read()
    with open(cpp_file, 'r') as f:
        cpp_text = f.read()

    # Check for problematic patterns
    warnings = check_for_problematic_patterns(cpp_text, cpp_file)
    if warnings:
        print(f"Warning: {cpp_file} may not be suitable for automatic reordering:")
        for warning in warnings:
            print(f"  - {warning}")
        print("Consider manual reordering for this file.")

    # Get function order from header
    header_order = extract_function_order_from_header(header_text)
    if not header_order:
        print(f"No member functions found in {header_file}")
        return False

    # Extract function definitions from cpp
    cpp_functions = extract_function_definitions(cpp_text, debug=False)
    if not cpp_functions:
        print(f"No member function implementations found in {cpp_file}")
        return False

    # Debug: print found functions
    if False:  # Enable for debugging
        print(f"Found {len(cpp_functions)} implementations in cpp:")
        for (cls, func, occ) in sorted(cpp_functions.keys()):
            print(f"  {cls}::{func} (occurrence {occ})")
        print(f"\nHeader declares {len(header_order)} functions:")

    # Match header order to cpp implementations
    matches = []
    occ_used = {}
    missing_implementations = []

    for class_name, func_name in header_order:
        key = (class_name, func_name)
        occ = occ_used.get(key, 0)
        occ_used[key] = occ + 1

        cpp_key = (class_name, func_name, occ)
        if cpp_key in cpp_functions:
            matches.append(cpp_key)
        else:
            # Function declared in header but not implemented in cpp
            missing_implementations.append(f"{class_name}::{func_name}")

    # Report missing implementations as errors
    if missing_implementations:
        print(f"ERROR: Functions declared in {header_file} but not implemented in {cpp_file}:")
        for func in missing_implementations:
            print(f"  - {func}")
        raise RuntimeError(f"Missing {len(missing_implementations)} function implementation(s)")

    if not matches:
        print(f"No matching functions between {header_file} and {cpp_file}")
        return False

    # Check if already ordered
    cpp_order = sorted(matches, key=lambda k: cpp_functions[k][0])
    if matches == cpp_order:
        print(f"Functions already in correct order in {cpp_file}")
        return False

    # Build reordered file
    first_pos = min(cpp_functions[k][0] for k in matches)
    last_pos = max(cpp_functions[k][1] for k in matches)

    result = cpp_text[:first_pos]
    for key in matches:
        _, _, text = cpp_functions[key]
        result += text
    result += cpp_text[last_pos:]

    with open(cpp_file, 'w') as f:
        f.write(result)

    print(f"Reordered {len(matches)} functions in {cpp_file}")
    return True

if __name__ == '__main__':
    if len(sys.argv) != 2:
        print("Usage: reorder-functions.py <file_stem>")
        sys.exit(1)

    stem = sys.argv[1]
    try:
        changed = reorder_cpp_file(stem + '.h', stem + '.cpp')
        sys.exit(0 if changed else 1)
    except Exception as e:
        print(f"Error: {e}", file=sys.stderr)
        import traceback
        traceback.print_exc()
        sys.exit(2)
