import pygame
import sys
import os
import re
import random

# Инициализация Pygame
pygame.init()

# Настройки окна
WIDTH, HEIGHT = 1920, 1080
screen = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption("Визуальная новелла")

# Цвета
WHITE = (255, 255, 255)
BLACK = (0, 0, 0)
GRAY = (200, 200, 200)
LIGHT_BLUE = (173, 216, 230)
DARK_BLUE = (0, 0, 139)
CHOICE_BG = (240, 240, 255)

NIGHT_BLUE = (5, 5, 30)
MORNING_YELLOW = (255, 220, 100)
MORNING_GREEN = (100, 180, 80)
MORNING_DARK_BLUE = (20, 40, 100)
DAY_GREEN = (100, 200, 80)
DAY_BLUE = (100, 200, 255)
EVENING_ORANGE = (255, 120, 50)
EVENING_DARK_GREEN = (30, 80, 40)

# Шрифты
try:
    font = pygame.font.Font(None, 36)
    character_font = pygame.font.Font(None, 32)
    choice_font = pygame.font.Font(None, 30)
    small_font = pygame.font.Font(None, 24)
    italic_font = pygame.font.Font(None, 32)
    italic_font.set_italic(True)
except:
    font = pygame.font.Font(None, 36)
    character_font = pygame.font.Font(None, 32)
    choice_font = pygame.font.Font(None, 30)
    small_font = pygame.font.Font(None, 24)
    italic_font = pygame.font.Font(None, 32)
    italic_font.set_italic(True)
    
