"""Labyrinth Quest – Hauptdatei / Einstiegspunkt."""
import pyxel
import math

from constants import *
from levels import LEVEL_MAPS, LEVEL_CFG
from collectibles import ITEM_MAP, INSTANT_ITEMS, Clock, Armor, Potion
from player import Player
from walls import WallPiece, create_wall_pieces
from states import (
    IntroState, MenuState, FlyingState, FlyingResumeState,
    LockedState, GhostState, CelebratingState, WinState
)

class Game:
    def __init__(self):
        pyxel.init(SCREEN_W, SCREEN_H, title="Labyrinth Quest", fps=30,
                   display_scale=DISPLAY_SCALE)

        self.has_res = False
        try:
            pyxel.load("res2.pyxres")
            self.has_res = True
        except Exception:
            self.has_res = False

        self.level          = 1
        self.score          = 0
        self.total_keys     = 0
        self.total_time     = 0
        self.total_bonus    = 0
        self.first_run      = True
        self.time_up        = False
        self.player         = None
        self.keys_needed    = 0
        self.keys_deposited = 0
        self.popups         = []
        self.hide_player    = False
        self.highscores = []       # Top-5 Session-Highscores
        self.last_score_rank = -1  # Position des letzten Scores (-1 = nicht in Top 5)

        self.states = {
            "intro":         IntroState(self),
            "menu":          MenuState(self),
            "flying":        FlyingState(self),
            "flying_resume": FlyingResumeState(self),
            "locked":        LockedState(self),
            "ghost":         GhostState(self),
            "celebrating":   CelebratingState(self),
            "win":           WinState(self),
        }
        self.current_state = None
        self.change_state("intro")
        pyxel.run(self.update, self.draw)

    def change_state(self, name):
        self.current_state = self.states[name]
        self.current_state.enter()

    def update(self):
        self.current_state.update()
        self._update_popups()

    def draw(self):
        pyxel.cls(0)
        self.current_state.draw()
        self._draw_popups()

    def reset_round(self):
        self._parse_map()
        self.player         = Player(self.start_x, self.start_y, self.has_res)
        self.carried_items  = []
        self.keys_deposited = 0
        self.bonus_points   = 0
        self.round_time     = 0
        self.locked         = False
        self.time_up        = False
        self.alarm_playing  = False
        self.popups         = []
        self.chest_anim     = 0
        self.hide_player    = False

    def _parse_map(self):
        lmap  = LEVEL_MAPS[self.level - 1]
        speed = LEVEL_CFG[self.level]["fly_speed"]

        self.wall_pieces = create_wall_pieces(lmap, speed)

        self.collectibles = []
        self.chest_x = self.chest_y = 0
        self.start_x = self.start_y = 0
        self.wall_grid = [[False] * COLS for _ in range(ROWS)]
        self.snapped_grid = [[False] * COLS for _ in range(ROWS)]

        for row, line in enumerate(lmap):
            for col, ch in enumerate(line):
                px = col * TILE
                py = row * TILE
                if ch == '#':
                    self.wall_grid[row][col] = True
                elif ch in ITEM_MAP:
                    self.collectibles.append(ITEM_MAP[ch](px + 4, py + 4))
                elif ch == 'C':
                    self.chest_x = px
                    self.chest_y = py
                elif ch == 'P':
                    self.start_x = px
                    self.start_y = py

        for w in self.wall_pieces:
            if w.snapped:
                col = w.tx // TILE
                row = w.ty // TILE
                self.snapped_grid[row][col] = True

        self.keys_needed = sum(1 for c in self.collectibles if c.is_required)

    # ── Safe-Zone ─────────────────────────────────────────────────────────────
    def is_in_safe_zone(self):
        if self.locked:
            return False
        p = self.player
        px = p.x + TILE // 2
        py = p.y + TILE // 2
        cx = self.chest_x + TILE // 2
        cy = self.chest_y + TILE // 2
        distance = math.hypot(px - cx, py - cy)
        return not (distance > SAFE_ZONE_RADIUS)

    # ── Wand-Logik ────────────────────────────────────────────────────────────
    def update_walls(self):
        all_snapped = True
        for w in self.wall_pieces:
            was_snapped = w.snapped
            w.update()
            if w.snapped and not was_snapped:
                col = w.tx // TILE
                row = w.ty // TILE
                self.snapped_grid[row][col] = True
            if not w.snapped:
                all_snapped = False
        return all_snapped

    def wall_blocks(self, px, py):
        for cx, cy in [(px + 2, py + 2), (px + 13, py + 2),
                       (px + 2, py + 13), (px + 13, py + 13)]:
            col = cx // TILE
            row = cy // TILE
            if 0 >= 0 and row >= 0 and ROWS > row and COLS > col and col >= 0:
                if self.wall_grid[row][col]:
                    return True
        return False

    def snapped_wall_blocks(self, px, py):
        for cx, cy in [(px + 2, py + 2), (px + 13, py + 2),
                       (px + 2, py + 13), (px + 13, py + 13)]:
            col = cx // TILE
            row = cy // TILE
            if row >= 0 and ROWS > row and col >= 0 and COLS > col:
                if self.snapped_grid[row][col]:
                    return True
        return False

    def push_player_out_of_walls(self):
        p = self.player
        if not self.wall_blocks(p.x, p.y):
            return
        cx = (p.x + TILE // 2) // TILE
        cy = (p.y + TILE // 2) // TILE
        for radius in range(1, ROWS + COLS):
            for dr in range(-radius, radius + 1):
                for dc in range(-radius, radius + 1):
                    if abs(dr) != radius and abs(dc) != radius:
                        continue
                    row = cy + dr
                    col = cx + dc
                    if not (row >= 0 and ROWS > row and col >= 0 and COLS > col):
                        continue
                    if self.wall_grid[row][col]:
                        continue
                    nx = max(0, min(SCREEN_W - TILE, col * TILE))
                    ny = max(0, min(SCREEN_H - TILE, row * TILE))
                    if not self.wall_blocks(nx, ny):
                        p.x = nx
                        p.y = ny
                        return

    def push_player_out_of_snapped(self):
        p = self.player
        if not self.snapped_wall_blocks(p.x, p.y):
            return
        cx = (p.x + TILE // 2) // TILE
        cy = (p.y + TILE // 2) // TILE
        for radius in range(1, ROWS + COLS):
            for dr in range(-radius, radius + 1):
                for dc in range(-radius, radius + 1):
                    if abs(dr) != radius and abs(dc) != radius:
                        continue
                    row = cy + dr
                    col = cx + dc
                    if not (row >= 0 and ROWS > row and col >= 0 and COLS > col):
                        continue
                    if self.snapped_grid[row][col]:
                        continue
                    nx = max(0, min(SCREEN_W - TILE, col * TILE))
                    ny = max(0, min(SCREEN_H - TILE, row * TILE))
                    if not self.snapped_wall_blocks(nx, ny):
                        p.x = nx
                        p.y = ny
                        return

    # ── Item-Logik ────────────────────────────────────────────────────────────
    def collect_items(self):
        p = self.player
        for item in self.collectibles:
            if item.check_pickup(p):
                if self.has_res:
                    pyxel.play(CH_SFX, SND_KEY)
                if isinstance(item, INSTANT_ITEMS):
                    item.apply_effect(self)
                    if item.points > 0:
                        self.bonus_points += item.points
                else:
                    self.carried_items.append(item)
                self._spawn_popup(item)

    def check_deposit(self):
        p = self.player
        if not self.carried_items:
            return False
        if not p.collides_with(self.chest_x, self.chest_y, TILE, TILE):
            return False

        deposited_count = len(self.carried_items)
        if deposited_count >= 4:
            multiplier = COMBO_MULTIPLIER_MAX
        else:
            multiplier = COMBO_MULTIPLIERS.get(deposited_count, 1.0)

        keys_in_deposit = 0
        raw_points = 0
        for item in self.carried_items:
            if item.is_required:
                keys_in_deposit += 1
            raw_points += item.points

        combo_points = int(raw_points * multiplier)
        self.keys_deposited += keys_in_deposit
        self.bonus_points   += (combo_points - keys_in_deposit * 100)
        self.carried_items = []
        self.chest_anim = 40

        # Ablage-Sound (kurz)
        if self.has_res:
            pyxel.play(CH_SFX, SND_KEY)

        if multiplier > 1.0:
            popup_text = f"+{combo_points}P COMBO x{multiplier:.1f}!"
            popup_color = COL_GOLD
        else:
            popup_text = f"+{combo_points}P abgelegt!"
            popup_color = COL_YELLOW
        self.popups.append({
            "text": popup_text,
            "x": self.chest_x + 4,
            "y": self.chest_y,
            "timer": 60,
            "color": popup_color,
        })

        # Gewinn?
        if self.keys_deposited >= self.keys_needed:
            if self.has_res:
                pyxel.stop(CH_ALARM)
            self.alarm_playing = False
            self.total_keys  += self.keys_deposited
            self.total_bonus += self.bonus_points
            self.total_time  += self.round_time
            self._calc_score()
            self.hide_player = True
            self.change_state("celebrating")
            return True
        return False

    def drop_carried_items(self):
        if not self.carried_items:
            return
        dropped_count = len(self.carried_items)
        for item in self.carried_items:
            item.respawn()
        self.carried_items = []
        self.popups.append({
            "text": f"-{dropped_count} ITEMS VERLOREN!",
            "x": self.player.x,
            "y": self.player.y - 8,
            "timer": 60,
            "color": COL_RED,
        })

    def check_wall_collision(self):
        p = self.player
        if self.is_in_safe_zone():
            return False

        for w in self.wall_pieces:
            if w.snapped or not w.active:
                continue
            wx, wy, ww, wh = w.pixel_rect()
            if p.collides_with(wx, wy, ww, wh):
                if self.has_res:
                    pyxel.play(CH_SFX, SND_DIE)
                p.take_hit()
                self.drop_carried_items()
                self.change_state("ghost")
                return True
        return False

    def check_time_up(self):
        limit_frames = LEVEL_CFG[self.level]["time_limit"] * 30
        if self.round_time >= limit_frames and not self.time_up:
            self.time_up = True
            if self.has_res:
                pyxel.stop(CH_ALARM)
                pyxel.play(CH_SFX, SND_DIE)
            self.alarm_playing = False
            self.total_keys  += self.keys_deposited
            self.total_bonus += self.bonus_points
            self.total_time  += self.round_time
            self._calc_score()
            self.change_state("ghost")
            return True
        return False

    def check_alarm(self):
        if self.has_res and not self.alarm_playing and not self.time_up:
            limit_secs = LEVEL_CFG[self.level]["time_limit"]
            elapsed    = self.round_time // 30
            remaining  = limit_secs - elapsed
            if not (remaining > 10):
                self.alarm_playing = True
                pyxel.play(CH_ALARM, SND_ALARM, loop=True)

    def _calc_score(self):
        time_bonus = max(0, 300 - self.total_time // 30)
        penalty    = self.player.hit_penalty if self.player else 0
        self.score = (self.total_keys * 100
                      + self.total_bonus
                      + time_bonus
                      - penalty)

    def record_highscore(self):
        """Speichert aktuellen Score in die Session-Highscore-Liste."""
        entry = {
            "score": self.score,
            "level": self.level,
            "keys":  self.total_keys,
            "time":  self.total_time // 30,
        }
        self.highscores.append(entry)
        self.highscores.sort(key=lambda e: e["score"], reverse=True)
        self.highscores = self.highscores[:5]

        # Rang des gerade erspielten Scores ermitteln
        self.last_score_rank = -1
        for i, e in enumerate(self.highscores):
            if e is entry:
                self.last_score_rank = i
                break

    # ── Popup-System ──────────────────────────────────────────────────────────
    def _spawn_popup(self, item):
        if item.is_required:
            text = "KEY +Inventar"
            color = COL_YELLOW
        elif isinstance(item, Potion):
            text = "GEHEILT!"
            color = COL_PINK
        elif isinstance(item, Clock):
            text = f"+{Clock.BONUS_SECONDS}s ZEIT!"
            color = COL_GOLD
        elif isinstance(item, Armor):
            text = "SCHILD!"
            color = COL_LBLUE
        elif item.points > 0:
            text = f"+{item.points}P Inventar"
            color = COL_LBLUE
        else:
            text = item.name
            color = COL_WHITE
        self.popups.append({
            "text": text, "x": item.x, "y": item.y,
            "timer": 45, "color": color,
        })

    def _update_popups(self):
        for popup in self.popups:
            popup["timer"] -= 1
            popup["y"] -= 0.5
        self.popups = [p for p in self.popups if p["timer"] > 0]

    def _draw_popups(self):
        for popup in self.popups:
            if 15 > popup["timer"] and (popup["timer"] // 3) % 2 == 0:
                continue
            pyxel.text(int(popup["x"]) - 4, int(popup["y"]) - 8,
                       popup["text"], popup["color"])

    # ── Zeichnen ──────────────────────────────────────────────────────────────
    def draw_bg(self):
        """Zeichnet den Hintergrund: Vollbild von Bank 2 oder Fallback."""
        if self.has_res:
            # Ein einziger Aufruf fuer das gesamte 256x256 Hintergrundbild
            pyxel.blt(0, 0, 2, 0, 0, 256, 256)
        else:
            # Fallback ohne .pyxres: Schachbrett
            for r in range(ROWS):
                for c in range(COLS):
                    x = c * TILE
                    y = r * TILE
                    if (r + c) % 2 == 0:
                        pyxel.rect(x, y, TILE, TILE, 4)
                        pyxel.pset(x + 5, y + 5, 9)
                        pyxel.pset(x + 10, y + 3, 9)
                        pyxel.pset(x + 3, y + 11, 9)
                    else:
                        pyxel.rect(x, y, TILE, TILE, 9)
                        pyxel.pset(x + 4, y + 4, 4)
                        pyxel.pset(x + 9, y + 9, 4)
                        pyxel.pset(x + 12, y + 2, 4)

    def draw_game(self):
        self.draw_bg()
        if not self.locked:
            self._draw_safe_zone()
        self._draw_chest(self.chest_x, self.chest_y)
        for item in self.collectibles:
            item.update()
            item.draw(self.has_res)
        for w in self.wall_pieces:
            w.draw(self.has_res)
        # Spieler nur zeichnen wenn nicht ausgeblendet
        if not self.hide_player:
            self.player.draw()
            if not isinstance(self.current_state, GhostState):
                self.player.draw_shield()
        self._draw_hud()

    def _draw_safe_zone(self):
        cx = self.chest_x + TILE // 2
        cy = self.chest_y + TILE // 2
        pulse = int(math.sin(self.round_time * 0.05) * 2)
        radius = SAFE_ZONE_RADIUS + pulse

        if self.is_in_safe_zone():
            col = COL_LGREEN
        else:
            col = COL_DARK
        pyxel.circb(cx, cy, radius, col)

        if self.is_in_safe_zone():
            if (self.round_time // 8) % 2 == 0:
                pyxel.text(cx - 8, cy - SAFE_ZONE_RADIUS - 8, "SAFE", COL_LGREEN)

    def draw_progress_bar(self):
        total_inner = sum(1 for w in self.wall_pieces if not w.is_border)
        snapped_inner = sum(1 for w in self.wall_pieces
                            if not w.is_border and w.snapped)
        pct = snapped_inner / max(1, total_inner)
        bw = SCREEN_W - 60
        pyxel.rect(30, SCREEN_H - 10, bw, 6, COL_DARK)
        pyxel.rect(30, SCREEN_H - 10, int(bw * pct), 6, COL_LGREEN)
        pyxel.rectb(30, SCREEN_H - 10, bw, 6, COL_WHITE)

        active_count = sum(1 for w in self.wall_pieces
                           if not w.is_border and w.active and not w.snapped)
        pyxel.text(30, SCREEN_H - 20,
                   f"Hecken fliegen ein! AUSWEICHEN! ({active_count} aktiv)",
                   COL_RED)

    def _draw_hud(self):
        p = self.player

        carried_keys = sum(1 for it in self.carried_items if it.is_required)
        key_text = f"KEYS: {self.keys_deposited}/{self.keys_needed}"
        if carried_keys > 0:
            key_text += f" (+{carried_keys})"
        pyxel.text(4, 4, key_text, COL_YELLOW)

        if self.carried_items:
            inv_count = len(self.carried_items)
            pyxel.text(4, 28, f"TRAGE: {inv_count} Items", COL_WHITE)
            for i, item in enumerate(self.carried_items[:10]):
                ix = 60 + i * 8
                if item.is_required:
                    pyxel.rect(ix, 27, 5, 5, COL_YELLOW)
                else:
                    pyxel.rect(ix, 27, 5, 5, COL_LBLUE)
            if inv_count > 10:
                pyxel.text(140, 28, f"+{inv_count - 10}", COL_WHITE)

        if self.bonus_points > 0:
            pyxel.text(4, 36, f"SICHER: +{self.bonus_points}P", COL_LGREEN)

        limit_secs = LEVEL_CFG[self.level]["time_limit"]
        elapsed    = self.round_time // 30
        remaining  = max(0, limit_secs - elapsed)
        mins, secs = divmod(remaining, 60)
        time_str   = f"{mins}:{secs:02d}"
        if not (remaining > 10):
            tcol = COL_RED if (self.round_time // 8) % 2 == 0 else COL_YELLOW
        elif not (remaining > 30):
            tcol = COL_GOLD
        else:
            tcol = COL_WHITE
        pyxel.text(4, 12, f"ENDE IN: {time_str}", tcol)

        pyxel.text(SCREEN_W - 60, 4,
                   f"LVL {self.level} {LEVEL_CFG[self.level]['label']}", COL_YELLOW)

        if p.hit_penalty > 0:
            hits = p.hit_penalty // 50
            pyxel.text(4, 20, f"TREFFER: -{p.hit_penalty}P ({hits}x)", COL_RED)

        if p.is_invincible and (p.hit_cooldown // 5) % 2 == 0:
            pyxel.text(SCREEN_W - 60, 12, "SCHUTZ!", COL_LBLUE)

    def _draw_chest(self, x, y):
        if self.chest_anim > 0:
            self.chest_anim -= 1

        fill_pct = self.keys_deposited / max(1, self.keys_needed)

        # Stufe bestimmen
        if fill_pct >= CHEST_STAGE_3_PCT:
            stage = 3
        elif fill_pct >= CHEST_STAGE_2_PCT:
            stage = 2
        else:
            stage = 1

        # Sprite zeichnen
        if self.has_res:
            if stage == 1:
                pyxel.blt(x, y, 0, CHEST_S_U, CHEST_S_V, 16, 16, 0)
            elif stage == 2:
                pyxel.blt(x, y, 0, CHEST_M_U, CHEST_M_V, 16, 16, 0)
            else:
                pyxel.blt(x, y, 0, CHEST_L_U, CHEST_L_V, 16, 16, 0)
            # Glow bei Stufe 3
            if stage >= 3:
                pyxel.rectb(x - 1, y - 1, 18, 18, COL_YELLOW)
                pyxel.rectb(x - 2, y - 2, 20, 20, COL_GOLD)
        else:
            # Fallback ohne .pyxres
            if stage == 1:
                bx, by = x + 2, y + 4
                bw, bh = 12, 10
                pyxel.rect(bx, by + 3, bw, bh - 3, COL_BROWN)
                pyxel.rect(bx, by, bw, 4, 4)
                pyxel.rectb(bx, by, bw, bh, COL_DARK)
                pyxel.rect(bx + 4, by + 5, 3, 3, COL_YELLOW)
                pyxel.line(bx, by + 4, bx + bw - 1, by + 4, COL_GOLD)
            elif stage == 2:
                bx, by = x + 1, y + 2
                bw, bh = 14, 12
                pyxel.rect(bx, by + 4, bw, bh - 4, COL_BROWN)
                pyxel.rect(bx, by, bw, 5, 4)
                pyxel.rectb(bx, by, bw, bh, COL_DARK)
                pyxel.rect(bx + 5, by + 6, 4, 4, COL_YELLOW)
                pyxel.line(bx, by + 5, bx + bw - 1, by + 5, COL_GOLD)
                pyxel.rectb(bx - 1, by - 1, bw + 2, bh + 2, COL_GOLD)
            else:
                bx, by = x, y + 1
                bw, bh = 16, 14
                pyxel.rect(bx, by + 5, bw, bh - 5, COL_GOLD)
                pyxel.rect(bx, by, bw, 6, COL_BROWN)
                pyxel.rectb(bx, by, bw, bh, COL_DARK)
                pyxel.rect(bx + 5, by + 7, 5, 5, COL_YELLOW)
                pyxel.pset(bx + 7, by + 6, COL_YELLOW)
                pyxel.line(bx, by + 6, bx + bw - 1, by + 6, COL_GOLD)
                pyxel.rectb(bx - 1, by - 1, bw + 2, bh + 2, COL_YELLOW)
                pyxel.rectb(bx - 2, by - 2, bw + 4, bh + 4, COL_GOLD)
                t = self.round_time
                for i in range(4):
                    angle = t * 0.08 + i * 1.57
                    px = int(x + 8 + math.cos(angle) * 12)
                    py = int(y + 8 + math.sin(angle) * 10)
                    pyxel.pset(px, py, COL_YELLOW)

        # Ablage-Animation (Aufleuchten)
        if self.chest_anim > 0:
            flash = (self.chest_anim // 3) % 2
            if flash:
                pyxel.circb(x + 8, y + 8, 12 + (40 - self.chest_anim) // 4,
                            COL_YELLOW)
                pyxel.circb(x + 8, y + 8, 14 + (40 - self.chest_anim) // 4,
                            COL_GOLD)

        # Fortschrittsbalken unter der Truhe
        bar_y = y + 17
        bar_w = 16
        filled = int(bar_w * fill_pct)
        pyxel.rect(x, bar_y, bar_w, 2, COL_DARK)
        if filled > 0:
            bar_col = COL_YELLOW if 3 > stage else COL_LGREEN
            pyxel.rect(x, bar_y, filled, 2, bar_col)
        pyxel.rectb(x, bar_y, bar_w, 2, COL_GOLD)

        # "ABLEGEN!" Hinweis
        if self.carried_items:
            p = self.player
            dist_to_chest = math.hypot(p.x - x, p.y - y)
            if TILE * 3 > dist_to_chest:
                if (self.round_time // 10) % 2 == 0:
                    pyxel.text(x - 8, y - 10, "ABLEGEN!", COL_GOLD)

    def draw_player_sprite_static(self, x, y, direction):
        if self.has_res:
            pyxel.blt(x, y, 0, PLAYER_U_BASE + direction * 16,
                      PLAYER_V, 16, 16, 0)
        else:
            pyxel.rect(x + 4, y + 6, 8, 8, COL_RED)
            pyxel.rect(x + 5, y + 1, 6, 6, COL_SKIN)
            pyxel.pset(x + 7, y + 3, COL_DARK)
            pyxel.pset(x + 9, y + 3, COL_DARK)
            pyxel.rect(x + 4, y + 13, 3, 3, COL_DARK)
            pyxel.rect(x + 9, y + 13, 3, 3, COL_DARK)


if __name__ == "__main__":
    Game()