import os
import sys
os.environ['PYGAME_HIDE_SUPPORT_PROMPT'] = "hide"
import pygame
import random
from typing import List, Tuple
import tetris_config as cfg

# Initialize Pygame
pygame.init()

# ============================================================================
# CONFIGURATION SECTION
# ============================================================================

cfg.load_config()
BLOCK_TYPE = cfg.BLOCK_TYPE
KEY_CONFIG = cfg.KEY_CONFIG
CONTROLS_TEXT = cfg.CONTROLS_TEXT
COLORS = cfg.COLORS
STORE_WIDTH = cfg.STORE_WIDTH
BLOCK_SIZE = cfg.BLOCK_SIZE

# Be carefully with this method call due to it may rewrite current configuration
# cfg.store_config()

# ============================================================================
# TETRIS GAME CLASS
# ============================================================================

class TetrisGame:
    def __init__(self, width: int = STORE_WIDTH):
        self.width = width
        self.height = 20

        self.score = 0
        self.lines_cleared = 0
        self.game_over = False
        self.paused = False

        # Game field: 0 = empty, 1-7 = block type
        self.field = [[0 for _ in range(self.width)] for _ in range(self.height)]

        # Current falling piece
        self.current_block = None
        self.current_block_x = 0
        self.current_block_y = 0
        self.current_block_rotation = 0

        # Next piece preview
        self.next_block = None
        self.next_block_rotation = 0

        # Timing
        self.fall_time = 0
        self.fall_speed = 0.5  # seconds
        self.last_time = pygame.time.get_ticks()
        self.key_pressed = {}  # Track which keys are pressed
        self.key_cooldown = {}  # Cooldown timers for each key
        self.key_cooldown_time = 100  # milliseconds
        self.pause_key_pressed = False  # Track pause key state for toggle

        # Initialize game
        self.spawn_piece()

    def get_block_min_score(self, block_type: BLOCK_TYPE) -> int:
        """Get minimal score of block to be shown"""
        return block_type.value['min_score']

    def get_block_shapes(self, block_type: BLOCK_TYPE) -> List[List[List[int]]]:
        """Get all rotation shapes for a block type"""
        return block_type.value['shapes']

    def get_block_color(self, block_type: BLOCK_TYPE) -> Tuple[int, int, int]:
        """Get color for a block type"""
        return block_type.value['color']

    def spawn_piece(self):
        """Spawn a new piece at the top"""
        if self.next_block is None:
            self.current_block = self.choice_random_block()
            self.current_block_rotation = 0
            self.next_block = self.choice_random_block()
            self.next_block_rotation = 0
        else:
            self.current_block = self.next_block
            self.current_block_rotation = self.next_block_rotation
            while self.current_block == self.next_block:
                self.next_block = self.choice_random_block()
            self.next_block_rotation = 0

        self.current_block_x = self.width // 2 - 2
        self.current_block_y = 0

        # Check if game is over (can't place new piece)
        if not self.can_place_block(self.current_block_x, self.current_block_y,
                                    self.current_block_rotation, self.current_block):
            self.game_over = True

    def choice_random_block(self) -> BLOCK_TYPE:
        block: BLOCK_TYPE = random.choice(list(BLOCK_TYPE))
        while self.get_block_min_score(block) > self.score:
            block = random.choice(list(BLOCK_TYPE))
        return block

    def get_block_shape(self, block_type: BLOCK_TYPE, rotation: int) -> List[List[int]]:
        """Get the shape matrix for a block at specific rotation"""
        shapes = self.get_block_shapes(block_type)
        return shapes[rotation % len(shapes)]

    def can_place_block(self, x: int, y: int, rotation: int, block_type: BLOCK_TYPE) -> bool:
        """Check if a block can be placed at given position"""
        shape = self.get_block_shape(block_type, rotation)

        for row_idx, row in enumerate(shape):
            for col_idx, cell in enumerate(row):
                if cell == 0:
                    continue

                field_x = x + col_idx
                field_y = y + row_idx

                # Check boundaries
                if field_x < 0 or field_x >= self.width or field_y >= self.height:
                    return False

                # Check collisions
                if field_y >= 0 and self.field[field_y][field_x] != 0:
                    return False

        return True

    def place_block(self, x: int, y: int, rotation: int, block_type: BLOCK_TYPE, block_id: int):
        """Place a block on the field"""
        shape = self.get_block_shape(block_type, rotation)

        for row_idx, row in enumerate(shape):
            for col_idx, cell in enumerate(row):
                if cell == 0:
                    continue

                field_x = x + col_idx
                field_y = y + row_idx

                if field_y >= 0 and 0 <= field_x < self.width:
                    self.field[field_y][field_x] = block_id

    def move_left(self):
        """Move current block left"""
        if self.can_place_block(self.current_block_x - 1, self.current_block_y,
                                self.current_block_rotation, self.current_block):
            self.current_block_x -= 1

    def move_right(self):
        """Move current block right"""
        if self.can_place_block(self.current_block_x + 1, self.current_block_y,
                                self.current_block_rotation, self.current_block):
            self.current_block_x += 1

    def rotate(self):
        """Rotate current block"""
        new_rotation = (self.current_block_rotation + 1) % len(
            self.get_block_shapes(self.current_block))

        if self.can_place_block(self.current_block_x, self.current_block_y,
                                new_rotation, self.current_block):
            self.current_block_rotation = new_rotation

    def drop(self):
        """Move block down, return True if block settled"""
        if self.can_place_block(self.current_block_x, self.current_block_y + 1,
                                self.current_block_rotation, self.current_block):
            self.current_block_y += 1
            return False
        else:
            # Block can't move down, place it
            self.place_block(self.current_block_x, self.current_block_y,
                             self.current_block_rotation, self.current_block,
                             list(BLOCK_TYPE).index(self.current_block) + 1)

            # Check for complete lines
            self.clear_lines()

            # Spawn new piece
            self.spawn_piece()
            return True

    def clear_lines(self):
        """Clear complete lines and return number cleared"""
        # Build new field without complete lines
        new_field = []
        cleared_count = 0

        # Check each row from bottom to top
        for row_idx in range(self.height):
            # If row is NOT complete, keep it
            if not all(cell != 0 for cell in self.field[row_idx]):
                new_field.append(self.field[row_idx][:])  # Copy the row
            else:
                cleared_count += 1

        # Add empty rows at the top for each cleared line
        for _ in range(cleared_count):
            new_field.insert(0, [0] * self.width)

        # Replace the field
        self.field = new_field

        # Update score and counter
        if cleared_count > 0:
            self.lines_cleared += cleared_count
            # Bonus scoring: 1 line = 100, 2 lines = 300, 3 lines = 500, 4 lines = 800
            score_bonus = [0, 100, 300, 500, 800]
            self.score += score_bonus[min(cleared_count, 4)]

        return cleared_count

    def update(self, current_ticks: int):
        """Update game state"""
        elapsed = (current_ticks - self.last_time) / 1000.0  # Convert to seconds
        self.last_time = current_ticks

        if cfg.HIGH_SCORE_INC_SPEED:
            if self.score > 5000:
                self.fall_speed = 0.55
            elif self.score > 10000:
                self.fall_speed = 0.6
            elif self.score > 15000:
                self.fall_speed = 0.65
        else:
            if self.score > 5000:
                self.fall_speed = 0.45
            elif self.score > 10000:
                self.fall_speed = 0.4
            elif self.score > 15000:
                self.fall_speed = 0.35

        if not self.game_over and not self.paused:
            self.fall_time += elapsed
            if self.fall_time >= self.fall_speed:
                self.fall_time = 0
                self.drop()

    def is_key_available(self, key: int, current_ticks: int) -> bool:
        """Check if enough time has passed since last key press"""
        if key not in self.key_cooldown:
            self.key_cooldown[key] = 0

        if current_ticks - self.key_cooldown[key] >= self.key_cooldown_time:
            self.key_cooldown[key] = current_ticks
            return True
        return False

    def handle_input(self, current_ticks: int, screen):
        """Handle keyboard input"""
        keys = pygame.key.get_pressed()

        if keys[KEY_CONFIG['exit']]:
            return not show_question_dialogue(screen, 'Are you sure to exit game?')

        # Handle pause toggle (detect key press, not hold)
        if keys[KEY_CONFIG['pause']]:
            if not self.pause_key_pressed:
                self.paused = not self.paused
                self.pause_key_pressed = True
        else:
            self.pause_key_pressed = False

        if keys[KEY_CONFIG['restart']]:
            self.reset()
            return True

        if self.game_over or self.paused:
            return True

        # Prevent double key pressing with cooldown
        if keys[KEY_CONFIG['left']] and self.is_key_available(KEY_CONFIG['left'], current_ticks):
            self.move_left()

        if keys[KEY_CONFIG['right']] and self.is_key_available(KEY_CONFIG['right'], current_ticks):
            self.move_right()

        if keys[KEY_CONFIG['rotate']] and self.is_key_available(KEY_CONFIG['rotate'], current_ticks):
            self.rotate()

        if keys[KEY_CONFIG['drop']] and self.is_key_available(KEY_CONFIG['drop'], current_ticks):
            self.drop()

        return True

    def reset(self):
        """Reset the game"""
        self.score = 0
        self.lines_cleared = 0
        self.game_over = False
        self.paused = False
        self.field = [[0 for _ in range(self.width)] for _ in range(self.height)]
        self.current_block = None
        self.next_block = None
        self.fall_time = 0
        self.key_cooldown = {}
        self.pause_key_pressed = False
        self.spawn_piece()


