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

import itertools
from heapq import heappop, heappush
from collections import namedtuple, Iterable
from enum import IntEnum

import api

class target(IntEnum):
    LIBRE = 0
    ATTAQUE = 1
    CONTROLE = 2
    ENNEMI = 3
    COUVERT = 4

class Etat():
    """Super etat qui stocke TOUT (d'utile)."""

    def __init__(self):
        self.tour = 0
        self.phase = 1
        self.change_phase = 70
        self.moi = Moi()
        self.adversaires = [Joueur(di) for di in api.adversaires()]

    def update_construction(self):
        # test des adversaires morts au tour d'avant
        encore_adv = api.adversaires()
        for adv in self.adversaires:
            if not adv.di in encore_adv:
                self.adversaires.remove(adv)
        # nv tour commence
        self.tour = api.tour_actuel()
        if self.tour == self.change_phase:
            self.phase = 2
        # delegue
        for j in self.adversaires + [self.moi]:
            j.update_construction()

    def update_deplacement(self):
        # delegue
        for j in self.adversaires + [self.moi]:
            j.update_deplacement()

    def update_tirs(self):
        # delegue
        for j in self.adversaires + [self.moi]:
            j.update_tirs()

    def update_siege(self):
        # delegue
        for j in self.adversaires + [self.moi]:
            j.update_siege()

    def info_case(self, c):
        return api.info_case(_(*c))

    def joueur_case(self, c):
        return api.joueur_case(_(*c))

    def tourelle_case(self, c):
        return api.tourelle_case(_(*c))

    def chemin(self, a, b):
        return [_(*c) for c in api.chemin(_(*a), _(*b))]


class Joueur():
    def __init__(self, di):
        self.di = di
        self.base = api.base_joueur(di)
        self.magie = api.magie(di)
        self.tourelles = self.tourelles_joueur()
        self.sorciers = {}

    def update_sorciers(self):
        self.sorciers = {}
        for x in range(api.TAILLE_TERRAIN):
            for y in range(api.TAILLE_TERRAIN):
                n = self.nb_sorciers((x, y))
                if n:
                    self.sorciers[(x, y)] = n

    def update_construction(self):
        """Apres siege: debut de tour."""

        self.magie = api.magie(self.di)
        self.tourelles = self.tourelles_joueur()

    def update_deplacement(self):
        """Apres construction: debut deplacement."""

        self.magie = api.magie(self.di)
        self.tourelles = self.tourelles_joueur()
        self.update_sorciers()

    def update_tirs(self):
        """Apres deplacement: debut tirs."""

        self.update_sorciers()

    def update_siege(self):
        """Apres tirs: debut siege."""

        self.update_sorciers()

    def nb_sorciers(self, c):
        return api.nb_sorciers(_(*c), self.di)

    def nb_sorciers_deplacable(self, c):
        return api.nb_sorciers_deplacable(_(*c), self.di)

    def constructible(self, c):
        return api.constructible(_(*c), self.di)

    def tourelles_joueur(self):
        a = api.tourelles_joueur(self.di)
        return [_(*c.pos) for c in a]

    def base_joueur(self):
        return _(*api.base_joueur(self.di))


class Moi(Joueur):
    def __init__(self):
        super().__init__(api.moi())

    def construire(self, c, portee):
        api.construire(_(*c), portee)

    def supprimer(self, c):
        api.detruire(_(*c))

    def tirer(self, pts, a, b):
        api.tirer(pts, _(*a), _(*b))

    def creer(self, n):
        api.creer(n)

    def deplacer(self, a, b, n):
        api.deplacer(_(*a), _(*b), n)

    def assieger(self, a, b, n):
        api.assieger(_(*a), _(*b), n)


#TODO Classe ``Moi`` qui wrappe les actions pour accelerer les update
#TODO de tourelles et sorciers (et faire des jolies exceptions...).

