# -*- coding: utf-8 -*-
import logging
import random
from typing import List

import pygame

from game import features, tweening
from game.audioqueue import AudioSystem
from game.effects import MessageEffect, Effect, ThoughtEffect
from game.player import Player
from game.rendersystem import Camera, SpriteAnimation, Renderer

logger = logging.getLogger(__name__)


class Platform:
    def __init__(self, p1, p2):
        self.p1 = p1
        self.p2 = p2


class Ladder:
    def __init__(self, rect):
        self.rect: pygame.Rect = pygame.Rect(rect)


class CheckpointTrigger:
    def __init__(self, rect, name):
        self.rect: pygame.Rect = pygame.Rect(rect)
        self.name = name


class Block:
    def __init__(self, rect):
        self.rect: pygame.Rect = pygame.Rect(rect)
        self.can_push = False
        self.position = pygame.Vector3(*self.rect.center, 0)


class Box:
    def __init__(self, rect):
        self._rect = rect
        self.position = pygame.Vector3(*self._rect.center, 0)
        self.can_push = True
        self.state = None
        self.facing = pygame.Vector2(1, 1)
        self.prev_anim_state = None

    @property
    def rect(self):
        self._rect.center = self.position.xy
        return self._rect

    def move(self, delta_x, delta_y, level):
        self.position.x += delta_x
        self.position.y += delta_y

        w = self.rect.collideobjects(level.blocks)
        if w:
            self.position.x -= delta_x
            self.position.y -= delta_y
            return False
        w = self.rect.collideobjectsall(level.boxes)
        w.remove(self)
        if w:
            self.position.x -= delta_x
            self.position.y -= delta_y
            return False
        return True


class Spark:
    def __init__(self, rect, off_duration, on_duration):
        self.on_duration = on_duration
        self.off_duration = off_duration
        self._rect = rect
        self.position = pygame.Vector3(*self._rect.center, 0)
        self.state = True
        self.facing = pygame.Vector2(1, 1)
        self.prev_anim_state = None
        self.count_down = 0

    def update(self, dt, level):
        self.count_down -= dt
        if self.count_down <= 0:
            if self.state:
                self.state = False
                self.count_down = self.off_duration
            else:
                self.state = True
                self.count_down = self.on_duration
                level.audio_system.play(self.position, 0)


class CrouchZone:
    def __init__(self, rect):
        self.rect: pygame.Rect = pygame.Rect(rect)


class Highlight:
    STATE_SHOW = 2

    def __init__(self, position: pygame.Vector3):
        self.position = position.copy()
        self.state = 0

    @property
    def visible(self):
        return self.state == self.STATE_SHOW

    @visible.setter
    def visible(self, value):
        self.state = self.STATE_SHOW if value else 0


class _Interact:

    def __init__(self, position: pygame.Vector3):
        self.highlight = Highlight(position)

    def interact(self, triggered_by, level):
        pass


class Switch(_Interact):

    def __init__(self, rect, is_on, position: pygame.Vector3, name):
        super().__init__(position)
        self.name = name
        self.rect: pygame.Rect = pygame.Rect(rect)
        self.is_on = is_on
        self.position = pygame.Vector3(*self.rect.center, 0)
        self.state = is_on
        self.prev_anim_state = None
        self.facing = pygame.Vector2(1, 1)
        logger.warning("SWITCH: %s: initialized: %s", self.name, self.is_on)

    def interact(self, triggered_by, level):
        logger.warning("SWITCH: %s: %s -> %s", self.name, self.is_on, not self.is_on)
        self.is_on = not self.is_on
        self.state = self.is_on
        # if level:
        level.interact(self, triggered_by)
        level.audio_system.play(self.position, 2, self.name)


class Fan(Switch):
    def __init__(self, rect, is_on, position: pygame.Vector3, name):
        super().__init__(rect, is_on, position, name)
        self.rect: pygame.Rect = pygame.Rect(rect)

    def interact(self, triggered_by, level):
        self.is_on = not self.is_on
        self.state = self.is_on
        level.interact(self, triggered_by)
        if self.is_on:
            self.play_sound(level)
        else:
            level.audio_system.stop(1, self.name)

    def play_sound(self, level):
        level.audio_system.play(self.position, 1, self.name, -1)


