# -*- coding: utf-8 -*-

from collections import defaultdict
from api import *
from pprint import pprint


class State:
    carte = [[None]*30 for _ in range(30)]
    moi, lui = 0, 0


    POID_DIST = 0.01 # meta constante, determinant l'evolution de POID_ATTAQUE
    POID_ATTAQUE = 5  # aggressivite
    POID_LIENS_ADV = 1
    POID_CHP_ADV = 1
    POID_TRI_POT = .5
    POID_LIBRE = 5
    POID_GROUPE = 4


signe = lambda x: 1 if x == 0 else int(x/abs(x))


def delta(p, q):
    """Renvoie la valeur absolue du delta et son signe."""
    delt = (q[0] - p[0], q[1] - p[1])
    return (abs(delt[0]), abs(delt[1])), (signe(delt[0]), signe(delt[1]))

def gen_cross(source, d):
    x, y = source
    yield from ((i, y) for i in range(x-d, x+d+1))
    for i in range(1, d+1):
        for j in range(-(d-i), d-i+1):
            yield (x + i, y + j)
            yield (x - i, y + j)


def rush(source):
    pass


def triangles(source, e):
    voisins = {p for p in e if not liens_bloquants(source, p)} | {source}
    for v in voisins:
        for v2 in voisins & {p for p in e if not liens_bloquants(v,p)}:
            yield (source, v, v2)


def get_objectives(source):
    objs = [p for p in gen_cross(source, 13)
            if (p in State.portails and portail_joueur(p) in (State.lui, -1)
                and not case_dans_champ(p))]
    valeurs = {}
    couts = {}

    if not objs:
        return None

    for obj in objs:
        v, c = 0, 0

        c += COUT_CAPTURE
        c += COUT_NEUTRALISATION_BOUCLIER*portail_boucliers(obj)

        v += POINTS_CAPTURE

        if portail_joueur(obj) == State.lui:
            # gerer l'ennemi
            c += COUT_NEUTRALISATION
            v += State.POID_ATTAQUE
            v += State.POID_LIENS_ADV*len(liens_incidents_portail(obj))
            v += State.POID_CHP_ADV*sum(score_triangle(*t[:-1]) for t in
                    champs_incidents_portail(obj))
            for t in triangles(obj, State.mes_portails):
                v += State.POID_TRI_POT*score_triangle(*t)*sum(
                        1 if x in State.mes_portails else 0 for x in t)
        else:
            # favoriser les truc libres
            v += State.POID_LIBRE
        
        v += State.POID_GROUPE*len(set(gen_cross(obj, 10)) &
                set(State.mes_portails))



        valeurs[obj] = v
        couts[obj] = c
    return objs, couts, valeurs


def knapsack_chelou(source, pa_max, pm_max, objs, valeurs, couts):
    """Knapsack sur 2 contraintes (distance PA et PM), dont une ne dependant
    pas seulement du noeud mais aussi de son antecedant (distance PM).
    Exponentiel, mais en pratique realisable car pm_max est tres faible
    (rarement des chemins de plus de 3 objectifs en un tour)."""

    queue = [(source, (0, 0), (), 0)]
    bests = {(0,0, ()): 0}
    i = 0
    while queue:
        i += 1
        pos, (pa, pm), path, pts = queue.pop(0)
        for obj in objs:
            if obj not in path:
                pa_n = couts[obj] + pa
                pm_n = distance(pos, obj) + pm
                pts_n = pts + valeurs[obj]
                if (pa_n <= pa_max and
                        (pm_n <= pm_max or pm_n <= pm_max + (pa_n-pa_max)/6)):
                    # s'il existe un truc avec moins de points
                    for (pa_b, pm_b, path), pts_b in bests.items():
                        if pts_n >= pts_b:
                            bests[(pa_n, pm_n, path+(obj,))] = pts_n
                            break
                    queue.append((obj, (pa_n, pm_n), path+(obj,), pts_n))
    return max(bests, key=lambda k: bests[k])


def bouger(p, q):
    """Se deplace jusqu'a la cible, ou s'en rapprocher le plus possible."""

    delt, s = delta(p, q)
    d = points_deplacement()
    arrivee = (p[0] + s[0]*min(d//2, delt[0]),
               p[1] + s[1]*min((d+1)//2, delt[1]))
    deplacer(arrivee)


def partie_init():
    State.moi = moi()
    State.lui = adversaire()
    State.portails = liste_portails()


def jouer_tour():
    State.mes_portails = [p for p in State.portails if portail_joueur(p) ==
                          State.moi]

    pos = position_agent(State.moi)
    pos_lui = position_agent(State.lui)
    State.POID_ATTAQUE *= State.POID_DIST*(distance(pos, pos_lui) - 20)
    res = get_objectives(pos)

    if res is None:
        rush()
        return

    else:
        objs, couts, valeurs = res
        pa, pm, path = knapsack_chelou(pos, NB_POINTS_ACTION,
                                       NB_POINTS_DEPLACEMENT, objs, valeurs,
                                       couts)
        for _ in range(pm - NB_POINTS_DEPLACEMENT):
            utiliser_turbo()
        for obj in path:
            print(deplacer(obj))
            neutraliser()
            capturer()
            for a,b,c in triangles(obj, State.mes_portails):
                lier(a)
                lier(b)
            for cible in State.mes_portails:
                lier(cible)
            pos = position_agent(State.moi)


def partie_fin():
    if score(State.moi) > score(State.lui):
        print("Lol t'as perduuuu!!! 42")
    elif score(State.moi) == score(State.lui):
        print("Pas trop mal, je voulais te tester...")
    else:
        print("Ma revenche sera terrible! >:O")
