import pygame
import random
import sys
import time
# [pygame.init()] is a function that initializes all the imported Pygame modules that require initialization.
pygame.init()

# Constants
WIDTH, HEIGHT = 600, 400
screen = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption("Cube Runnin'")

# Fonts
font_small = pygame.font.SysFont(None, 24)
font_med = pygame.font.SysFont(None, 36)
font_large = pygame.font.SysFont(None, 48)
font_xlarge = pygame.font.SysFont(None, 72)

# Colors
WHITE = (255, 255, 255)
BLUE = (0, 0, 255)
DARK_BLUE = (0, 0, 139)  # New double points powerup color
RED = (255, 0, 0)
DARK_RED = (180, 0, 0)
GREEN = (0, 255, 0)
YELLOW = (255, 255, 0)  # Shield color
BLACK = (0, 0, 0)
GRAY = (200, 200, 200)
TRANSPARENT_BLACK_180 = (0, 0, 0, 180)
SPEED_GLOW_COLOR = (50, 255, 50)
SUPER_SHINY_PINK = (255, 100, 180)  # supershield pink glow color
OPSHIELD_COLOR = (180, 30, 90)  # opshield dark pink

# Game settings
clock = pygame.time.Clock()
FPS = 60
floor_height = 40
player_size = 20
gravity = 1
jump_speed = 15
base_move_speed = 5

# States
STATE_MENU = 0
STATE_PLAYING = 1
STATE_PAUSED = 2
STATE_GAMEOVER = 3
STATE_SETTINGS = 4  # added settings state

# Power-up durations in frames
POWERUP_DURATION_FRAMES = FPS * 30  # 30 seconds (double jump, speed boost, double points)
SHIELD_DURATION_FRAMES = FPS * 10   # 10 seconds (shield)
SUPERSHIELD_DURATION_FRAMES = FPS * 30  # 30 seconds supershield
OPSHIELD_DURATION_FRAMES = FPS * 45  # 45 seconds opshield

OPSHIELD_HITS = 5
# def is a keyword used to define a function. Functions are reusable blocks of code that perform a specific task.
def draw_text_with_outline(surface, text, font, color, outline_color, x, y, center=True):
    text_surface = font.render(text, True, color)
    outline_surf = font.render(text, True, outline_color)
# the if statement is used for conditional execution of code.
    if center:
        rect = text_surface.get_rect(center=(x, y))
# the else keyword is used in several contexts to define a block of code that executes when some conditions are met or not.
    else:
        rect = text_surface.get_rect(topleft=(x, y))

    # Draw outline
    outline_positions = [(rect.x - 1, rect.y), (rect.x + 1, rect.y), (rect.x, rect.y - 1), (rect.x, rect.y + 1)]
# the for loop is used to iterate over a sequence (like a range) and execute a block of code for each element in that sequence.
    for pos in outline_positions :
        surface.blit(outline_surf, pos)
    surface.blit(text_surface, rect)

