import pygame
from coords import Coords, Direction
from common import DrawableGameObject, EventHandler


def point_at(coords: Coords) -> tuple[pygame.Rect, pygame.Mask]:
    rect = pygame.Rect(coords.x, coords.y, 1, 1)
    mask = pygame.Mask((1, 1), fill=True)
    return rect, mask


def collide_with_walls(coords: Coords, walls: DrawableGameObject) -> bool:
    rect, mask = point_at(coords)
    return walls.check_collision(rect, mask)


def is_valid_point_to_move(coords: Coords, walls: DrawableGameObject) -> bool:
    return not collide_with_walls(coords, walls)


class Hero(DrawableGameObject, EventHandler):
    """объект главного героя"""

    def __init__(
        self,
        coords: Coords,
        parent: DrawableGameObject,
        assets: dict | None = None,
    ):
        super().__init__(coords, parent, assets)
        self._surface = pygame.image.load(self.assets["ghost.png"])
        self.rect = self.surface.get_rect()
        sf = Coords(0.8, 0.8)
        self._surface, self.rect = self.scene.scale_box(self.surface, self.rect, sf)
        self.rect.topleft = coords
        self.active = True
        self.looking_right = False
        self._speed = 1
        self.direction = Direction.RIGHT
        self.mouse_active = False
        self.auto_move_target = None

        # картинка изначально влево, а надо бы начинать со взгляда вправо
        self.flip()

    def draw(self):
        self.auto_move()
        self.parent.surface.blit(self.surface, self.rect)

    def _check_collision(self, coords):
        """Проверка пересечения со стенами"""
        new_rect = self.rect.copy()
        new_rect.topleft = coords
        return self.scene.walls.check_collision(new_rect, self.mask)

    @property
    def speed(self):
        return max(self._speed, 1)

    @speed.setter
    def speed(self, value):
        self._speed = min(value, 15)

    def _reduce_step(self, coords):
        """Уменьшение шага движения, с целью подойти вплотную к стене"""
        delta = coords - self.coords
        dx, dy = 0, 0
        if abs(delta.x) > 1:
            dx = 1 * (delta.x < 0 or -1)
        if abs(delta.y) > 1:
            dy = 1 * (delta.y < 0 or -1)
        return coords + Coords(dx, dy)

    def set_coords(self, coords: Coords):
        # проверка колизии
        has_collision = self._check_collision(coords)
        if not has_collision:
            super().set_coords(coords)
            self.scene.coins.collect(self)
            return

        self.auto_move_target = None

        # уменьшение шага
        while has_collision and coords != self.coords:
            coords_new = self._reduce_step(coords)
            if coords_new == coords:
                return  # не могу уменьшить шаг
            coords = coords_new
            has_collision = self._check_collision(coords)
        super().set_coords(coords)
        self.scene.coins.collect(self)

    def flip(self):
        self.looking_right = not self.looking_right
        self._surface = pygame.transform.flip(self.surface, flip_x=True, flip_y=False)

    def update_direction(self, direction: Direction):
        if direction in (Direction.LEFT, Direction.RIGHT):
            going_right = direction == Direction.RIGHT
            if self.looking_right != going_right:
                self.flip()

        if direction != self.direction:
            self.speed = 0
            self.direction = direction
        else:
            self.speed += 1

    def move(self, direction: Direction, step: int = 1):
        self.update_direction(direction)
        self.coords += direction.as_coords() * step * self.speed // 3

    def auto_move(self):
        if self.auto_move_target is None:
            return

        rect, mask = point_at(self.auto_move_target)
        if self.overlap(rect, mask):
            self.auto_move_target = None
            return

        direction = Direction.from_coords(self.auto_move_target - self.coords)
        if direction:
            self.move(direction, step=1)
        else:
            self.auto_move_target = None

    def handle_mouse_event(self, event: pygame.event.Event):
        if event.type not in (
            pygame.MOUSEBUTTONDOWN,
            pygame.MOUSEBUTTONUP,
            pygame.MOUSEMOTION,
        ):
            return
        match event.type:
            case pygame.MOUSEBUTTONDOWN:
                self.mouse_active = self.rect.collidepoint(event.pos)
                if not self.mouse_active and is_valid_point_to_move(
                    Coords(*event.pos), self.scene.walls
                ):
                    self.auto_move_target = Coords(*event.pos)
                else:
                    self.auto_move_target = None
            case pygame.MOUSEBUTTONUP:
                self.mouse_active = False
            case pygame.MOUSEMOTION if self.mouse_active:
                rel = Coords(*event.rel)
                direction = Direction.from_coords(rel)
                if direction:
                    self.update_direction(direction)
                    self.coords += rel

    def handle_event(self, event: pygame.event.Event):
        if not self.active:
            return
        self.handle_mouse_event(event)

    def handle_keys(self, keys_pressed):
        if not self.active:
            return

        wide, short = 3, 1
        if keys_pressed[pygame.K_UP]:
            self.move(Direction.UP, wide)
        if keys_pressed[pygame.K_DOWN]:
            self.move(Direction.DOWN, wide)
        if keys_pressed[pygame.K_LEFT]:
            self.move(Direction.LEFT, wide)
        if keys_pressed[pygame.K_RIGHT]:
            self.move(Direction.RIGHT, wide)
        if keys_pressed[pygame.K_w]:
            self.move(Direction.UP, short)
        if keys_pressed[pygame.K_s]:
            self.move(Direction.DOWN, short)
        if keys_pressed[pygame.K_a]:
            self.move(Direction.LEFT, short)
        if keys_pressed[pygame.K_d]:
            self.move(Direction.RIGHT, short)