import logging
from examples.voroni_world.models import *
from coopgame.gameTemplate import tt
from cooptools.decor import timer
import pprint
import math
from typing import Dict, Tuple, List, Callable
from cooptools import graphs as grph
from cooptools.physics import waypointFollower as wf
from examples.voroni_world.behaviors import cmn

logger = logging.getLogger(__name__)

_village_to_region_route_cache = {}

@timer(logger=logger, log_level=0, time_tracking_class=tt)
def choose_scoutable_region(village_region: Region,
                            regions: Dict[int, Region],
                            route_provider: Callable[[Region, Region], grph.AStarResults]) -> Tuple[List[Region], Region]:
    scoutables = [r for r in regions.values()
                  if r.visibility_level == VisibilityLevel.REVEALABLE]

    routes = {}
    for r in scoutables:
        if r in _village_to_region_route_cache:
            routes[r] = _village_to_region_route_cache[r]
        else:
            routes[r] = route_provider(village_region, r)

            if routes[r].nodes is not None:
                _village_to_region_route_cache[r] = routes[r]

    route_ls = {k.name: v.Length if v.nodes is not None else None
                for k, v in routes.items() if v.nodes is not None}
    logger.debug(f"Scoutable Regions: {pprint.pformat(route_ls)}")

    """CRASH IN"""
    r_654 = next((x for x in scoutables if x.name == 654), None)
    if r_654 is not None: print(routes[r_654].nodes)
    # if r_654 is not None and routes[r_654].nodes is None:
    #     osm.save_jsonable_data(routes[r_654].to_json(), file_path=r"C:\Users\Tj Burns\Downloads\654_route.json")
    #     grph.plot_astar_steps(
    #         results=routes[r_654],
    #         graph=self.region_graph,
    #         save_path=r"C:\Users\Tj Burns\Downloads\654_route.gif"
    #     )

    if len(route_ls) == 0:
        return scoutables, None

    """CRASH OUT"""

    min_l = min(l for l in route_ls.values())
    selct = next((k, v) for k, v in routes.items()
                 if v.nodes is not None and math.isclose(v.Length, min_l))

    del _village_to_region_route_cache[selct[0]]

    logger.debug(f"Selected Region to scout: {selct[0]} with dist {selct[1].Length}")
    return scoutables, selct[0]

def reveal_a_region(r: Region,
                    regions: Dict[int, Region],
                    force: bool = False):

    r.set_visibility(requested=VisibilityLevel.REVEALED, force=force)
    r.remove_action_state(VisibilityLevel.REVEALING, force=force)

    for neighbor in r.neighbors.keys():
        if regions[neighbor].visibility_level in [VisibilityLevel.HIDDEN]:
            regions[neighbor].set_visibility(VisibilityLevel.REVEALABLE)

    return r

def scout(
        villager: Villager,
        villager_current_region_provider: Callable[[Villager], Region],
        route_provider: Callable[[Region, Region], grph.AStarResults],
        route_to_waypoints_provider: Callable[[List[grph.Node]], List[wf.Waypoint]],
        village_region: Region,
        regions: Dict[int, Region],
):

    scoutables, to_scout = choose_scoutable_region(
        village_region=village_region,
        regions=regions,
        route_provider=route_provider
    )

    if to_scout is None:
        logger.error(f"No routable, scoutable areas")
        return None

    if to_scout.visibility_level != VisibilityLevel.REVEALABLE:
        raise Exception(
            f"Villager \'{villager.name}\' cannot scout region \'{to_scout.name}\' because its visibility is {to_scout.visibility_level}")

    cmn.send_villager_to_region(
        villager=villager,
        region=to_scout,
        villager_current_region_provider=villager_current_region_provider,
        route_provider=route_provider,
        route_to_waypoints_provider=route_to_waypoints_provider,
        stop_at_border=True
    )

    to_scout.set_visibility(requested=VisibilityLevel.REVEALING)
    to_scout.add_action_state(VisibilityLevel.REVEALING)

    return to_scout