class Player:
    def __init__(self):
        self.rect = pygame.Rect(WIDTH // 2, HEIGHT - floor_height - player_size, player_size, player_size)
        self.dy = 0
        self.on_floor = True
        self.can_double_jump = False
        self.double_jump_available = False
        self.speed_boost = False
        self.shield_active = False
        self.supershield_active = False
        self.opshield_active = False
        self.double_points_active = False  # New double points flag
        self.supershield_hits_left = 0
        self.opshield_hits_left = 0

    def jump(self):
        if self.on_floor:
            self.dy = -jump_speed
            self.on_floor = False
            self.double_jump_available = self.can_double_jump
        elif self.double_jump_available:
            self.dy = -jump_speed
            self.double_jump_available = False

    def move(self, dx):
        self.rect.x += dx
        if self.rect.x < 0:
            self.rect.x = 0
        if self.rect.x > WIDTH - player_size:
            self.rect.x = WIDTH - player_size

    def update(self, floors):
        self.dy += gravity
        self.rect.y += self.dy

        collided_floor = None
        for floor in floors:
            if self.rect.colliderect(floor.rect):
                # Check if player is landing from above
                if self.dy >= 0 and self.rect.bottom - self.dy <= floor.rect.top:
                    self.rect.bottom = floor.rect.top
                    self.dy = 0
                    self.on_floor = True
                    collided_floor = floor
                    break
        else:
            self.on_floor = False

        if collided_floor and self.dy == 0:  # landed, damage floor
            if collided_floor.damage():
                floors.remove(collided_floor)

        if self.rect.y > HEIGHT - floor_height - player_size:
            self.rect.y = HEIGHT - floor_height - player_size
            self.dy = 0
            self.on_floor = True

        # Restrict player horizontal movement inside screen bounds
        if self.rect.x < 0:
            self.rect.x = 0
        elif self.rect.x > WIDTH - player_size:
            self.rect.x = WIDTH - player_size

    def draw(self, surface):
        if self.speed_boost:
            glow_radius = player_size * 2
            glow_surf = pygame.Surface((glow_radius * 2, glow_radius * 2), pygame.SRCALPHA)
            pygame.draw.circle(glow_surf, (*SPEED_GLOW_COLOR, 100), (glow_radius, glow_radius), glow_radius)
            surface.blit(glow_surf, (self.rect.centerx - glow_radius, self.rect.centery - glow_radius))
        if self.opshield_active:
            pygame.draw.ellipse(surface, OPSHIELD_COLOR, self.rect.inflate(18, 18), 5)
        if self.supershield_active:
            pygame.draw.ellipse(surface, SUPER_SHINY_PINK, self.rect.inflate(14, 14), 4)
        if self.shield_active:
            pygame.draw.ellipse(surface, YELLOW, self.rect.inflate(10, 10), 3)
        pygame.draw.rect(surface, BLUE, self.rect)

class Floor:
    def __init__(self, y):
        self.rect = pygame.Rect(0, y, WIDTH, floor_height)
        self.max_health = 5
        self.health = self.max_health

    def draw(self, surface):
        health_ratio = self.health / self.max_health
        floor_color = (
            int(RED[0] * health_ratio + DARK_RED[0] * (1 - health_ratio)),
            int(RED[1] * health_ratio + DARK_RED[1] * (1 - health_ratio)),
            int(RED[2] * health_ratio + DARK_RED[2] * (1 - health_ratio))
        )
        pygame.draw.rect(surface, floor_color, self.rect)
        bar_width = self.rect.width * 0.3
        bar_height = 6
        bar_x = (WIDTH - bar_width) / 2
        bar_y = self.rect.top - bar_height - 4
        pygame.draw.rect(surface, BLACK, (bar_x, bar_y, bar_width, bar_height), 1)
        inner_width = bar_width * (self.health / self.max_health)
        pygame.draw.rect(surface, GREEN, (bar_x + 1, bar_y + 1, inner_width - 2, bar_height - 2))

    def damage(self):
        self.health -= 1
        return self.health <= 0

class PowerUp:
    def __init__(self, x, y, power_type):
        self.rect = pygame.Rect(x, y, player_size, player_size)
        self.power_type = power_type
        self.fall_speed = random.randint(3, 6)

    def apply(self, player, timers):
        if self.power_type == "double_jump":
            player.can_double_jump = True
            timers['double_jump'] = POWERUP_DURATION_FRAMES
        elif self.power_type == "speed_boost":
            player.speed_boost = True
            timers['speed_boost'] = POWERUP_DURATION_FRAMES
        elif self.power_type == "shield":
            player.shield_active = True
            timers['shield'] = SHIELD_DURATION_FRAMES
        elif self.power_type == "supershield":
            player.supershield_active = True
            player.supershield_hits_left = 3
            timers['supershield'] = SUPERSHIELD_DURATION_FRAMES
        elif self.power_type == "opshield":
            player.opshield_active = True
            player.opshield_hits_left = OPSHIELD_HITS
            timers['opshield'] = OPSHIELD_DURATION_FRAMES
        elif self.power_type == "double_points":
            player.double_points_active = True
            timers['double_points'] = POWERUP_DURATION_FRAMES

    def update(self):
        self.rect.y += self.fall_speed

    def draw(self, surface):
        if self.power_type == "double_jump":
            color = BLUE
            pygame.draw.rect(surface, color, self.rect)
        elif self.power_type == "speed_boost":
            color = GREEN
            pygame.draw.rect(surface, color, self.rect)
        elif self.power_type == "shield":
            color = YELLOW
            pygame.draw.rect(surface, color, self.rect)
        elif self.power_type == "supershield":
            glow_radius = player_size
            glow_surf = pygame.Surface((glow_radius * 4, glow_radius * 4), pygame.SRCALPHA)
            for i in range(3, 0, -1):
                alpha = 30 * i
                rect = pygame.Rect(
                    glow_radius - i * 6,
                    glow_radius - i * 6,
                    self.rect.width + i * 12,
                    self.rect.height + i * 12,
                )
                pygame.draw.rect(glow_surf, (*SUPER_SHINY_PINK[:3], alpha), rect, border_radius=4)
            surface.blit(glow_surf, (self.rect.x - glow_radius * 1.5, self.rect.y - glow_radius * 1.5))
            pygame.draw.rect(surface, SUPER_SHINY_PINK, self.rect, border_radius=4)
        elif self.power_type == "opshield":
            glow_radius = player_size
            glow_surf = pygame.Surface((glow_radius * 4, glow_radius * 4), pygame.SRCALPHA)
            for i in range(3, 0, -1):
                alpha = 40 * i
                rect = pygame.Rect(
                    glow_radius - i * 6,
                    glow_radius - i * 6,
                    self.rect.width + i * 12,
                    self.rect.height + i * 12,
                )
                pygame.draw.rect(glow_surf, (*OPSHIELD_COLOR[:3], alpha), rect, border_radius=4)
            surface.blit(glow_surf, (self.rect.x - glow_radius * 1.5, self.rect.y - glow_radius * 1.5))
            pygame.draw.rect(surface, OPSHIELD_COLOR, self.rect, border_radius=4)
        elif self.power_type == "double_points":
            color = DARK_BLUE
            pygame.draw.rect(surface, color, self.rect)
        else:
            color = WHITE
            pygame.draw.rect(surface, color, self.rect)

class KillBlock:
    def __init__(self, x, y):
        self.size = 60
        self.rect = pygame.Rect(x, y, self.size, self.size)
        self.fall_speed = random.randint(4, 7)

    def update(self):
        self.rect.y += self.fall_speed
        if self.rect.top > HEIGHT:
            self.rect.x = random.randint(0, WIDTH - self.size)
            self.rect.y = -self.size
            self.fall_speed = random.randint(4, 7)

    def draw(self, surface):
        pygame.draw.rect(surface, DARK_RED, self.rect)

def draw_paused_overlay(surface):
    overlay = pygame.Surface((WIDTH, HEIGHT), pygame.SRCALPHA)
    overlay.fill(TRANSPARENT_BLACK_180)
    surface.blit(overlay, (0, 0))
    draw_text_with_outline(surface, "PAUSED", font_xlarge, WHITE, BLACK, WIDTH//2, HEIGHT//3)
    draw_text_with_outline(surface, "Press ESC to Resume", font_med, WHITE, BLACK, WIDTH//2, HEIGHT//2)

def draw_powerup_timers(surface, timers):
    x = 10
    y = HEIGHT - 90
    bar_width = 140
    bar_height = 20
    gap = 10
    for p_type in timers:
        pygame.draw.rect(surface, BLACK, (x, y, bar_width, bar_height))
        if p_type == 'shield':
            max_frames = SHIELD_DURATION_FRAMES
        elif p_type == 'supershield':
            max_frames = SUPERSHIELD_DURATION_FRAMES
        elif p_type == 'opshield':
            max_frames = OPSHIELD_DURATION_FRAMES
        else:
            max_frames = POWERUP_DURATION_FRAMES
        progress = timers[p_type] / max_frames
        color = (
            GREEN if p_type == 'speed_boost' else
            BLUE if p_type == 'double_jump' else
            YELLOW if p_type == 'shield' else
            SUPER_SHINY_PINK if p_type == 'supershield' else
            OPSHIELD_COLOR if p_type == 'opshield' else
            DARK_BLUE if p_type == 'double_points' else
            WHITE
        )
        pygame.draw.rect(surface, color, (x + 2, y + 2, int((bar_width - 4) * progress), bar_height - 4))
        label = f"{p_type.replace('_', ' ').title()}: {timers[p_type] // FPS}s"
        draw_text_with_outline(surface, label, font_small, WHITE, BLACK, x + bar_width//2, y + 2 + bar_height//2)
        y += bar_height + gap

def draw_settings_menu(surface, show_fps):
    draw_text_with_outline(surface, "Settings", font_large, BLACK, WHITE, WIDTH // 2, HEIGHT // 6)
    status_fps = "ON" if show_fps else "OFF"
    draw_text_with_outline(surface, f"Show FPS: {status_fps} (Press F to toggle)", font_med, BLACK, WHITE, WIDTH // 2, HEIGHT // 3)
    draw_text_with_outline(surface, "Press ESC to return to Menu", font_med, BLACK, WHITE, WIDTH // 2, HEIGHT // 2)

def main():
    state = STATE_MENU
    paused = False
    show_fps = True  # Settings option

    player = Player()
    floors = [Floor(HEIGHT - (i + 1) * floor_height) for i in range(10)]
    powerups = []
    kill_blocks = []
    for i in range(3):
        kb_x = random.randint(0, WIDTH - 60)
        kb_y = random.randint(-600, -60)
        kill_blocks.append(KillBlock(kb_x, kb_y))

    score = 0
    score_timer = 0

    move_speed_current = base_move_speed

    powerup_timers = {
        'double_jump': 0,
        'speed_boost': 0,
        'shield': 0,
        'supershield': 0,
        'opshield': 0,
        'double_points': 0,  # Added new timer entry
    }

    # Track consecutive shields collected in a row
    consecutive_shields_collected = 0

    button_rect = pygame.Rect(WIDTH // 2 - 100, HEIGHT // 2 - 25, 200, 50)

    while True:
        clock.tick(FPS)

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

            if state == STATE_MENU:
                if event.type == pygame.MOUSEBUTTONDOWN and event.button == 1:
                    mouse_pos = pygame.mouse.get_pos()
                    if button_rect.collidepoint(mouse_pos):
                        # Reset game state
                        player = Player()
                        floors = [Floor(HEIGHT - (i + 1) * floor_height) for i in range(10)]
                        powerups = []
                        kill_blocks = []
                        for i in range(3):
                            kb_x = random.randint(0, WIDTH - 60)
                            kb_y = random.randint(-600, -60)
                            kill_blocks.append(KillBlock(kb_x, kb_y))
                        score = 0
                        score_timer = 0
                        move_speed_current = base_move_speed
                        powerup_timers = {k: 0 for k in powerup_timers}
                        consecutive_shields_collected = 0
                        state = STATE_PLAYING
                        paused = False
                elif event.type == pygame.KEYDOWN:
                    if event.key == pygame.K_SPACE:
                        player = Player()
                        floors = [Floor(HEIGHT - (i + 1) * floor_height) for i in range(10)]
                        powerups = []
                        kill_blocks = []
                        for i in range(3):
                            kb_x = random.randint(0, WIDTH - 60)
                            kb_y = random.randint(-600, -60)
                            kill_blocks.append(KillBlock(kb_x, kb_y))
                        score = 0
                        score_timer = 0
                        move_speed_current = base_move_speed
                        powerup_timers = {k: 0 for k in powerup_timers}
                        consecutive_shields_collected = 0
                        state = STATE_PLAYING
                        paused = False

                    elif event.key == pygame.K_s:
                        state = STATE_SETTINGS

            elif state == STATE_SETTINGS:
                if event.type == pygame.KEYDOWN:
                    if event.key == pygame.K_ESCAPE:
                        state = STATE_MENU
                    elif event.key == pygame.K_f:
                        show_fps = not show_fps

            elif state == STATE_PLAYING:
                if event.type == pygame.KEYDOWN and not paused:
                    if event.key in (pygame.K_SPACE, pygame.K_w, pygame.K_UP):
                        player.jump()
                if event.type == pygame.KEYDOWN:
                    if event.key == pygame.K_ESCAPE:
                        paused = not paused

            elif state == STATE_GAMEOVER:
                if event.type == pygame.KEYDOWN:
                    if event.key == pygame.K_r:
                        state = STATE_MENU

        keys = pygame.key.get_pressed()

        if state == STATE_PLAYING and not paused:
            dx = 0
            if keys[pygame.K_a] or keys[pygame.K_LEFT]:
                dx -= move_speed_current
            if keys[pygame.K_d] or keys[pygame.K_RIGHT]:
                dx += move_speed_current

            if powerup_timers['speed_boost'] > 0:
                dx *= 2

            player.move(dx)
            player.update(floors)

            # Chance to spawn powerup: Added 10% for double_points, others changed slightly for balance
            if random.randint(1, 100) == 1:
                pu_type = random.choices(
                    ["double_jump", "speed_boost", "shield", "supershield", "opshield", "double_points"],
                    weights=[25, 25, 25, 1, 0.1, 10],  # weights balanced, double_points 10% rare but much more common than supershield/opshield
                    k=1
                )[0]
                powerups.append(PowerUp(random.randint(0, WIDTH - player_size), -player_size, pu_type))

            for pu in powerups[:]:
                pu.update()
                if pu.rect.top > HEIGHT:
                    pu.rect.x = random.randint(0, WIDTH - player_size)
                    pu.rect.y = -player_size
                    pu.fall_speed = random.randint(3, 6)
                elif player.rect.colliderect(pu.rect):
                    pu.apply(player, powerup_timers)

                    # Score gain doubled if double_points active
                    points_earned = 2 if player.double_points_active else 1
                    score += points_earned

                    # Track consecutive shields
                    if pu.power_type == "shield":
                        consecutive_shields_collected += 1
                        if consecutive_shields_collected == 6:
                            # Grant opshield immediately
                            player.opshield_active = True
                            player.opshield_hits_left = OPSHIELD_HITS
                            powerup_timers['opshield'] = OPSHIELD_DURATION_FRAMES
                            consecutive_shields_collected = 0
                    elif pu.power_type == "opshield":
                        consecutive_shields_collected = 0
                    else:
                        consecutive_shields_collected = 0

                    powerups.remove(pu)

            for kb in kill_blocks:
                kb.update()
                if player.rect.colliderect(kb.rect):
                    if player.opshield_active:
                        player.opshield_hits_left -= 1
                        if player.opshield_hits_left <= 0:
                            player.opshield_active = False
                            powerup_timers['opshield'] = 0
                        kb.rect.x = random.randint(0, WIDTH - kb.size)
                        kb.rect.y = -kb.size
                        kb.fall_speed = random.randint(4, 7)
                    elif player.supershield_active:
                        player.supershield_hits_left -= 1
                        if player.supershield_hits_left <= 0:
                            player.supershield_active = False
                            powerup_timers['supershield'] = 0
                        kb.rect.x = random.randint(0, WIDTH - kb.size)
                        kb.rect.y = -kb.size
                        kb.fall_speed = random.randint(4, 7)
                    elif player.shield_active:
                        player.shield_active = False
                        powerup_timers['shield'] = 0
                        kb.rect.x = random.randint(0, WIDTH - kb.size)
                        kb.rect.y = -kb.size
                        kb.fall_speed = random.randint(4, 7)
                    else:
                        state = STATE_GAMEOVER

            # Update timers for power-ups and deactivate when due
            for key in powerup_timers:
                if powerup_timers[key] > 0:
                    powerup_timers[key] -= 1
                    if powerup_timers[key] == 0:
                        if key == 'double_jump':
                            player.can_double_jump = False
                            player.double_jump_available = False
                        elif key == 'speed_boost':
                            player.speed_boost = False
                        elif key == 'shield':
                            player.shield_active = False
                        elif key == 'supershield':
                            player.supershield_active = False
                            player.supershield_hits_left = 0
                        elif key == 'opshield':
                            player.opshield_active = False
                            player.opshield_hits_left = 0
                        elif key == 'double_points':
                            player.double_points_active = False

            score_timer += 1
            if score_timer >= 10:
                # Increment score by 1 every 10 frames, double if double_points active
                points_earned = 2 if player.double_points_active else 1
                score += points_earned
                score_timer = 0

        screen.fill(WHITE)

        if state == STATE_MENU:
            draw_text_with_outline(screen, "Cube Runnin'", font_large, BLACK, WHITE, WIDTH // 2, HEIGHT // 4)
            pygame.draw.rect(screen, GRAY, button_rect)
            draw_text_with_outline(screen, "PLAY GAME", font_med, BLACK, WHITE, WIDTH // 2, HEIGHT // 2 - 12)
            draw_text_with_outline(screen, "Move: A/D or Arrows, Jump: W/Up/Space", font_small, BLACK, WHITE, WIDTH // 2, HEIGHT // 2 + 50)
            draw_text_with_outline(screen, "Avoid falling red blocks! Collect powerups!", font_small, BLACK, WHITE, WIDTH // 2, HEIGHT // 2 + 80)
            draw_text_with_outline(screen, "Press S for Settings", font_small, BLACK, WHITE, WIDTH // 2, HEIGHT // 2 + 110)

        elif state == STATE_SETTINGS:
            draw_settings_menu(screen, show_fps)

        elif state == STATE_PLAYING:
            for floor in floors:
                floor.draw(screen)

            for kb in kill_blocks:
                kb.draw(screen)

            for pu in powerups:
                pu.draw(screen)

            player.draw(screen)

            draw_text_with_outline(screen, f"Score: {score}", font_med, BLACK, WHITE, 60, 20, center=False)

            active_timers = {k: v for k, v in powerup_timers.items() if v > 0}
            if active_timers:
                draw_powerup_timers(screen, active_timers)

            y_ui = 50
            if player.can_double_jump:
                draw_text_with_outline(screen, "Double Jump Active", font_small, BLUE, WHITE, WIDTH - 150, y_ui)
                y_ui += 24
            if player.speed_boost:
                draw_text_with_outline(screen, "Speed Boost Active", font_small, GREEN, WHITE, WIDTH - 150, y_ui)
                y_ui += 24
            if player.shield_active:
                draw_text_with_outline(screen, "Shield Active", font_small, YELLOW, WHITE, WIDTH - 150, y_ui)
                y_ui += 24
            if player.supershield_active:
                draw_text_with_outline(screen, f"Super Shield Active ({player.supershield_hits_left} hits left)", font_small, SUPER_SHINY_PINK, WHITE, WIDTH - 200, y_ui)
                y_ui += 24
            if player.opshield_active:
                draw_text_with_outline(screen, f"OPShield Active ({player.opshield_hits_left} hits left)", font_small, OPSHIELD_COLOR, WHITE, WIDTH - 220, y_ui)
                y_ui += 24
            if player.double_points_active:
                draw_text_with_outline(screen, "Double Points Active", font_small, DARK_BLUE, WHITE, WIDTH - 150, y_ui)
                y_ui += 24

            if paused:
                draw_paused_overlay(screen)

            if show_fps:
                fps = int(clock.get_fps())
                draw_text_with_outline(screen, f"FPS: {fps}", font_small, BLACK, WHITE, 10, HEIGHT - 25, center=False)

        elif state == STATE_GAMEOVER:
            draw_text_with_outline(screen, "GAME OVER", font_xlarge, RED, BLACK, WIDTH // 2, HEIGHT // 3)
            draw_text_with_outline(screen, f"Final Score: {score}", font_med, BLACK, WHITE, WIDTH // 2, HEIGHT // 2)
            draw_text_with_outline(screen, "Press R to Restart", font_med, BLACK, WHITE, WIDTH // 2, HEIGHT // 2 + 50)

        pygame.display.flip()

if __name__ == "__main__":
    main()