class TetrisRenderer:
    def __init__(self, width: int = STORE_WIDTH):
        self.width = width
        self.height = 20
        self.block_size = BLOCK_SIZE

        # Calculate window size
        self.field_width = self.width * self.block_size
        self.field_height = self.height * self.block_size
        self.ui_width = 250

        self.window_width = self.field_width + self.ui_width + 60
        self.window_height = self.field_height + 80

        self.screen = pygame.display.set_mode((self.window_width, self.window_height))
        pygame.display.set_caption("Tetris by -=:dAs:=-")

        self.clock = pygame.time.Clock()
        self.font_large = pygame.font.Font(None, 48)
        self.font_medium = pygame.font.Font(None, 32)
        self.font_small = pygame.font.Font(None, 24)

        # Positioning
        self.field_x = 20
        self.field_y = 20
        self.ui_x = self.field_x + self.field_width + 30
        self.ui_y = self.field_y

    def draw_block(self, x: int, y: int, color: Tuple[int, int, int], size: int = None):
        """Draw a single block"""
        if size is None:
            size = self.block_size

        pygame.draw.rect(self.screen, color, (x, y, size - 2, size - 2))
        pygame.draw.rect(self.screen, (255, 255, 255), (x, y, size - 2, size - 2), 1)

    def render(self, game: TetrisGame):
        """Render the entire game"""
        self.screen.fill(COLORS['background'])

        # Draw field border
        pygame.draw.rect(self.screen, COLORS['border'],
                         (self.field_x - 2, self.field_y - 2,
                          self.field_width + 4, self.field_height + 4), 2)

        # Draw field background
        pygame.draw.rect(self.screen, COLORS['field'],
                         (self.field_x, self.field_y, self.field_width, self.field_height))

        # Draw placed blocks
        for row_idx, row in enumerate(game.field):
            for col_idx, cell in enumerate(row):
                if cell != 0:
                    block_type = list(BLOCK_TYPE)[cell - 1]
                    color = game.get_block_color(block_type)
                    x = self.field_x + col_idx * self.block_size
                    y = self.field_y + row_idx * self.block_size
                    self.draw_block(x, y, color)

        # Draw current falling piece
        if game.current_block is not None:
            shape = game.get_block_shape(game.current_block, game.current_block_rotation)
            color = game.get_block_color(game.current_block)

            for row_idx, row in enumerate(shape):
                for col_idx, cell in enumerate(row):
                    if cell == 1:
                        x = self.field_x + (game.current_block_x + col_idx) * self.block_size
                        y = self.field_y + (game.current_block_y + row_idx) * self.block_size

                        if y >= self.field_y:  # Only draw if visible
                            self.draw_block(x, y, color)

        # Draw UI panel
        self._draw_ui(game)

        # Draw game over screen
        if game.game_over:
            self._draw_game_over()

        # Draw pause screen
        if game.paused:
            self._draw_paused()

        pygame.display.flip()

    def _draw_ui(self, game: TetrisGame):
        """Draw UI panel with score and next piece"""
        # Title
        title = self.font_medium.render("-=:dAs:=- TETRIS", True, COLORS['text'])
        self.screen.blit(title, (self.ui_x, self.ui_y))

        # Score
        score_label = self.font_small.render("Score:", True, COLORS['text'])
        score_value = self.font_medium.render(str(game.score), True, (255, 255, 0))
        self.screen.blit(score_label, (self.ui_x, self.ui_y + 60))
        self.screen.blit(score_value, (self.ui_x, self.ui_y + 90))

        # Lines
        lines_label = self.font_small.render("Lines:", True, COLORS['text'])
        lines_value = self.font_medium.render(str(game.lines_cleared), True, (255, 255, 0))
        self.screen.blit(lines_label, (self.ui_x, self.ui_y + 140))
        self.screen.blit(lines_value, (self.ui_x, self.ui_y + 170))

        # Next block
        next_label = self.font_small.render("Next:", True, COLORS['text'])
        self.screen.blit(next_label, (self.ui_x, self.ui_y + 220))

        if game.next_block is not None:
            shape = game.get_block_shape(game.next_block, game.next_block_rotation)
            color = game.get_block_color(game.next_block)

            preview_x = self.ui_x + 10
            preview_y = self.ui_y + 260
            block_preview_size = 15

            for row_idx, row in enumerate(shape):
                for col_idx, cell in enumerate(row):
                    if cell == 1:
                        x = preview_x + col_idx * block_preview_size
                        y = preview_y + row_idx * block_preview_size
                        self.draw_block(x, y, color, block_preview_size)

        # Controls
        controls_y = self.ui_y + 380

        for i, text in enumerate(CONTROLS_TEXT):
            if i == 0:
                control_surf = self.font_small.render(text, True, (100, 200, 255))
            else:
                control_surf = self.font_small.render(text, True, (150, 150, 200))
            self.screen.blit(control_surf, (self.ui_x - 5, controls_y + i * 25))

    def _draw_game_over(self):
        """Draw game over overlay"""
        overlay = pygame.Surface((self.window_width, self.window_height))
        overlay.set_alpha(128)
        overlay.fill((0, 0, 0))
        self.screen.blit(overlay, (0, 0))

        game_over_text = self.font_large.render("GAME OVER", True, COLORS['game_over'])
        restart_text = self.font_medium.render("Press R to Restart", True, COLORS['text'])

        text_rect = game_over_text.get_rect(center=(self.window_width // 2, self.window_height // 2 - 40))
        restart_rect = restart_text.get_rect(center=(self.window_width // 2, self.window_height // 2 + 40))

        self.screen.blit(game_over_text, text_rect)
        self.screen.blit(restart_text, restart_rect)

    def _draw_paused(self):
        """Draw pause overlay"""
        overlay = pygame.Surface((self.window_width, self.window_height))
        overlay.set_alpha(128)
        overlay.fill((0, 0, 0))
        self.screen.blit(overlay, (0, 0))

        paused_text = self.font_large.render("PAUSED", True, (100, 200, 255))
        continue_text = self.font_medium.render("Press P to Continue", True, COLORS['text'])

        text_rect = paused_text.get_rect(center=(self.window_width // 2, self.window_height // 2 - 40))
        continue_rect = continue_text.get_rect(center=(self.window_width // 2, self.window_height // 2 + 40))

        self.screen.blit(paused_text, text_rect)
        self.screen.blit(continue_text, continue_rect)

    def get_fps(self) -> float:
        """Get current FPS"""
        return self.clock.get_fps()


def show_question_dialogue(screen, question_text, font=None):
    """
    Shows a question dialogue with Yes and No buttons.

    Args:
        screen: Pygame display surface
        question_text: The question to display
        font: Pygame font object (optional, will create default if None)

    Returns:
        True if Yes is clicked, False if No is clicked
    """
    # Initialize font if not provided
    if font is None:
        font = pygame.font.Font(None, 36)

    # Get screen dimensions
    screen_width, screen_height = screen.get_size()

    # Define colors
    OVERLAY_COLOR = (0, 0, 0, 180)  # Semi-transparent black
    DIALOG_BG = (50, 50, 50)
    TEXT_COLOR = (255, 255, 255)
    YES_COLOR = (50, 150, 50)
    YES_HOVER = (70, 180, 70)
    NO_COLOR = (150, 50, 50)
    NO_HOVER = (180, 70, 70)
    BUTTON_TEXT = (255, 255, 255)

    # Create dialogue box dimensions
    dialog_width = 500
    dialog_height = 250
    dialog_x = (screen_width - dialog_width) // 2
    dialog_y = (screen_height - dialog_height) // 2

    # Button dimensions
    button_width = 120
    button_height = 50
    button_spacing = 40
    yes_button_x = dialog_x + dialog_width // 2 - button_width - button_spacing // 2
    no_button_x = dialog_x + dialog_width // 2 + button_spacing // 2
    button_y = dialog_y + dialog_height - button_height - 30

    yes_button_rect = pygame.Rect(yes_button_x, button_y, button_width, button_height)
    no_button_rect = pygame.Rect(no_button_x, button_y, button_width, button_height)

    # Render question text (word wrap)
    words = question_text.split()
    lines = []
    current_line = []
    max_width = dialog_width - 40

    for word in words:
        test_line = ' '.join(current_line + [word])
        if font.size(test_line)[0] <= max_width:
            current_line.append(word)
        else:
            if current_line:
                lines.append(' '.join(current_line))
            current_line = [word]
    if current_line:
        lines.append(' '.join(current_line))

    # Main dialogue loop
    running = True
    result = None

    while running:
        mouse_pos = pygame.mouse.get_pos()
        yes_hovered = yes_button_rect.collidepoint(mouse_pos)
        no_hovered = no_button_rect.collidepoint(mouse_pos)

        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                pygame.quit()
                sys.exit()

            if event.type == pygame.MOUSEBUTTONDOWN:
                if event.button == 1:  # Left click
                    if yes_hovered:
                        result = True
                        running = False
                    elif no_hovered:
                        result = False
                        running = False

            # Optional: keyboard shortcuts
            if event.type == pygame.KEYDOWN:
                if event.key == pygame.K_y:
                    result = True
                    running = False
                elif event.key == pygame.K_n:
                    result = False
                    running = False

        # Draw everything
        # Create semi-transparent overlay
        overlay = pygame.Surface((screen_width, screen_height))
        overlay.set_alpha(180)
        overlay.fill((0, 0, 0))
        screen.blit(overlay, (0, 0))

        # Draw dialogue box
        pygame.draw.rect(screen, DIALOG_BG, (dialog_x, dialog_y, dialog_width, dialog_height))
        pygame.draw.rect(screen, TEXT_COLOR, (dialog_x, dialog_y, dialog_width, dialog_height), 2)

        # Draw question text
        text_y = dialog_y + 30
        for line in lines:
            text_surface = font.render(line, True, TEXT_COLOR)
            text_rect = text_surface.get_rect(center=(dialog_x + dialog_width // 2, text_y))
            screen.blit(text_surface, text_rect)
            text_y += font.get_height() + 5

        # Draw Yes button
        yes_color = YES_HOVER if yes_hovered else YES_COLOR
        pygame.draw.rect(screen, yes_color, yes_button_rect, border_radius=5)
        pygame.draw.rect(screen, TEXT_COLOR, yes_button_rect, 2, border_radius=5)
        yes_text = font.render("Yes", True, BUTTON_TEXT)
        yes_text_rect = yes_text.get_rect(center=yes_button_rect.center)
        screen.blit(yes_text, yes_text_rect)

        # Draw No button
        no_color = NO_HOVER if no_hovered else NO_COLOR
        pygame.draw.rect(screen, no_color, no_button_rect, border_radius=5)
        pygame.draw.rect(screen, TEXT_COLOR, no_button_rect, 2, border_radius=5)
        no_text = font.render("No", True, BUTTON_TEXT)
        no_text_rect = no_text.get_rect(center=no_button_rect.center)
        screen.blit(no_text, no_text_rect)

        pygame.display.flip()

    return result


def main():
    """Main game loop"""
    game = TetrisGame(width=STORE_WIDTH)
    renderer = TetrisRenderer(width=STORE_WIDTH)

    running = True

    while running:
        current_ticks = pygame.time.get_ticks()

        # Handle events
        for event in pygame.event.get():
            if event.type == pygame.QUIT and show_question_dialogue(renderer.screen, 'Are you sure to exit game?'):
                running = False
                break

        if not running:
            break  # Exit main loop if quit was requested

        # Handle input
        if not game.handle_input(current_ticks, renderer.screen):
            running = False
            continue

        # Update game
        game.update(current_ticks)

        # Render
        renderer.render(game)
        renderer.clock.tick(60)  # 60 FPS

    pygame.quit()


if __name__ == "__main__":
    main()