import random
from queue import PriorityQueue

from direct.interval.LerpInterval import *

from entity.entity import Entity
from global_stuff import base, collisions, safe, merge_sfx
from world import instance as world


class Ghost(Entity):
    model_name = 'creatures/ghost'

    def __init__(self, urn):
        self.urn = urn
        super().__init__(urn.tile, urn.color)
        self.model.reparent_to(base.render)
        base.task_mgr.add(self.woosh, 'move ghost'+str(id(self)))

        self._path = []
        self.valid_shrine_tiles = set()
        self.calc_valid_shrines()

    def __del__(self):
        base.task_mgr.remove('move_ghost'+str(id(self)))
        self.urn.ghosts.discard(self)
        super().__del__()

    def die(self):
        merge_sfx.play()
        self.__del__()


    def calc_valid_shrines(self):
        self.valid_shrine_tiles = set((s.tile for s in world().shrines if s.color == self.color))


    @property
    def tile(self):
        if not self.model:
            return None
        x, y, z = self.model.get_pos()
        x = round(x)
        y = round(world().height - y + 1)
        return world()[x][y]

    @tile.setter
    def tile(self, t):
        if self.model:
            self.model.set_pos(t.model.get_pos())

    def is_impassable(self, tile):
        if tile in self.valid_shrine_tiles:
            return True
        return tile.impassable or tile.color == self.color or \
            any((e.tile == tile for e in world().urns + world().shrines if e.color != self.color))

    def woosh(self, task):
        delay = 1

        if not self.model:
            return

        if self.tile in self.valid_shrine_tiles:
            base.messenger.send('ghost_enshrined', [self])
            world().add_feng_shui(1, self.tile.model.get_pos())
            self.__del__()
            return task.done

        for g in set(world().ghosts):
            if g.tile == self.tile:
                if self.color == g.color:
                    continue
                if (self.color, g.color) in safe:
                    continue
                g.die()
                if (self.color, g.color) in collisions.keys():
                    self.color = collisions[(self.color, g.color)]
                else:
                    self.color = '?'
                    world().add_feng_shui(-1, self.model.get_pos())
                base.messenger.send('ghost_collision', [self, g])
                self.calc_valid_shrines()
                self._path = []

        if len(self._path) == 0 or any(self.is_impassable(t) for t in self._path):
            self.calculate_path()

        if len(self._path) == 0:
            valid_neighbors = list((n for n in self.tile.cross_neighbors if not self.is_impassable(n)))
            if len(valid_neighbors) == 0:
                target = self.tile
            else:
                target = random.choice(valid_neighbors)
        else:
            target = self._path.pop()

        target_pos = target.model.get_pos()
        target_pos.z += random.random()*0.4 - 0.2
        LerpPosInterval(self.model, delay, target_pos, blendType='easeInOut').start()
        starting_hpr = self.model.get_hpr()
        self.model.look_at(target.model)
        target_hpr = self.model.get_hpr() + random.random()*60 - 30
        LerpHprInterval(self.model, delay, target_hpr, starting_hpr, blendType='easeOut').start()

        task.delay_time = delay
        return task.again

    def calculate_path(self):
        self._path = []
        if len(self.valid_shrine_tiles) == 0:
            return

        x, y, z = self.model.get_pos()
        x = round(x)
        y = round(world().height - y + 1)

        if len(self.valid_shrine_tiles) == 1:
            shrine = list(self.valid_shrine_tiles)[0]

            def heuristic(t):
                return abs(t.x - shrine.x) + abs(t.y - shrine.y)
        else:
            def heuristic(t):
                return sum((abs(t.x - s.x) + abs(t.y - s.y) for s in self.valid_shrine_tiles))

        open_queue = PriorityQueue()

        start = world()[x][y]

        open_queue.put((0, 0, start))

        parent = {}
        cost = {}

        parent[start] = None
        cost[start] = 0

        node = None

        counter = 0

        while not open_queue.empty():
            node = open_queue.get()[2]
            if node in self.valid_shrine_tiles:
                break
            if self.is_impassable(node) or node.color == self.color:
                continue
            for next in node.cross_neighbors:
                new_cost = cost[node] + 1
                if next not in cost.keys() or new_cost < cost[next]:
                    cost[next] = new_cost
                    priority = new_cost + heuristic(next)
                    counter += 1
                    open_queue.put((priority, counter, next))
                    parent[next] = node

        if node not in self.valid_shrine_tiles:
            return

        while parent[node]:
            self._path.append(node)
            node = parent[node]
