#ifndef GENETICALGORITHM__H
#define GENETICALGORITHM__H

#include <dynet/nodes.h>
#include <dynet/dynet.h>
#include <dynet/training.h>
#include <dynet/timing.h>
#include <dynet/expr.h>
#include <dynet/io.h>
#include <string>
#include <memory>
#include "NeuralNetwork.hpp"
#include "FeatureModel.hpp"
#include "MLPBase.hpp"

class GeneticAlgorithm : public NeuralNetwork
{
  private :

  /// @brief An Individual is a part of the current population.
  ///
  /// It can be evaluated against a particular metric, can mutate and reproduce with another of its kind.
  struct Individual
  {
    /// @brief The neural network corresponding to this Individual.
    MLPBase mlp;
    /// @brief The value of this Individual.
    ///
    /// This metric is used to dertermine wich are the best Individuals of the generation.
    /// For example it can be the inverse of the loss value of its MLP against the dev dataset.
    float value;
    /// @brief Unique identifier for this individual.
    int id;
    /// @brief Create a new Individual from a certain topology.
    ///
    /// @param model The dynet model that will contains the mlp parameters.
    /// @param nbInputs The size of the mlp input layer.
    /// @param topology The desired topology for the mlp.
    /// @param nbOutputs The size of the mlp output layer..
    Individual(dynet::ParameterCollection & model, int nbInputs, const std::string & topology, int nbOutputs);
    void becomeChildOf(Individual * other);
  };

  /// @brief The current generation.
  std::vector< std::unique_ptr<Individual> > generation;

  private :

  /// @brief Load this GeneticAlgorithm from a file.
  ///
  /// @param filename The name of the file where the GeneticAlgorithm is stored.
  void load(const std::string & filename);
  /// @brief Get the value of an Individual depending on the loss of its MLP
  ///
  /// @param loss The loss of the MLP.
  ///
  /// @return  The value of the Individual.
  static float loss2value(float loss);

  public :

  /// @brief Create a new untrained GeneticAlgorithm from scratch.
  GeneticAlgorithm();

  /// @brief Create and load an already trained GeneticAlgorithm from a file.
  ///
  /// @param filename The file where the GeneticAlgorithm is stored.
  GeneticAlgorithm(const std::string & filename);

  /// @brief initialize a new untrained GeneticAlgorithm from a desired topology.
  ///
  /// @param nbInputs The size of the input.
  /// @param topology Description of the GeneticAlgorithm.
  /// @param nbOutputs The size of the output.
  void init(int nbInputs, const std::string & topology, int nbOutputs) override;

  /// @brief Give a score to each possible class, given an input.
  ///
  /// @param fd The input to use.
  ///
  /// @return A vector containing one score per possible class.
  std::vector<float> predict(FeatureModel::FeatureDescription & fd) override;

  /// @brief Update the parameters according to the given gold class.
  ///
  /// @param fd The input to use.
  /// @param gold The gold class of this input.
  ///
  /// @return The loss.
  float update(FeatureModel::FeatureDescription & fd, int gold) override;

  /// @brief Save the GeneticAlgorithm to a file.
  /// 
  /// @param filename The file to write the GeneticAlgorithm to.
  void save(const std::string & filename) override;

  /// @brief Print the topology of the GeneticAlgorithm.
  ///
  /// @param output Where the topology will be printed.
  void printTopology(FILE * output) override;
};

#endif