def _(x, y):
    """Transforme absolu en relatif et inversement."""
    if etat.moi.base[0] == api.TAILLE_TERRAIN - 1:
        x = api.TAILLE_TERRAIN - 1 - x
    if etat.moi.base[1] == api.TAILLE_TERRAIN - 1:
        y = api.TAILLE_TERRAIN - 1 - y
    return x, y

def dist(a, b):
    return abs(b[0] - a[0]) + abs(b[1] - a[1])


class Armee():
    def __init__(self):
        self.bataillons = []
        self.bataillons_a_creer = []
        self.bataillons_a_deplacer = []
        self.bataillons_a_attaquer = []


    def update_construction(self):
        for bat in self.bataillons:
            if bat.pos:
                if bat.pos in etat.moi.sorciers:
                    if etat.moi.sorciers[bat.pos] >= bat.effectif:
                        self.bataillons.remove(bat)
        for bat in self.bataillons_a_creer:
            bat.creer()

    def update_deplacement(self):
        for bat in self.bataillons_a_deplacer:
            bat.deplacer()

    def update_siege(self):
        for bat in self.bataillons_a_attaquer:
            bat.attaquer()

    def bataillon_arrive(self, bat):
        self.bataillons_a_deplacer.remove(bat)
        self.bataillons_a_attaquer.append(bat)

    def bataillon_cree(self, bat):
        self.bataillons_a_creer.remove(bat)
        self.bataillons_a_deplacer.append(bat)

    def add(self, bat):
        self.bataillons.append(bat)
        self.bataillons_a_creer.append(bat)

class Bataillon():
    def __init__(self, n, cible, armee, v_creer=5):
        self.cible = cible
        self.pos = None
        self.n = n
        self.itineraire = etat.chemin((0,0), self.cible)

        self.effectif = 0
        self.cree = False
        self.armee = armee
        self.v_creer = v_creer

    def fixe_cible(self, c):
        self.cible = c
        self.itineraire = etat.chemin(self.pos, self.cible)

    def creer(self):
        if not self.effectif == self.n:
            self.pos = (0,0)
            a_creer = min(self.n, self.v_creer)
            etat.moi.creer(a_creer)
            self.effectif += a_creer
        if self.effectif == self.n:
            self.armee.bataillon_cree(self)

    def deplacer(self):
        for i in range(len(self.itineraire)):
            if ((dist(self.pos, self.itineraire[i]) == api.PORTEE_SORCIER) or
                    (self.itineraire[i] == self.cible) or
                    ((etat.info_case(self.cible) == api.case_info.CASE_TOURELLE) and (i == len(self.itineraire) - 2))):
                etat.moi.deplacer(self.pos, self.itineraire[i], self.n)
                self.pos = self.itineraire[i]
                del self.itineraire[:i+1]
                break
        if self.pos == self.cible:
            self.armee.bataillon_arrive(self)
            return

    def danger(self, c):
        danger = 0

        ennemis = {}
        for adv in etat.adversaires:
            for c in adv.sorciers:
                if c in ennemis:
                    ennemis[c] += adv.sorciers[c]
                else:
                    ennemis[c] = adv.sorciers[c]
        for ennemi in ennemis:
            danger += dist(c, ennemi) * ennemis[ennemi] * 0.1
        return danger


    def chemin(self, a, b):
        def childs(c):
            for c2 in ((c[0]-1, c[1]), (c[0],   c[1]+1),
                      (c[0]+1, c[1]), (c[0]-1, c[1])):
                yield c2, self.danger(c2)

        queue = [(0, a, ())]
        visited = set()

        while True:
            cost, v, path = heappop(queue)
            if not v in visited:
                visited.add(v)
                if v == b:
                    return list(flatten(path)[::-1]) + [v]
                path = (v, path)
                for v2, cost2 in childs(v):
                    if not v2 in visited:
                        heappush(queue, (cost + cost2, v2, path))

    def attaquer(self):
        pass


def flatten(a):
    b = []
    if not isinstance(a, Iterable):
        return (a,)
    for i in a:
        b.extend(flatten(i))
    return b
etat = Etat()