import random
import numpy as np
from K_Means import euclidianDistanceMean


def kmeans(population, dataSet, k, relocations):
    best_fitness, index = (min([(np.ma.masked_invalid(v), i) for i, v in enumerate(population.interlen)]))
    centers = population.Chromosomes[index]
    X = dataSet
    centroids = [[] for j in range(k)]
    clusters = [[] for l in range(k)]
    for i, member in enumerate(centroids[:]):
        centroids[i].append(centers[i])
    for individum in X:
        for i, centroid in enumerate(centroids):
            if (euclidianDistanceMean(individum, centroid)) <= min(euclidianDistanceMean(individum, centroid) \
                                                                   for centroid in centroids):
                clusters[i].append(individum)
                centroid.clear()
                centroid.append(np.mean(clusters[i], axis=0))
    relocation = 0
    while relocation != relocations:
         for cluster_index, cluster in enumerate(clusters):
            relocation = 0
            for index, get_value in enumerate(cluster):
                if (euclidianDistanceMean(get_value, cluster)) > (
                min(euclidianDistanceMean(get_value, old_cluster) for old_cluster in clusters)):
                    a, value = min(enumerate(clusters), key=lambda x: euclidianDistanceMean(get_value, x[1]))

                    for position, value in enumerate(cluster):
                        if (get_value == value).all():
                            clusters[cluster_index].pop(position)
                    clusters[a].append(get_value)
            relocation += 1
    return centroids

def generate_population(length, geneSet, popSize, fnGetFitness):
    chromosomes = []
    fitness = []
    interlen = []
    for i in range(popSize):
        chromosome = []
        while len(chromosome) < length:
            item = random.choice(geneSet)
            chromosome.append(item)
        chromosomes.append(chromosome)
        np.set_printoptions(edgeitems=3, infstr='inf',linewidth = 75, nanstr = 'nan', precision = 1,suppress = False, threshold = 1000, formatter = None)
        print(np.array(chromosomes))
        print("1")
        db, ln, centroids = fnGetFitness(chromosome)
        fitness.append(db)
        interlen.append(ln)
    return Population(chromosomes, fitness, interlen)

def _crossover(chromosomes):
    if len(chromosomes[:]) < 2:
        return chromosomes
    offspring1, offspring2 = random.sample(range(len(chromosomes[:])), 2)
    chromosomes[offspring1], chromosomes[offspring2] = \
        chromosomes[offspring2], chromosomes[offspring1]
    for gene in chromosomes:
        if len(gene[:]) < 2:
            return gene
        offspring1, offspring2 = random.sample(range(len(gene[:])), 2)
        gene[offspring1], gene[offspring2] = \
            gene[offspring2], gene[offspring1]
    return chromosomes

def _mutation(population, fnGetFitness, X, k):
    # pouzij centroidy jako chromozomy z Davies Bouldin funkce
    fitness = []
    interlen = []
    temp_chromosom = []
    for i, chromosome in enumerate(population.Chromosomes):
        db, ln, centroids = fnGetFitness(chromosome)
        fitness.append(db)
        interlen.append(ln)
        temp_chromosom.append(centroids)
    mutate = X[random.randint(1, len(X)-1)]
    temp_chromosom[random.randint(0, len(temp_chromosom)-1)][random.randint(0, k-1)] = mutate
    return Population(temp_chromosom, fitness, interlen)

def _recompute_fitness(population, fnGetFitness):
    fitness = []
    interlen = []
    for chromosome in population.Chromosomes:
        db, ln, centroids = fnGetFitness(chromosome)
        fitness.append(db)
        interlen.append(ln)
    return Population(population.Chromosomes, fitness, interlen)

def generate_best(population, fnGetFitness, dataSet, k):
    pop = population
    for i in range(4):
        newPop = pop
        newPop.Chromosomes = _crossover(pop.Chromosomes)
        newPop = _recompute_fitness(pop, fnGetFitness)
        newPop = _mutation(newPop, fnGetFitness, dataSet, k)
        # kmeans(newPop, dataSet, k, 3) puvodni navrh, velice neefektivni
        if pop > newPop:
            pop = newPop
        value, index = (min([(np.ma.masked_invalid(v), i) for i, v in enumerate(pop.interlen)]))
        print(value)
    return pop


class Population(object):
    def __init__(self, chromosomes, fitness, interlen):
        self.SumOfSum = []
        self.Chromosomes = chromosomes
        self.Fitness = fitness
        self.interlen = interlen
        self._sum()

    def _sum(self):
        self.SumOfSum = sum(sum(self.interlen, []))

    def __str__(self):
        return "Davies-Bouldin Index {}\nPrůměrná vzdálenost uvnitř shluku: {}\t ".format(self.Fitness, self.interlen)

    def __gt__(self, other):
        local, index = min([(np.ma.masked_invalid(v), i) for i, v in enumerate(self.interlen)])
        external, externalindex = min([(np.ma.masked_invalid(v), i) for i, v in enumerate(other.interlen)])
        return local > external

    def __lt__(self, other):
        local, index = min([(np.ma.masked_invalid(v), i) for i, v in enumerate(self.interlen)])
        external, externalindex = min([(np.ma.masked_invalid(v), i) for i, v in enumerate(other.interlen)])
        return local < external

    def __eq__(self, other):
        local, index = min([(np.ma.masked_invalid(v), i) for i, v in enumerate(self.interlen)])
        external, externalindex = min([(np.ma.masked_invalid(v), i) for i, v in enumerate(other.interlen)])
        return local == external
