from math import pi, cos, sin
from random import randint

from direct.interval.FunctionInterval import Func
from direct.interval.Interval import Interval
from direct.interval.LerpInterval import LerpScaleInterval
from direct.interval.MetaInterval import Sequence
from direct.particles.ParticleEffect import ParticleEffect
from direct.showbase import DirectObject
from panda3d.core import Vec3, CollisionTraverser, CollisionHandlerQueue, CollisionSphere, CollisionNode, GeomNode, \
    CollisionPlane, Plane, Point3, CollisionRay, CardMaker, Texture, Vec3, Vec4, NodePath, TransparencyAttrib, Point2

import sound_manager
from helper import base, pedestrian_colliders, BUILDING_MASK, pedestrian_spawnpoints, PLANE_MASK, PERSON_CLICK_MASK, \
    building_colliders, BUILDING_CLICK_MASK, PAUSED, GAME_OVER_LISTENERS
from people.person import Person
from sound_manager import audio3d

DEBUG = False
SPEED = 2
RING_RADIUS = 4
MIN_RADIUS = 0
RING_ANIM_TIME = 0.2
CAM_ROT_SPEED = 120
MAX_HEALTH = 150
HEALTH_REGEN = 1/10


class Player (Person):
    def __init__(self):
        self.alive = True
        self.HP = MAX_HEALTH
        self.ctrav = CollisionTraverser()
        self.chandler = CollisionHandlerQueue()
        if not pedestrian_spawnpoints:
            print("no ped spawn")
            pos = Vec3(5, 5, 0)
        else:
            pos = pedestrian_spawnpoints[randint(0, len(pedestrian_spawnpoints)-1)]
            pos.z = 0
        super().__init__(pos)
        self.push_collider.node().set_from_collide_mask(BUILDING_MASK)
        self.push_collider.node().set_into_collide_mask(self.PERSON_MASK)
        self.push_collider.node().set_tag('ally', 't')
        pedestrian_colliders[self.push_collider] = self
        self.accept('%s attacked' % self.name, self.attacked)


        self.speak_ring = base.loader.load_model('ring.egg')
        self.speak_ring.reparent_to(self.model)
        ring_colider = self.speak_ring.find("**/+CollisionNode")
        ring_colider.node().set_from_collide_mask(Person.PEDESTRIAN_ONLY_MASK)
        ring_colider.node().set_into_collide_mask(Person.NO_MASK)
        self.speak_ring.set_scale(MIN_RADIUS)

        self.speak_ring_animation = None

        if DEBUG:
            self.ctrav.show_collisions(base.render)

        self.ally_pos = None
        self._ally_target = None
        self.allies = set()

        # region init clicking on things
        self.ground_plane = base.render.attach_new_node(CollisionNode('ground plane'))
        self.ground_plane.node().add_solid( CollisionPlane(Plane(Vec3(0, 0, 1), Point3(0, 0, 0))))
        self.ground_plane.set_collide_mask(PLANE_MASK)
        self.click_handler = CollisionHandlerQueue()
        self.click_ray = base.camera.attach_new_node(CollisionNode('mouseRay'))
        self.click_ray.node().set_from_collide_mask(BUILDING_CLICK_MASK | PLANE_MASK | PERSON_CLICK_MASK)
        self.click_ray.node().set_into_collide_mask(0)
        self.click_col_ray = CollisionRay()
        self.click_ray.node().add_solid(self.click_col_ray)
        self.ctrav.add_collider(self.click_ray, self.click_handler)
        # endregion
        # region ring animation
        self.speak_ring_extend_anim = LerpScaleInterval(
            self.speak_ring, RING_ANIM_TIME, blendType='easeOut', scale=RING_RADIUS, startScale=MIN_RADIUS)
        self.speak_ring_retract_anim = LerpScaleInterval(
            self.speak_ring, RING_ANIM_TIME, blendType='easeOut', scale=MIN_RADIUS, startScale=RING_RADIUS)
        # endregion
        self.audio_listener = self.model.attach_new_node('listener')
        self.audio_listener.set_h(180)
        audio3d.attach_listener(self.audio_listener)

        self.ctrav.add_collider(ring_colider, self.chandler)
        self._extends = 0
        self._recall = False
        base.accept('mouse1', self.extend_ring)
        base.accept('mouse1-up', self.retract_ring)
        base.accept('mouse3', self.order)
        base.accept('mouse3-up', self._end_recall)
        self.cam_rot = 0
        self._last_mouse_pos = Point2(0, 0)

    def attacked(self, event):
        self.HP -= 1

    def extend_ring(self, retract=False):
        if not retract:
            self._extends += 1
        if retract:
            self._extends -= 1
            if self._extends > 0:
                return
            if self._extends < 0:
                self._extends = 0
        old_interval = self.speak_ring_animation
        self.speak_ring_animation = self.speak_ring_retract_anim if retract else self.speak_ring_extend_anim
        self.speak_ring_animation.set_t(0)
        if old_interval and not old_interval.is_stopped():
            self.speak_ring_animation.set_t(RING_ANIM_TIME - old_interval.get_t())
            old_interval.finish()
        self.speak_ring_animation.resume()

    def retract_ring(self):
        self.extend_ring(True)

    def _end_recall(self):
        if self._recall:
            self.extend_ring(True)
        self._recall = False

    @property
    def ally_target(self):
        return self._ally_target

    @ally_target.setter
    def ally_target(self, target):
        self._ally_target = target
        if target is None:
            target_marker.detach_node()
        else:
            target_marker.reparent_to(target.model)

    def order(self):
        # get mouse position
        if not base.mouseWatcherNode.hasMouse():
            return
        mpos = base.mouseWatcherNode.getMouse()
        self.click_col_ray.set_from_lens(base.camNode, mpos.getX(), mpos.getY())
        self.ctrav.traverse(base.render)
        if self.click_handler.get_num_entries() <= 0:
            return
        self.click_handler.sort_entries()
        clicked_entry = self.click_handler.get_entry(0)
        clicked_col = clicked_entry.get_into_node_path()

        self.ally_pos = None
        self.ally_target = None

        if clicked_col == self.click_collider:
            self._recall = True
            self.extend_ring(False)
        #elif clicked_col == self.ground_plane:
        #    self.ally_pos = clicked_entry.get_surface_point(base.render)
        elif pedestrian_colliders.get(clicked_col, False):
            self.attack(clicked_col)
        elif building_colliders.get(clicked_col, False):
            self.attack_building(building_colliders[clicked_col])

    def attack(self, clicked_col):
        pedestrian = pedestrian_colliders[clicked_col]
        if pedestrian.relation != pedestrian.Relation.ALLY:
            target_marker.reparent_to(pedestrian.model)
            target_marker.set_pos(0, 0, 1)
            self.ally_target = pedestrian

    def attack_building(self, building):
        target_marker.reparent_to(building.model)
        target_marker.set_pos(base.render, building.center)
        self.ally_target = building

    def update(self):
        if not PAUSED[0]:
            self.HP += globalClock.getDt() * HEALTH_REGEN
            if self.HP > MAX_HEALTH:
                self.HP = MAX_HEALTH
            elif self.HP <= 0:
                self.die()
        is_down = base.mouseWatcherNode.is_button_down
        if base.mouseWatcherNode.hasMouse():
            mpos = base.mouseWatcherNode.getMouse()
        else:
            mpos = self._last_mouse_pos

        target_marker.look_at(base.camera)

        # region movement
        self.velocity = Vec3(0, 0, 0)
        if is_down('w'):
            self.velocity += Vec3(0, 1, 0)
        if is_down('s'):
            self.velocity += Vec3(0, -1, 0)
        if is_down('a'):
            self.velocity += Vec3(-1, 0, 0)
        if is_down('d'):
            self.velocity += Vec3(1, 0, 0)
        if self.velocity != Vec3(0, 0, 0) and not self.jump.is_playing():
            self.jump.start()
        self.velocity = base.camera.get_quat().xform(self.velocity)
        self.velocity.z = 0
        self.velocity.normalize()
        self.velocity *= SPEED
        # endregion

        self.speak_ring.set_p(base.render, 0)

        shouting = is_down('mouse1')
        if shouting or self._recall:
            # do shouting
            self.ctrav.traverse(base.render)
            for entry in self.chandler.getEntries():
                pedestrian = pedestrian_colliders[entry.get_into_node_path()]
                if shouting and pedestrian.relation == pedestrian.Relation.NEUTRAL:
                    pedestrian.test_relation()
                if self._recall:
                    if pedestrian.relation == pedestrian.Relation.ALLY:
                        pedestrian.retreat()

        sound_manager.shout(shouting)

        if is_down('mouse2'):  # rot
            mdiff = mpos.getX() - self._last_mouse_pos.getX()
            self.cam_rot += mdiff * CAM_ROT_SPEED
            while self.cam_rot > 360:
                self.cam_rot -= 360
            while self.cam_rot < 0:
                self.cam_rot += 360
        rotrad = self.cam_rot/180 * pi

        self.zoom = 5
        base.camera.set_pos(self.pos + Vec3(cos(rotrad), sin(rotrad), 1)*self.zoom)
        base.camera.look_at(self.pos)
        self._last_mouse_pos = Point2(mpos)
        return super().update()

    def die(self):
        self.alive = False
        for l in GAME_OVER_LISTENERS:
            l()

instance = Player()


def gen_target_card():
    cm = CardMaker('target cm')
    tex = base.loader.load_texture('img/target.png')
    assert isinstance(tex, Texture)
    tex.set_border_color(Vec4(0, 0, 1, 1))
    tex.set_wrap_u(Texture.WM_border_color)
    tex.set_wrap_v(Texture.WM_border_color)
    tex.set_clear_color(Vec4(0, 1, 0, 1))

    cm.set_frame(-tex.get_orig_file_x_size(), tex.get_orig_file_x_size(),
                 -tex.get_orig_file_y_size(), tex.get_orig_file_y_size())

    card = NodePath(cm.generate())
    card.set_texture(tex)
    card.set_transparency(TransparencyAttrib.M_alpha)
    card.set_scale(1/1000)
    card.set_color(Vec4(1, 1, 1, 1))
    card.set_h(180)
    card.set_bin("fixed", 0)
    card.set_depth_test(False)
    card.set_depth_write(False)
    card.set_light_off()
    marker = NodePath('marker')
    card.reparent_to(marker)
    return marker


target_marker = gen_target_card()
