#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Storm Engine Advanced Linter
Проверяет логические ошибки и квестовую логику:
- Недостижимый код после return
- Бесконечные циклы
- Бессмысленные условия (if(true), if(false))
- Неиспользуемые функции
- Дублирующиеся ID персонажей
- Win conditions без .over
- Квестовые флаги без проверок
- Вложенные циклы (O(n^2))

Использование:
  python lint-advanced.py <путь_к_PROGRAM>                  # проверить все .c файлы
  python lint-advanced.py <путь_к_PROGRAM> file1.c file2.c  # проверить конкретные файлы

Примеры:
  python lint-advanced.py "C:/Games/SeaDogs/PROGRAM"
  python lint-advanced.py ./PROGRAM quests/ChardLine_reaction.c
"""

import sys
import os
import re
from pathlib import Path
from collections import defaultdict

class Colors:
    RED = '\033[91m'
    YELLOW = '\033[93m'
    GREEN = '\033[92m'
    CYAN = '\033[96m'
    GRAY = '\033[90m'
    RESET = '\033[0m'

    @classmethod
    def disable(cls):
        cls.RED = cls.YELLOW = cls.GREEN = cls.CYAN = cls.GRAY = cls.RESET = ''

error_count = 0
warning_count = 0

def error(file_path, line_num, message):
    global error_count
    print(f"{Colors.RED}ERROR:{Colors.RESET} {Colors.YELLOW}{file_path}:{line_num}{Colors.RESET} {message}")
    error_count += 1

def warning(file_path, line_num, message):
    global warning_count
    print(f"{Colors.YELLOW}WARNING:{Colors.RESET} {Colors.GRAY}{file_path}:{line_num}{Colors.RESET} {message}")
    warning_count += 1

def info(file_path, line_num, message):
    print(f"{Colors.CYAN}INFO:{Colors.RESET} {Colors.GRAY}{file_path}:{line_num}{Colors.RESET} {message}")

def lint_advanced(file_path):
    """Продвинутая проверка файла"""
    if not os.path.exists(file_path):
        print(f"{Colors.RED}ОШИБКА: Файл не найден: {file_path}{Colors.RESET}")
        return

    print(f"\n{Colors.CYAN}Продвинутая проверка: {file_path}{Colors.RESET}")

    try:
        with open(file_path, 'r', encoding='utf-8', errors='ignore') as f:
            lines = f.readlines()
    except Exception as e:
        print(f"{Colors.RED}ОШИБКА: Не удалось прочитать файл: {e}{Colors.RESET}")
        return

    # Статистика
    functions_declared = {}  # {name: line_num}
    functions_called = set()
    variables_declared = {}  # {name: line_num}
    variables_used = set()
    characters_created = {}  # {id: line_num}
    characters_deleted = set()
    win_conditions = {}  # {name: line_num}
    win_callbacks = set()
    quest_flags_set = {}  # {flag: line_num}
    quest_flags_checked = set()

    prev_line = ""
    in_comment_block = False

    for line_num, line in enumerate(lines, 1):
        stripped = line.strip()

        if not stripped:
            prev_line = line
            continue

        # Обработка многострочных комментариев
        if '/*' in stripped:
            in_comment_block = True
        if '*/' in stripped:
            in_comment_block = False
            prev_line = line
            continue
        if in_comment_block:
            prev_line = line
            continue

        if stripped.startswith('//'):
            prev_line = line
            continue

        # ============================================
        # 1. ЛОГИЧЕСКИЕ ОШИБКИ
        # ============================================

        # Недостижимый код после return
        if 'return' in prev_line and not stripped.startswith('}'):
            if not stripped.startswith('//') and stripped != '}':
                warning(file_path, line_num, "Недостижимый код после return")

        # Бесконечный цикл
        if re.match(r'^\s*while\s*\(\s*true\s*\)', stripped):
            if 'break' not in ''.join(lines[line_num:min(line_num+20, len(lines))]):
                warning(file_path, line_num, "Бесконечный цикл без break")

        # Бессмысленное условие
        if re.match(r'^\s*if\s*\(\s*true\s*\)', stripped):
            warning(file_path, line_num, "Бессмысленное условие if(true)")

        if re.match(r'^\s*if\s*\(\s*false\s*\)', stripped):
            warning(file_path, line_num, "Недостижимый код if(false)")

        # ============================================
        # 2. ФУНКЦИИ
        # ============================================

        func_decl = re.match(r'^\s*(void|int|float|string|bool|ref|aref)\s+([a-zA-Z_][a-zA-Z0-9_]*)\s*\(', stripped)
        if func_decl:
            func_name = func_decl.group(2)
            if func_name not in ['main', 'Run', 'Execute']:
                functions_declared[func_name] = line_num

        func_calls = re.findall(r'([a-zA-Z_][a-zA-Z0-9_]*)\s*\(', line)
        for func_call in func_calls:
            if func_call not in ['if', 'while', 'for', 'switch', 'void', 'int', 'float', 'string',
                                  'ref', 'aref', 'Rand', 'Trace', 'Log_Info', 'CheckAttribute',
                                  'GetCharacterIndex', 'CharacterFromID', 'sti', 'stf']:
                functions_called.add(func_call)

        # ============================================
        # 3. ПЕРЕМЕННЫЕ
        # ============================================

        var_decl = re.findall(r'\b(int|float|string|bool|ref|aref)\s+([a-zA-Z_][a-zA-Z0-9_]*)', line)
        for var_type, var_name in var_decl:
            if var_name not in ['n', 'i', 'j', 'k']:
                variables_declared[var_name] = line_num

        var_uses = re.findall(r'\b([a-z][a-zA-Z0-9_]*)\b', line)
        for var in var_uses:
            if len(var) > 2:
                variables_used.add(var)

        # ============================================
        # 4. ПЕРСОНАЖИ
        # ============================================

        char_creates = re.findall(r'NPC_GenerateCharacter\s*\(\s*"([^"]+)"', line)
        for char_id in char_creates:
            if char_id in characters_created:
                error(file_path, line_num, f"Дублирующийся ID персонажа: '{char_id}' (уже создан на строке {characters_created[char_id]})")
            characters_created[char_id] = line_num

        if 'NPC_GenerateCharacter' in line:
            next_lines = ''.join(lines[line_num:min(line_num+5, len(lines))])
            if '.name' not in next_lines and '.lastname' not in next_lines:
                warning(file_path, line_num, "Персонаж создан без имени")

        # ============================================
        # 5. КВЕСТЫ
        # ============================================

        win_cond = re.findall(r'pchar\.quest\.([a-zA-Z0-9_]+)\.win_condition', line)
        for quest_name in win_cond:
            win_conditions[quest_name] = line_num

        win_cb = re.findall(r'pchar\.quest\.[a-zA-Z0-9_]+\.function\s*=\s*"([^"]+)"', line)
        for cb in win_cb:
            win_callbacks.add(cb)

        flags_set = re.findall(r'pchar\.questTemp\.([a-zA-Z0-9_]+)\s*=', line)
        for flag in flags_set:
            quest_flags_set[flag] = line_num

        flags_check = re.findall(r'pchar\.questTemp\.([a-zA-Z0-9_]+)\s*==', line)
        for flag in flags_check:
            quest_flags_checked.add(flag)

        if re.search(r'pchar\.quest\.[a-zA-Z0-9_]+\.win_condition', line):
            quest_name = re.findall(r'pchar\.quest\.([a-zA-Z0-9_]+)\.win_condition', line)[0]
            file_content = ''.join(lines)
            if f'pchar.quest.{quest_name}.over' not in file_content:
                warning(file_path, line_num, f"Win condition '{quest_name}' не удаляется (.over = \"yes\")")

        # ============================================
        # 6. ПРОИЗВОДИТЕЛЬНОСТЬ
        # ============================================

        if re.match(r'^\s*for\s*\(', stripped):
            next_lines = lines[line_num:min(line_num+10, len(lines))]
            nested_for = False
            for next_line in next_lines:
                if re.match(r'^\s*for\s*\(', next_line.strip()):
                    nested_for = True
                    break
            if nested_for:
                warning(file_path, line_num, "Вложенные циклы могут тормозить (O(n^2))")

        loop_match = re.match(r'^\s*for\s*\([^;]+;\s*[^<>]+(<|>)\s*(\d+)', stripped)
        if loop_match:
            limit = int(loop_match.group(2))
            if limit > 1000:
                warning(file_path, line_num, f"Большой цикл ({limit} итераций) может тормозить")

        prev_line = line

    # ============================================
    # ИТОГОВЫЙ АНАЛИЗ
    # ============================================

    print(f"\n{Colors.CYAN}=== Анализ кода ==={Colors.RESET}")

    # Неиспользуемые функции
    unused_funcs = set(functions_declared.keys()) - functions_called
    if unused_funcs:
        print(f"\n{Colors.YELLOW}Неиспользуемые функции ({len(unused_funcs)}):{Colors.RESET}")
        for func in sorted(unused_funcs):
            info(file_path, functions_declared[func], f"Функция '{func}' объявлена но не используется")

    # Неопределённые функции
    undefined_funcs = functions_called - set(functions_declared.keys())
    undefined_funcs = {f for f in undefined_funcs if not f.startswith('LAi_') and not f.startswith('Get') and not f.startswith('Set')}
    if undefined_funcs and len(undefined_funcs) < 20:
        print(f"\n{Colors.YELLOW}Неопределённые функции ({len(undefined_funcs)}):{Colors.RESET}")
        for func in sorted(undefined_funcs):
            info(file_path, 0, f"Функция '{func}' вызывается но не объявлена (возможно в другом файле)")

    # Дублирующиеся персонажи
    non_deleted_chars = set(characters_created.keys()) - characters_deleted
    if len(non_deleted_chars) > 0 and len(non_deleted_chars) < 10:
        print(f"\n{Colors.YELLOW}Персонажи не удаляются ({len(non_deleted_chars)}):{Colors.RESET}")
        for char_id in sorted(non_deleted_chars):
            info(file_path, characters_created[char_id], f"Персонаж '{char_id}' создан но не удалён")

    # Win callbacks не определены
    undefined_callbacks = win_callbacks - set(functions_declared.keys())
    if undefined_callbacks:
        print(f"\n{Colors.RED}Win callbacks не определены:{Colors.RESET}")
        for cb in sorted(undefined_callbacks):
            error(file_path, 0, f"Callback функция '{cb}' используется в win_condition но не объявлена")

    # Quest flags выставлены но не проверяются
    unused_flags = set(quest_flags_set.keys()) - quest_flags_checked
    if unused_flags and len(unused_flags) < 10:
        print(f"\n{Colors.YELLOW}Quest flags выставлены но не проверяются ({len(unused_flags)}):{Colors.RESET}")
        for flag in sorted(unused_flags):
            info(file_path, quest_flags_set[flag], f"Флаг 'pchar.questTemp.{flag}' выставлен но не проверяется")

    # Статистика
    print(f"\n{Colors.CYAN}=== Статистика ==={Colors.RESET}")
    print(f"  Функций объявлено: {len(functions_declared)}")
    print(f"  Функций вызвано: {len(functions_called)}")
    print(f"  Переменных объявлено: {len(variables_declared)}")
    print(f"  Персонажей создано: {len(characters_created)}")
    print(f"  Win conditions: {len(win_conditions)}")
    print(f"  Quest flags: {len(quest_flags_set)}")

def main():
    if len(sys.argv) < 2 or '--help' in sys.argv or '-h' in sys.argv:
        print(__doc__)
        return 0

    # Разбор аргументов
    program_dir = None
    files_to_check = []
    no_color = False
    for arg in sys.argv[1:]:
        if arg == '--no-color':
            no_color = True
        elif program_dir is None:
            program_dir = arg
        else:
            files_to_check.append(arg)

    if no_color or not sys.stdout.isatty():
        Colors.disable()

    program_dir = os.path.abspath(program_dir)
    if not os.path.isdir(program_dir):
        print(f"ОШИБКА: Директория PROGRAM не найдена: {program_dir}")
        return 1

    print("")
    print(f"{Colors.CYAN}============================================{Colors.RESET}")
    print(f"{Colors.CYAN}  STORM ENGINE ADVANCED LINTER{Colors.RESET}")
    print(f"{Colors.CYAN}============================================{Colors.RESET}")

    if files_to_check:
        for file_path in files_to_check:
            full_path = file_path
            if not os.path.isabs(file_path):
                full_path = os.path.join(program_dir, file_path)
            lint_advanced(full_path)
    else:
        # Проверить все .c файлы
        print(f"\n{Colors.YELLOW}Проверка всех .c файлов в: {program_dir}{Colors.RESET}")
        c_files = sorted(Path(program_dir).rglob("*.c"))
        for fpath in c_files:
            lint_advanced(str(fpath))

    # РЕЗУЛЬТАТЫ
    print("")
    print(f"{Colors.CYAN}============================================{Colors.RESET}")
    print(f"{Colors.CYAN}  РЕЗУЛЬТАТЫ{Colors.RESET}")
    print(f"{Colors.CYAN}============================================{Colors.RESET}")

    if error_count == 0 and warning_count == 0:
        print(f"{Colors.GREEN}ПРОБЛЕМ НЕ НАЙДЕНО!{Colors.RESET}")
        return 0

    print(f"{Colors.RED}Ошибок: {error_count}{Colors.RESET}")
    print(f"{Colors.YELLOW}Предупреждений: {warning_count}{Colors.RESET}")

    if error_count > 0:
        print("")
        print(f"{Colors.RED}НАЙДЕНЫ КРИТИЧЕСКИЕ ОШИБКИ! Исправьте их перед запуском игры.{Colors.RESET}")
        return 1

    print("")
    print(f"{Colors.YELLOW}Найдены предупреждения. Рекомендуется проверить.{Colors.RESET}")
    return 0

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