class CheckPoint:

    def __init__(self, player: Player):
        self.position = player.position.copy()
        self.old_position = player.old_position.copy()
        self.velocity = player.velocity.copy()
        self.acceleration = player.acceleration.copy()
        self.mass = player.mass
        self.facing = player.facing.copy()
        self.state = player.state
        self.platform = player.platform
        self.ladder = player.ladder
        self.block = player.block
        self.fall_ignore_ladders = player.fall_ignore_ladders
        self.fall_ignore_platforms = player.fall_ignore_platforms
        self.name = 0

    def restore(self, player: Player):
        player.position.update(self.position)
        player.old_position.update(self.old_position)
        player.velocity.update(self.velocity)
        player.acceleration.update(self.acceleration)
        player.mass = self.mass
        player.facing.update(self.facing)
        player.state = self.state
        player.platform = self.platform
        player.ladder = None
        player.block = self.block
        player.fall_ignore_ladders = self.fall_ignore_ladders
        player.fall_ignore_platforms = self.fall_ignore_platforms

    def store(self, player: Player, check_point):
        name = check_point.name
        if name != self.name:
            logger.info("storing checkpoint %s", name)
        position = pygame.Vector3(*check_point.rect.center, 0)
        self.position.update(position)
        self.old_position.update(position)
        zero = pygame.Vector3(0, 0, 0)
        self.velocity.update(zero)
        self.acceleration.update(zero)
        self.mass = player.mass
        self.facing.update(player.facing)
        self.state = Player.STATE_FALL
        self.platform = player.platform
        self.ladder = player.ladder
        self.block = player.block
        self.fall_ignore_ladders = player.fall_ignore_ladders
        self.fall_ignore_platforms = player.fall_ignore_platforms
        self.name = name


def sign(x):
    if x > 0:
        return 1
    elif x < 0:
        return -1
    else:
        return 0


# todo unstable ground (run fall)
# todo animations


