import argparse
import os
import sys
import numpy as np

#TODO add new example: steadystate.py (analogous to standard.py)
#TODO extend both standard.py and steadystate.py to support >1 criteria (using DEAP's selNSGA2() and selSPEA2())
#TODO add comments to all examples in this directory
#TODO add to standard.py and steadystate.py evaluating each genotype in HOF N (configurable, default 10) times when the evolution ends
#TODO protect all examples against invalid genotypes (fill population until all genotypes are conrrectly evaluated). And maybe remove invalid.py if it overlaps with (is a subset of) other examples

from FramsticksLib import FramsticksLib
from evolalg.base.lambda_step import LambdaStep
from evolalg.dissimilarity.frams_dissimilarity import FramsDissimilarity
from evolalg.experiment import Experiment
from evolalg.fitness.fitness_step import FitnessStep
from evolalg.mutation_cross.frams_cross_and_mutate import FramsCrossAndMutate
from evolalg.population.frams_population import FramsPopulation
from evolalg.selection.tournament import TournamentSelection
from evolalg.statistics.halloffame_stats import HallOfFameStatistics
from evolalg.statistics.statistics_deap import StatisticsDeap
from evolalg.base.union_step import UnionStep


def ensureDir(string):
    if os.path.isdir(string):
        return string
    else:
        raise NotADirectoryError(string)


def parseArguments():
    parser = argparse.ArgumentParser(
        description='Run this program with "python -u %s" if you want to disable buffering of its output.' % sys.argv[
            0])
    parser.add_argument('-path', type=ensureDir, required=True, help='Path to Framsticks without trailing slash.')
    parser.add_argument('-opt', required=True,
                        help='optimization criteria : vertpos, velocity, distance, vertvel, lifespan, numjoints, numparts, numneurons, numconnections. Single or multiple criteria.')
    parser.add_argument('-lib', required=False, help="Filename of .so or .dll with framsticks library")
    parser.add_argument('-genformat', required=False, default="1",
                        help='Genetic format for the demo run, for example 4, 9, or B. If not given, f1 is assumed.')

    parser.add_argument("-popsize", type=int, default=50, help="Size of population, default 50.")
    return parser.parse_args()


def extract_fitness(ind):
    return ind.fitness


def print_population_count(pop):
    print("Current:", len(pop))
    return pop  # Each step must return a population


def main():
    parsed_args = parseArguments()
    frams = FramsticksLib(parsed_args.path, parsed_args.lib,
                          "eval-allcriteria.sim")

    hall_of_fame = HallOfFameStatistics(100, "fitness")
    statistics_union = UnionStep([
        hall_of_fame,
        StatisticsDeap([
            ("avg", np.mean),
            ("stddev", np.std),
            ("min", np.min),
            ("max", np.max),
            ("count", len)
        ], extract_fitness)
    ])

    fitness = FitnessStep(frams, fields={parsed_args.opt: "fitness", }, fields_defaults={})

    init_stages = [FramsPopulation(frams, parsed_args.genformat, 50),
                   fitness,
                   statistics_union]

    selection = TournamentSelection(5, copy=True, fit_attr="fitness")

    new_generation_steps = [
        FramsCrossAndMutate(frams, cross_prob=0.2, mutate_prob=0.9),
        fitness,
    ]

    generation_modifications = [
        statistics_union
    ]

    experiment = Experiment(init_population=init_stages,
                            selection=selection,
                            new_generation_steps=new_generation_steps,
                            generation_modification=generation_modifications,
                            end_steps=[],
                            population_size=parsed_args.popsize
                            )
    experiment.init()
    experiment.run(3)
    for ind in hall_of_fame.haloffame:
        print("%g\t%s" % (ind.fitness, ind.genotype))


if __name__ == '__main__':
    main()