class NovelEngine:
    def __init__(self):
        self.script = []
        self.current_line = 0
        self.running = True
        self.background = None
        self.characters = {}
        self.variables = {}
        self.choices = []
        self.waiting_for_choice = False
        self.call_stack = []  # Стек для хранения позиций возврата
        self.text_box_rect = pygame.Rect(50, HEIGHT - 250, WIDTH - 100, 200)
        self.choice_box_rect = pygame.Rect(WIDTH//2 - 300, HEIGHT//2 - 150, 600, 300)
        self.special_background = None
        self.background_textures = {}  # Текстуры для сложных фонов
        self.init_background_textures()  # Инициализируем текстуры

    def load_script(self, filename):
        """Загрузка скрипта из файла"""
        self.script = []
        script_path = os.path.join(os.path.dirname(__file__), filename)
        
        if not os.path.exists(script_path):
            print(f"Ошибка: файл {script_path} не найден!")
            return False
            
        with open(script_path, 'r', encoding='utf-8') as file:
            for line in file:
                line = line.strip()
                if line and not line.startswith('#'):
                    self.script.append(line)
        return True
    
    def parse_character(self, command):
        """Обработка описания персонажа с дополнительными атрибутами"""
        if command.startswith("char "):
            parts = command[5:].split("|")
            char_id = parts[0].strip().split(":")[0]
            char_data = {
                "name": parts[0].split(":")[1].strip(),
                "appearance": parts[1].strip() if len(parts) > 1 else "",
                "details": parts[2].strip() if len(parts) > 2 else "",
                "scent": parts[3].strip() if len(parts) > 3 else ""
            }
            self.characters[char_id] = char_data
            return True
        return False
    
    def init_background_textures(self):
        """Создаем текстуры для сложных фонов один раз при инициализации"""
        # Ночное небо
        night_surface = pygame.Surface((WIDTH, HEIGHT))
        night_surface.fill(NIGHT_BLUE)
        for _ in range(100):
            x = random.randint(0, WIDTH)
            y = random.randint(0, HEIGHT // 2)
            size = random.randint(1, 3)
            brightness = random.randint(200, 255)
            pygame.draw.circle(night_surface, (brightness, brightness, brightness), (x, y), size)
        self.background_textures["night"] = night_surface

        morning_surface = pygame.Surface((WIDTH, HEIGHT))
        for y in range(HEIGHT):
            ratio = y / HEIGHT
            # Верхняя часть (темно-синяя)
            if ratio < 0.3:
                r = int(MORNING_DARK_BLUE[0])
                g = int(MORNING_DARK_BLUE[1])
                b = int(MORNING_DARK_BLUE[2])
            # Средняя часть (переход)
            elif ratio < 0.7:
                r = int(MORNING_DARK_BLUE[0] + (MORNING_YELLOW[0] - MORNING_DARK_BLUE[0]) * (ratio - 0.3) / 0.4)
                g = int(MORNING_DARK_BLUE[1] + (MORNING_YELLOW[1] - MORNING_DARK_BLUE[1]) * (ratio - 0.3) / 0.4)
                b = int(MORNING_DARK_BLUE[2] + (MORNING_YELLOW[2] - MORNING_DARK_BLUE[2]) * (ratio - 0.3) / 0.4)
            # Нижняя часть (зеленая)
            else:
                r = int(MORNING_GREEN[0])
                g = int(MORNING_GREEN[1])
                b = int(MORNING_GREEN[2])
            pygame.draw.line(morning_surface, (r, g, b), (0, y), (WIDTH, y))
        pygame.draw.circle(morning_surface, MORNING_YELLOW, (WIDTH // 2, HEIGHT // 3), 50)
        self.background_textures["morning"] = morning_surface

        day_surface = pygame.Surface((WIDTH, HEIGHT))
        # Градиент от голубого к зеленому
        for y in range(HEIGHT):
            ratio = y / HEIGHT
            if ratio < 0.8:
                r = int(DAY_BLUE[0])
                g = int(DAY_BLUE[1])
                b = int(DAY_BLUE[2])
            else:
                r = int(DAY_GREEN[0])
                g = int(DAY_GREEN[1])
                b = int(DAY_GREEN[2])
            pygame.draw.line(day_surface, (r, g, b), (0, y), (WIDTH, y))

        # Рисуем облака
        for _ in range(5):
            x = random.randint(0, WIDTH)
            y = random.randint(50, HEIGHT // 3)
            size = random.randint(30, 70)
            pygame.draw.circle(day_surface, WHITE, (x, y), size)
            pygame.draw.circle(day_surface, WHITE, (x + size//2, y - size//3), size//2)
            pygame.draw.circle(day_surface, WHITE, (x - size//2, y - size//4), size//2)
        self.background_textures["day"] = day_surface

        evening_surface = pygame.Surface((WIDTH, HEIGHT))
        for y in range(HEIGHT):
            ratio = y / HEIGHT
            if ratio < 0.5:
                r = int(EVENING_ORANGE[0])
                g = int(EVENING_ORANGE[1])
                b = int(EVENING_ORANGE[2])
            else:
                r = int(EVENING_ORANGE[0] + (EVENING_DARK_GREEN[0] - EVENING_ORANGE[0]) * (ratio - 0.5) / 0.5)
                g = int(EVENING_ORANGE[1] + (EVENING_DARK_GREEN[1] - EVENING_ORANGE[1]) * (ratio - 0.5) / 0.5)
                b = int(EVENING_ORANGE[2] + (EVENING_DARK_GREEN[2] - EVENING_ORANGE[2]) * (ratio - 0.5) / 0.5)
            pygame.draw.line(evening_surface, (r, g, b), (0, y), (WIDTH, y))

        # Рисуем заходящее солнце
        pygame.draw.circle(evening_surface, (255, 200, 100), (WIDTH // 2, HEIGHT // 2), 60)
        pygame.draw.rect(evening_surface, EVENING_DARK_GREEN, (0, HEIGHT // 2, WIDTH, HEIGHT // 2))
        self.background_textures["evening"] = evening_surface

    def draw_night_sky(self):
        """Рисует ночное небо со звездами"""
        screen.blit(self.background_textures["night"], (0, 0))

    def draw_morning_sky(self):
        """Рисует утреннее небо с градиентом"""
        screen.blit(self.background_textures["morning"], (0, 0))

    def draw_day_sky(self):
        """Рисует дневное небо с облаками"""
        screen.blit(self.background_textures["day"], (0, 0))

    def draw_evening_sky(self):
        """Рисует вечернее небо с закатом"""
        screen.blit(self.background_textures["evening"], (0, 0))
        
    def parse_command(self, command):
        """Обработка команд скрипта"""
        # Случайный переход в подпрограмму
        if command.startswith("random_gosub "):
            parts = command[12:].split()
            if len(parts) >= 2:
                # Создаем список вариантов с их вероятностями
                options = []
                probabilities = []
                labels = []
                
                # Разбираем части на вероятности и метки
                i = 0
                while i < len(parts):
                    try:
                        prob = float(parts[i])
                        label = parts[i+1]
                        probabilities.append(prob)
                        labels.append(label)
                        options.append((prob, label))
                        i += 2
                    except (ValueError, IndexError):
                        break
                
                if options:
                    # Нормализуем вероятности (на случай, если они не суммируются в 1)
                    total = sum(prob for prob, label in options)
                    if total > 0:
                        rand = random.random() * total
                        cumulative = 0
                        for prob, label in options:
                            cumulative += prob
                            if rand <= cumulative:
                                # Сохраняем текущую позицию для возврата
                                self.call_stack.append(self.current_line + 1)
                                # Переходим к выбранной подпрограмме
                                self.jump_to_label(label)
                                break
            return True
        
        # Возврат из подпрограммы
        elif command == "return":
            if self.call_stack:
                self.current_line = self.call_stack.pop()
            else:
                print("Ошибка: стек вызовов пуст!")
            return True
        
        # Установка переменной
        elif command.startswith("set "):
            parts = command[4:].split("|")
            for part in parts:
                part = part.strip()
                if "+=" in part:
                    var, val = part.split("+=", 1)
                    var = var.strip()
                    current = int(self.variables.get(var, 0))
                    self.variables[var] = str(current + int(val.strip()))
                elif "-=" in part:
                    var, val = part.split("-=", 1)
                    var = var.strip()
                    current = int(self.variables.get(var, 0))
                    self.variables[var] = str(current - int(val.strip()))
                elif "=" in part:
                    var, val = part.split("=", 1)
                    var = var.strip()
                    val = val.strip()
                    
                    # Обработка инкремента (++var)
                    if val.startswith("++"):
                        var_to_inc = val[2:]
                        self.variables[var] = str(int(self.variables.get(var_to_inc, 0)) + 1)
                    # Обработка декремента (--var)
                    elif val.startswith("--"):
                        var_to_dec = val[2:]
                        self.variables[var] = str(int(self.variables.get(var_to_dec, 0)) - 1)
                    # Обычное присваивание
                    else:
                        self.variables[var] = val
            print(f"После выполнения '{command}': {self.variables}")  # Отладочный вывод
            return True     
           
        # Условие
        elif command.startswith("if "):
            # ИСПРАВЛЕННЫЙ regex с обработкой пробелов
            match = re.match(r'if\s+(\w+)\s*([=!<>]+)\s*(.+?)\s+then\s+goto\s+(\w+)', command)
            if match:
                var_name, op, value, label = match.groups()
                current_value = self.variables.get(var_name, "0")
                
                print(f"Отладка: if {var_name}({current_value}) {op} {value} then goto {label}")  # Отладка
                
                # Пробуем численное сравнение
                try:
                    current_num = float(current_value)
                    value_num = float(value)
                    if op == ">": condition_met = current_num > value_num
                    elif op == "<": condition_met = current_num < value_num
                    elif op == ">=": condition_met = current_num >= value_num
                    elif op == "<=": condition_met = current_num <= value_num
                    elif op == "==": condition_met = current_num == value_num
                    elif op == "!=": condition_met = current_num != value_num
                    else: condition_met = False
                except ValueError:
                    # Строковое сравнение
                    if op == ">": condition_met = current_value > value
                    elif op == "<": condition_met = current_value < value
                    elif op == ">=": condition_met = current_value >= value
                    elif op == "<=": condition_met = current_value <= value
                    elif op == "==": condition_met = current_value == value
                    elif op == "!=": condition_met = current_value != value
                    else: condition_met = False
                
                if condition_met:
                    print(f"Условие выполнено, переход к {label}")  # Отладка
                    self.jump_to_label(label)
                else:
                    print("Условие не выполнено")  # Отладка
            return True        
        
        # Метка
        elif command.startswith("label "):
            return True
        
        # Подпрограмма
        elif command.startswith("sub_"):
            if not self.call_stack or self.call_stack[-1] != self.current_line:
                self.call_stack.append(self.current_line)
            return True
        
        # Возврат из подпрограммы
        elif command == "return":
            if not self.call_stack:
                print("Предупреждение: return без вызова подпрограммы, пропускаем")
                self.current_line += 1
            else:
                self.current_line = self.call_stack.pop() + 1  # Возвращаемся на следующую строку
            return True
        
        # Переход
        elif command.startswith("goto "):
            label = command[5:].strip()
            self.jump_to_label(label)
            return True
        
        # Фон
        elif command.startswith("bg "):
            bg_color = command[3:].strip().lower()
            if bg_color == "black":
                self.background = BLACK
                self.special_background = None
            elif bg_color == "white":
                self.background = WHITE
                self.special_background = None
            elif bg_color == "blue":
                self.background = LIGHT_BLUE
                self.special_background = None
            elif bg_color == "night":
                self.special_background = "night"
            elif bg_color == "morning":
                self.special_background = "morning"
            elif bg_color == "day":
                self.special_background = "day"
            elif bg_color == "evening" or bg_color == "sunset":
                self.special_background = "evening"
            else:
                self.background = GRAY
                self.special_background = None
            return True        
        
        # Персонаж
        elif command.startswith("char "):
            return self.parse_character(command)
        
        return False
    
    def draw_descriptions(self, text, x, y, font_obj, color):
        """Отрисовка текста с учётом звёздочек"""
        if text.startswith("*") and text.endswith("*"):
            # Удаляем звёздочки и рисуем курсивом
            self.draw_text(text[1:-1], x, y, italic_font, (150, 150, 150))
        else:
            self.draw_text(text, x, y, font_obj, color)
    
    def jump_to_label(self, label):
        """Переход к метке в скрипте"""
        for i, line in enumerate(self.script):
            if line.startswith(f"@label {label}") or line.startswith(f"@{label}"):
                self.current_line = i
                return
        print(f"Метка '{label}' не найдена!")
    
    def process_choices(self, line):
        """Обработка строки с выбором"""
        if line.startswith("choice "):
            choices_text = line[7:].split("|")
            self.choices = []
            
            for choice_text in choices_text:
                parts = choice_text.split("=>")
                if len(parts) == 2:
                    choice_display = parts[0].strip()
                    choice_action = parts[1].strip()
                    self.choices.append((choice_display, choice_action))
            
            self.waiting_for_choice = True
            return True
        return False
    
    def substitute_variables(self, text):
        """Подстановка переменных в текст"""
        for var_name, var_value in self.variables.items():
            text = text.replace(f"{{{var_name}}}", str(var_value))
        return text
    
    def get_character_info(self, char_id):
        """Возвращает полное описание персонажа"""
        # Убираем вывод "Персонаж none" для несуществующих ID
        if char_id not in self.characters:
            return "", ""  # Возвращаем пустые строки
        
        char = self.characters.get(char_id)
        if isinstance(char, str):
            return char, ""
        
        details = []
        if char.get("appearance"): details.append(char['appearance'])
        if char.get("details"): details.append(char['details'])
        if char.get("scent"): details.append(char['scent'])
        
        return char.get("name", ""), " | ".join(details)
    
    def process_line(self):
        if self.current_line >= len(self.script):
            self.running = False
            return None
            
        line = self.script[self.current_line]
        line = self.substitute_variables(line)
        
        # Обработка команд
        if line.startswith("@"):
            if self.parse_command(line[1:]):
                self.current_line += 1
                return self.process_line()
            return None
        
        # Обработка выбора
        if self.process_choices(line):
            self.current_line += 1
            return None
            
        # Обработка диалога с проверкой разделителя
        if ":" in line and not line.startswith("*"):
            parts = line.split(":", 1)
            if len(parts) == 2:
                char_id = parts[0].strip()
                text = parts[1].strip()
                return char_id, text
        
        # Обработка описания или простого текста
        return None, line
    
    def make_choice(self, choice_index):
        """Обработка выбора игрока"""
        if 0 <= choice_index < len(self.choices):
            choice_action = self.choices[choice_index][1]
            
            if choice_action.startswith("goto "):
                label = choice_action[5:].strip()
                self.jump_to_label(label)
            elif "+=" in choice_action:
                var, value = choice_action.split("+=", 1)
                var = var.strip()
                self.variables[var] = str(int(self.variables.get(var, 0)) + int(value.strip()))
                self.current_line += 1
            elif "-=" in choice_action:
                var, value = choice_action.split("-=", 1)
                var = var.strip()
                self.variables[var] = str(int(self.variables.get(var, 0)) - int(value.strip()))
                self.current_line += 1
            elif "=" in choice_action:
                var, value = choice_action.split("=", 1)
                self.variables[var.strip()] = value.strip()
                self.current_line += 1
            
            self.choices = []
            self.waiting_for_choice = False
    
    def next_line(self):
        """Переход к следующей строке"""
        self.current_line += 1
        if self.current_line >= len(self.script):
            self.running = False
    
    def draw_text_box(self):
        """Отрисовка текстового поля"""
        pygame.draw.rect(screen, WHITE, self.text_box_rect)
        pygame.draw.rect(screen, DARK_BLUE, self.text_box_rect, 3)
    
    def draw_choice_box(self):
        """Отрисовка поля выбора"""
        # Создаем полупрозрачную поверхность
        s = pygame.Surface((600, 300), pygame.SRCALPHA)
        s.fill((240, 240, 255, 230))  # Последний параметр - прозрачность
        screen.blit(s, (self.choice_box_rect.x, self.choice_box_rect.y))
        pygame.draw.rect(screen, DARK_BLUE, self.choice_box_rect, 3)

    def draw_text(self, text, x, y, font_obj, color=BLACK, max_width=None):
        """Отрисовка текста с переносом строк"""
        if max_width is None:
            max_width = self.text_box_rect.width - 40
        
        words = text.split(' ')
        space_width = font_obj.size(' ')[0]
        line_height = font_obj.get_height()
        
        current_line = []
        current_width = 0
        
        for word in words:
            word_surface = font_obj.render(word, True, color)
            word_width = word_surface.get_width()
            
            if current_width + word_width <= max_width:
                current_line.append(word)
                current_width += word_width + space_width
            else:
                line_text = ' '.join(current_line)
                line_surface = font_obj.render(line_text, True, color)
                screen.blit(line_surface, (x, y))
                y += line_height
                current_line = [word]
                current_width = word_width + space_width
        
        if current_line:
            line_text = ' '.join(current_line)
            line_surface = font_obj.render(line_text, True, color)
            screen.blit(line_surface, (x, y))
    
    def draw(self):
        """Отрисовка текущего состояния"""
        # Фон
        if self.special_background and self.special_background in self.background_textures:
            screen.blit(self.background_textures[self.special_background], (0, 0))
        elif self.background:
            screen.fill(self.background)
        else:
            screen.fill(LIGHT_BLUE)
        
        # Текстовое поле (убираем очистку перед выбором)
        if not self.waiting_for_choice:
            self.draw_text_box()        

        # Текст
        line_data = self.process_line()
        x = self.text_box_rect.x + 20
        y = self.text_box_rect.y + 20
        
        if line_data and not self.waiting_for_choice:
            if isinstance(line_data, tuple):  # Диалог с персонажем
                char_id, text = line_data
                name, details = self.get_character_info(char_id)
                
                # Отрисовываем имя только если оно есть
                if name:
                    name_surface = character_font.render(name + ":", True, DARK_BLUE)
                    screen.blit(name_surface, (x, y))
                    y += 40
                
                # Отрисовка текста персонажа
                self.draw_text(text, x, y, font, BLACK)
                y += 60
                
                # Отрисовка деталей персонажа (если есть)
                if details:
                    details_surface = small_font.render(details, True, (100, 100, 100))
                    screen.blit(details_surface, (x, y))
            else:  # Описание или простой текст
                _, text = line_data
                self.draw_descriptions(text, x, y, font, BLACK)
                
        # Варианты выбора
        if self.waiting_for_choice and self.choices:
            self.draw_choice_box()
            choice_y = self.choice_box_rect.y + 30
            for i, (choice_text, _) in enumerate(self.choices):
                choice_surface = choice_font.render(f"{i+1}. {choice_text}", True, BLACK)
                screen.blit(choice_surface, (self.choice_box_rect.x + 30, choice_y))
                choice_y += 40
        
        # Индикатор продолжения
        if self.running and not self.waiting_for_choice and line_data:
            indicator = font.render("> ПРОБЕЛ", True, DARK_BLUE)
            screen.blit(indicator, (WIDTH - 200, HEIGHT - 50))
        
        pygame.display.flip()
    
    def run(self):
        """Основной цикл"""
        clock = pygame.time.Clock()
        
        while self.running:
            for event in pygame.event.get():
                if event.type == pygame.QUIT:
                    pygame.quit()
                    sys.exit()
                elif event.type == pygame.KEYDOWN:
                    if event.key == pygame.K_SPACE and not self.waiting_for_choice:
                        self.next_line()
                    elif event.key == pygame.K_1 and self.waiting_for_choice and len(self.choices) >= 1:
                        self.make_choice(0)
                    elif event.key == pygame.K_2 and self.waiting_for_choice and len(self.choices) >= 2:
                        self.make_choice(1)
                    elif event.key == pygame.K_3 and self.waiting_for_choice and len(self.choices) >= 3:
                        self.make_choice(2)
                    elif event.key == pygame.K_4 and self.waiting_for_choice and len(self.choices) >= 4:
                        self.make_choice(3)
                    elif event.key == pygame.K_ESCAPE:
                        self.running = False
            
            self.draw()
            clock.tick(60)

# Создаем экземпляр движка и запускаем
if __name__ == "__main__":
    engine = NovelEngine()
    
    if engine.load_script("island_script.txt"):
        engine.run()
    
    pygame.quit()
    sys.exit()