#ifndef NEURALNETWORK__H
#define NEURALNETWORK__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 "FeatureModel.hpp"

class NeuralNetwork
{
  protected :

  /// @brief Activation function for a Layer.
  enum Activation
  {
    SIGMOID,
    TANH,
    RELU,
    ELU,
    LINEAR,
    SPARSEMAX,
    CUBE,
    SOFTMAX
  };

  /// @brief Get the string corresponding to an Activation.
  ///
  /// @param a The activation.
  ///
  /// @return The string corresponding to a.
  static std::string activation2str(Activation a);

  /// @brief Get the Activation corresponding to a string.
  ///
  /// @param s The string.
  ///
  /// @return The Activation corresponding to s. If s is unknown, the program abort.
  static Activation str2activation(std::string s);

  /// @brief A simple struct that represents a Layer.
  struct Layer
  {
    /// @brief Number of input neurons of this Layer.
    int input_dim;
    /// @brief Number of output neurons of this Layer.
    int output_dim;

    /// @brief The dropout rate to apply to this Layer when training.
    float dropout_rate;
    /// @brief The activation function for this Layer.
    Activation activation;

    /// @brief Construct a new Layer
    ///
    /// @param input_dim 
    /// @param output_dim
    /// @param dropout_rate
    /// @param activation
    Layer(int input_dim, int output_dim,
      float dropout_rate, Activation activation);
    /// @brief Print a description of this Layer.
    ///
    /// @param file Where to print the output.
    void print(FILE * file);
  };

  protected :

  /// @brief The seed that will be used by RNG (srand and dynet)
  static int randomSeed;

  /// @brief Tracks wether or not dynet has been initialized
  static bool dynetIsInit;

  /// @brief The dynet model containing the parameters to be trained.
  dynet::ParameterCollection model;

  protected :

  /// @brief Convert a FeatureValue to a dynet Expression that will be used as an input of the NeuralNetwork.
  ///
  /// @param cg The current Computation Graph.
  /// @param fv The FeatureValue that will be converted.
  ///
  /// @return A dynet Expression of value fv that can be used as an input in the NeuralNetwork 
  dynet::Expression featValue2Expression(dynet::ComputationGraph & cg, const FeatureModel::FeatureValue & fv);
  /// @brief Set dynet and srand() seeds.
  ///
  /// @return The DynetParams containing the set seed.
  dynet::DynetParams & getDefaultParams();
  /// @brief Initialize the dynet library.
  ///
  /// Must be called only once, and before any call to dynet functions.
  void initDynet();
  /// @brief Compute the image of an expression by an activation function.
  ///
  /// @param h The expression we want the image of.
  /// @param f The activation function.
  ///
  /// @return f(h)
  dynet::Expression activate(dynet::Expression h, Activation f);

  public :

  /// @brief Convert a dynet expression to a string (usefull for debug purposes)
  ///
  /// @param expr The expression to convert.
  ///
  /// @return A string representing the expression.
  static std::string expression2str(dynet::Expression & expr);

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

  /// @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.
  virtual std::vector<float> predict(FeatureModel::FeatureDescription & fd) = 0;

  /// @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.
  virtual float update(FeatureModel::FeatureDescription & fd, int gold) = 0;

  /// @brief Save the NeuralNetwork to a file.
  /// 
  /// @param filename The file to write the NeuralNetwork to.
  virtual void save(const std::string & filename) = 0;

  /// @brief Print the topology of the NeuralNetwork.
  ///
  /// @param output Where the topology will be printed.
  virtual void printTopology(FILE * output) = 0;

  /// @brief Return the model.
  ///
  /// @return The model of this NeuralNetwork.
  dynet::ParameterCollection & getModel();
};

#endif