from src.gameplay.typings import Context
import src.gameplay.utils as gameplayUtils
from time import monotonic
from src.repositories.radar.typings import Coordinate
from src.repositories.battleList.core import hasSkull
from src.repositories.battleList.extractors import getContent
from src.gameplay.targeting import getFirstCreatureOnPath
from ...typings import Context
from ..waypoint import generateFloorWalkpoints
from .common.vector import VectorTask
from .walk import WalkTask
from .attackMonstersBox import AttackMonstersBoxTask
from .lootMonstersBox import LootMonstersBoxTask
from .resetSpellIndex import ResetSpellIndexTask
from .clickInClosestCreature import ClickInClosestCreatureTask
from .walkToTargetCreature import WalkToTargetCreatureTask

class WalkToCoordinateTask(VectorTask):
    def __init__(self, coordinate: Coordinate, passinho=False, tolerance=0):
        super().__init__()
        self.name = 'walkToCoordinate'
        self.coordinate = coordinate
        self.passinho = passinho
        self.tolerance = tolerance
        self.isTrapped = False
        self.blockingCreature = None
        self.blockedCoordinates = []
        self.nextPathRetryAt = 0

    def blockWalkpoint(self, coordinate: Coordinate):
        if coordinate not in self.blockedCoordinates:
            self.blockedCoordinates.append(coordinate)
        self.tasks = []
        self.currentTaskIndex = 0

    def shouldRestartAfterAllChildrensComplete(self, context: Context) -> bool:
        if self.isTrapped == True:
            return True
        if len(self.tasks) == 0:
            return True
        return not self._hasReached(context['ng_radar']['coordinate'])

    def _hasReached(self, coordinate: Coordinate) -> bool:
        return (
            coordinate[2] == self.coordinate[2] and
            max(
                abs(coordinate[0] - self.coordinate[0]),
                abs(coordinate[1] - self.coordinate[1]),
            ) <= self.tolerance
        )

    def onBeforeStart(self, context: Context) -> Context:
        self.calculateWalkpoint(context)
        return context

    def onBeforeRestart(self, context: Context) -> Context:
        return self.onBeforeStart(context)

    def onInterrupt(self, context: Context) -> Context:
        return gameplayUtils.releaseKeys(context)

    def onComplete(self, context: Context):
        return gameplayUtils.releaseKeys(context)

    def did(self, context: Context) -> bool:
        return self._hasReached(context['ng_radar']['coordinate'])

    def ping(self, context: Context) -> Context:
        if self.isTrapped and monotonic() >= self.nextPathRetryAt:
            self.calculateWalkpoint(context)
        return context

    # TODO: add unit tests
    def calculateWalkpoint(self, context: Context):
        self.tasks = []
        if self._hasReached(context['ng_radar']['coordinate']):
            self.isTrapped = False
            self.blockingCreature = None
            return
        fixedNonWalkableCoordinates = context['ng_cave']['holesOrStairs'].copy()
        for sqm in context['ng_cave'].get('specialSqms', []):
            # Sperrzonen des Nutzers (Loecher, PZ, Gefahrenfelder).
            fixedNonWalkableCoordinates.append(tuple(sqm))
        monsterCoordinates = []
        for monster in context['gameWindow']['monsters']:
            # TODO: func to check if coord is none
            if monster['coordinate'] is not None:
                hasNoneCoord = any(coord is None for coord in monster['coordinate'])
                if not hasNoneCoord:
                    monsterCoordinates.append(monster['coordinate'])
        refillCheckerIndexPlayer = next((index for index, item in enumerate(context['ng_cave']['waypoints']['items']) if item['type'] == 'refillChecker'), None)
        if refillCheckerIndexPlayer is None or refillCheckerIndexPlayer is not None and context['ng_cave']['waypoints']['currentIndex'] < refillCheckerIndexPlayer:
            for player in context['gameWindow']['players']:
                # TODO: func to check if coord is none
                if player['coordinate'] is not None:
                    hasNoneCoord = any(coord is None for coord in player['coordinate'])
                    if not hasNoneCoord:
                        fixedNonWalkableCoordinates.append(player['coordinate'])
        baseNonWalkableCoordinates = (
            fixedNonWalkableCoordinates + monsterCoordinates
        )
        fieldCoordinates = context['gameWindow'].get('fieldCoordinates', [])
        nonWalkableCoordinates = (
            baseNonWalkableCoordinates + self.blockedCoordinates +
            fieldCoordinates
        )
        walkpoints = generateFloorWalkpoints(
                context['ng_radar']['coordinate'], self.coordinate, nonWalkableCoordinates=nonWalkableCoordinates)
        while len(walkpoints) == 0 and self.blockedCoordinates:
            self.blockedCoordinates.pop(0)
            walkpoints = generateFloorWalkpoints(
                context['ng_radar']['coordinate'],
                self.coordinate,
                nonWalkableCoordinates=(
                    baseNonWalkableCoordinates + self.blockedCoordinates +
                    fieldCoordinates
                ),
            )
        if len(walkpoints) == 0 and fieldCoordinates:
            # Eingeschlossen (z. B. Feuerfelder/Spieler): im Notfall
            # durch die Felder laufen statt stecken zu bleiben.
            walkpoints = generateFloorWalkpoints(
                context['ng_radar']['coordinate'],
                self.coordinate,
                nonWalkableCoordinates=(
                    baseNonWalkableCoordinates + self.blockedCoordinates
                ),
            )
            if walkpoints:
                from src.gameplay.monitoring import logEvent
                logEvent('escapeThroughFields', {
                    'fields': len(fieldCoordinates),
                })
        if len(walkpoints) == 0 and not self._hasReached(context['ng_radar']['coordinate']):
            self.isTrapped = True
            self.nextPathRetryAt = monotonic() + 1
            openPath = generateFloorWalkpoints(
                context['ng_radar']['coordinate'],
                self.coordinate,
                nonWalkableCoordinates=fixedNonWalkableCoordinates,
            )
            self.blockingCreature = getFirstCreatureOnPath(
                context['gameWindow']['monsters'], openPath,
            )
            creatures = context['ng_battleList']['creatures']
            battleContent = (
                getContent(context['ng_screenshot'])
                if len(creatures) > 0 else None
            )
            hasSafeUnknownBlocker = (
                len(creatures) > 0 and battleContent is not None and
                not context['gameWindow']['players'] and
                not hasSkull(battleContent, creatures)
            )
            if (any(creature[0] != 'Unknown' for creature in creatures) or
                    hasSafeUnknownBlocker):
                refillCheckerIndex = next((index for index, item in enumerate(context['ng_cave']['waypoints']['items']) if item['type'] == 'refillChecker'), None)
                if refillCheckerIndex is None or refillCheckerIndex is not None and context['ng_cave']['waypoints']['currentIndex'] < refillCheckerIndex:
                    self.tasks = [
                        AttackMonstersBoxTask().setParentTask(self).setRootTask(self),
                        LootMonstersBoxTask().setParentTask(self).setRootTask(self),
                        LootMonstersBoxTask().setParentTask(self).setRootTask(self),
                        LootMonstersBoxTask().setParentTask(self).setRootTask(self),
                        ResetSpellIndexTask().setParentTask(self).setRootTask(self),
                    ]
                else:
                    self.tasks = [
                        ClickInClosestCreatureTask().setParentTask(self).setRootTask(self.rootTask),
                        WalkToTargetCreatureTask().setParentTask(self).setRootTask(self.rootTask),
                    ]
        else:
            self.isTrapped = False
            self.blockingCreature = None
        for walkpoint in walkpoints:
            self.tasks.append(WalkTask(context, walkpoint, self.passinho).setParentTask(
                self).setRootTask(self.rootTask))