class Level:
    def __init__(self):
        self.blow_messages = [
            "This path blows me away...",
            "A truly mind blowing fall.",
            "I can't fly...",
            "The wind is to strong.",
        ]
        self.electro_messages = [
            "This is the way to get electrocuted.",
            "What an electrifying path.",
            "This will ruin my hair style.",
        ]
        self.help_message = """
        __

        Help
        ----
        Move: a, s, d, w
        Run: shift
        Jump: space
        Use: e

        F1 this help







        __            """

        self.effects: List[Effect] = []
        self.audio_system: AudioSystem = AudioSystem()
        self.level_name = None
        self.size = (0, 0)

        self.done = False

        self.cam = Camera()
        self.cam.zoom = 1
        self.cam.look_at(pygame.Vector3(87 * 32, 33 * 32, 0) / self.cam.zoom)
        # self.cam.look_at(pygame.Vector3(124 * 32, 60 * 32, 0) / self.cam.zoom)
        self.cam.look_at(pygame.Vector3(7 * 32, 59 * 32, 0) / self.cam.zoom)

        self.sprites: List[SpriteAnimation] = []
        self.renderer = Renderer()
        self.renderer.add_sprites(self.sprites)

        self.focus = Player()
        self.focus.position.update(self.cam.position)

        self.platforms: List[Platform] = []
        self.ladders: List[Ladder] = []
        self.blocks: List[Block] = []
        self.boxes: List[Box] = []
        self.switches: List[Switch] = []
        self.crouch_zones: List[CrouchZone] = []
        self.checkpoint_triggers = []
        self.sparks: List[Spark] = []
        self.fans: List[Fan] = []

        self.cell_rect = self.cam._viewport.copy()
        self.cell_current = pygame.Vector2(2, 0)
        self.cell_previous = self.cell_current.copy()

        self.cell_rect.topleft = (self.cell_current.x * self.cell_rect.w, self.cell_current.y * self.cell_rect.h)
        self.cam.position.update(pygame.Vector3(*self.cell_rect.center, 0))

        self.check_point: CheckPoint = None

    def update(self, dt):
        tweening.tweener.update(dt)

        for e in reversed(self.effects):
            if not e.update(dt):
                self.effects.remove(e)

        pressed = pygame.key.get_pressed()
        if features.debug_cam:
            dx = pressed[pygame.K_l] - pressed[pygame.K_j]
            dy = pressed[pygame.K_k] - pressed[pygame.K_i]
            vel = 20
            self.cam.position += pygame.Vector3(dx * vel, dy * vel, 0)
        else:
            self.update_cam_position()

        if pygame.key.get_just_pressed()[pygame.K_F1]:
            self.effects.append(MessageEffect(self.help_message, 10))

        cp = self.focus.rect.collideobjects(self.checkpoint_triggers)
        if cp:
            self.store_checkpoint(self.focus, cp)

        sw = self.focus.rect.collideobjects(self.switches)
        if sw:
            self.focus.use_obj = sw
        else:
            self.focus.use_obj = None

        r_colliderect = self.focus.rect.inflate(0, -50).colliderect
        for sp in self.sparks:
            sp.update(dt, self)
            if sp.state is True and r_colliderect(sp._rect):
                message = random.choice(self.electro_messages)
                self.focus.die(message)

        for f in self.fans:
            if f.state is True and r_colliderect(f.rect.inflate(-64, 0)):
                message = random.choice(self.blow_messages)
                self.focus.die(message)

        # update position and facing
        dx = pressed[pygame.K_d] - pressed[pygame.K_a]
        dy = pressed[pygame.K_s] - pressed[pygame.K_w]
        mods = pygame.key.get_mods()
        shift = bool(mods & pygame.KMOD_SHIFT)
        just_pressed = pygame.key.get_just_pressed()
        space = just_pressed[pygame.K_SPACE]
        use_key = just_pressed[pygame.K_e]

        # logger.debug("space: %s, shift: %s", space, shift)
        self.focus.move_in_direction(dx, dy, shift, space, use_key)
        self.focus.update(dt, self)

        self.audio_system.update(self.focus.position)

        if features.cheats_enable:
            if just_pressed[pygame.K_PAGEDOWN]:
                for cpt in sorted(self.checkpoint_triggers, key=lambda x: x.name):
                    if cpt.name > self.check_point.name:
                        self.focus.state = Player.STATE_FALL
                        self.focus.position.update(*cpt.rect.center, 0)
                        self.focus.old_position.update(self.focus.position)
                        self.focus.velocity.update(0, 0, 0)
                        self.update_cam_position()
                        logger.info("jumping to %s at %s  ply: %s  col: %s ply.pos: %s", cpt.name, cpt.rect,
                                    self.focus.rect, self.focus.rect.colliderect(cpt.rect), self.focus.position)
                        self.effects.append(MessageEffect(f"Checkpoint {cpt.name}", 5))
                        break
            if just_pressed[pygame.K_PAGEUP]:
                for cpt in reversed(sorted(self.checkpoint_triggers, key=lambda x: x.name)):
                    if cpt.name < self.check_point.name:
                        logger.info("jumping to %s at %s", cpt.name, cpt.rect.center)
                        self.focus.state = Player.STATE_FALL
                        self.focus.position.update(*cpt.rect.center, 0)
                        self.focus.old_position.update(self.focus.position)
                        self.focus.velocity.update(0, 0, 0)
                        self.update_cam_position()
                        logger.info("jumping to %s at %s  ply: %s  col: %s ply.pos: %s", cpt.name, cpt.rect,
                                    self.focus.rect, self.focus.rect.colliderect(cpt.rect), self.focus.position)
                        self.effects.append(MessageEffect(f"Checkpoint {cpt.name}", 5))
                        break

    def update_cam_position(self):
        new_cell = pygame.Vector2(*self.focus.rect.center)
        new_cell.x //= self.cell_rect.w
        new_cell.y //= self.cell_rect.h
        if new_cell != self.cell_current:
            if new_cell.x > 3:
                # wrap to left
                new_cell.x = 0
                # self.focus.position.x = -self.cell_rect.w + self.focus.position.x % self.cell_rect.w
                self.focus.position.x -= self.cell_rect.w * 4
            if new_cell.x < 0:
                # wrap to the right
                new_cell.x = 3
                self.focus.position.x += self.cell_rect.w * 4

            self.cell_previous.update(self.cell_current)
            self.cell_current.update(new_cell)
            logger.debug("prev %s-> current %s facing: %s", str(self.cell_previous),
                         str(self.cell_current),
                         str(self.focus.facing))

            self.cell_rect.topleft = (self.cell_current.x * self.cell_rect.w,
                                      self.cell_current.y * self.cell_rect.h)
            self.cam.position.update(pygame.Vector3(*self.cell_rect.center, 0))

    def draw(self, screen: pygame.Surface, fdt):

        screen.fill((50, 50, 60))

        self.renderer.render(screen, self.cam, fdt)

        for e in self.effects:
            e.draw(screen, self.cam, fdt)

        if features.debug_draw:
            for platform in self.platforms:
                self.cam.draw_debug_line(platform.p1, platform.p2, screen, (200, 200, 200), 2)

            for ladder in self.ladders:
                self.cam.draw_debug_rect(ladder.rect, screen, (100, 100, 200))

            self.cam.draw_debug_rect(self.focus.rect, screen, (200, 100, 100, 100))

            # pygame.draw.rect(screen, (255, 255, 0), pygame.Rect(800, 50, 32, 32))

            for cp in self.checkpoint_triggers:
                self.cam.draw_debug_rect(cp.rect, screen, (165, 255, 128), 5)

            p2 = self.focus.position + pygame.Vector3(*self.focus.facing, 0) * 20
            self.cam.draw_debug_line(self.focus.position, p2, screen, (0, 40, 80), 2)

            # todo draw state of switches and fans
            font = pygame.font.Font(None, 20)
            for s in self.switches:
                p = self.cam.world_to_screen(s.position)
                label = font.render(str(s.is_on), True, (255, 255, 0))
                screen.blit(label, p)

            screen.set_at(self.cam.world_to_screen(self.focus.position), (255, 255, 255))

        pygame.display.flip()

    def initialize(self):
        self.renderer.add_sprites(self.sprites)
        self.update_cam_position()
        self.check_point = CheckPoint(self.focus)
        self.audio_system.load()
        for f in self.fans:
            if f.is_on:
                f.play_sound(self)

        self.audio_system.play_no_position(3, -1)

    def restore_last_checkpoint(self, message):
        logger.warning("death message: %s", message)
        self.check_point.restore(self.focus)
        start_pos = self.focus.position.copy()
        start_pos.x = start_pos.x // self.cell_rect.w * self.cell_rect.w + self.cell_rect.w / 2
        start_pos.y = start_pos.y // self.cell_rect.h * self.cell_rect.h + self.cell_rect.h / 2
        end_pos = self.focus.position.copy()
        end_pos.y -= 4 * 32
        self.effects.append(ThoughtEffect(start_pos, end_pos, 2))
        self.effects.append(MessageEffect(message, 5))

    def store_checkpoint(self, player: Player, check_point):
        self.check_point.store(player, check_point)

    def interact(self, trigger, triggered_by):
        logger.info("trigger %s fired (triggered by %s)", trigger.name, triggered_by)
        if not isinstance(triggered_by, list):
            triggered_by = [triggered_by]

        if trigger in triggered_by:
            return

        if trigger.name == "switch1":
            for f in self.fans:
                if f.name == 'fan4' or f.name == 'fan5':
                    if f not in triggered_by:
                        f.interact([trigger] + triggered_by, self)
        elif trigger.name == "switch2":
            switch3 = [s for s in self.switches if s.name == 'switch3'][0]
            self.fan6_or(trigger, switch3, triggered_by)
        elif trigger.name == "switch3":
            switch2 = [s for s in self.switches if s.name == 'switch2'][0]
            self.fan6_or(switch2, trigger, triggered_by)
        elif trigger.name == "switch4":
            for f in self.fans:
                if f.name in ['fan9', 'fan10']:
                    f.interact([trigger] + triggered_by, self)
        elif trigger.name == "switch5":
            for f in self.fans:
                if f.name in ['fan8', 'fan9']:
                    f.interact([trigger] + triggered_by, self)
        elif trigger.name == "switch6":
            for f in self.fans:
                if f.name in ['fan9']:
                    f.interact([trigger] + triggered_by, self)
                    break
        elif trigger.name == "switch7":
            self.done = True

    def fan6_or(self, switch2: Switch, switch3: Switch, triggered_by: list):
        fan6 = [f for f in self.fans if f.name == 'fan6'][0]
        if switch2.is_on or switch3.is_on:
            if not fan6.is_on:
                fan6.interact([switch2] + triggered_by, self)
        else:
            if fan6.is_on:
                fan6.interact([switch2] + triggered_by, self)
