namespace{
  using arma::mat;
  using arma::zeros;
  using std::default_random_engine;
  using std::normal_distribution;
  //define helper function zero initializer
  mat zeroMatrix(const int &row, const int &col){
    //return a matrix with row x col dimensions initialized with zeroes
    return zeros<mat>(row, col);
  }

  mat randomMatrix(int row, int col, double x){
    //Return a Matrix with row x col dimensions that are distributed with random elements
    mat A(row, col);
    //Use C++ random number generator using the <random> library
    default_random_engine generator;
    normal_distribution<double> distribution(0.0, x); //change x with 1/sqrt(number of nodes from incoming layer)
    //Re-write the data with random numbers
    for (int i = 0; i < row; ++i){
      for(int j = 0; j < col; ++j){
        A.at(i,j) = distribution(generator);
      }
    }
    //Return the newly overwritten matrix
    return A;
  }

}//Unnamed Namespace. this is local to the compilation unit. Therefore only matrix.cpp can see it

namespace matrixPrometheusNSpace{
  using namespace arma;
  //Define the default constructor,
  matrixPrometheus::matrixPrometheus(){
    //create an armadillo matrix
    myMatrix = mat();
  }
  //Define the overloaded constructor,
  //x is an optional parameter. However, if you'd like to initialize a matrixPrometheus object with random values, you need to pass it.
  matrixPrometheus::matrixPrometheus(int rows, int columns, char type, double x){
    if (type == 'R' && x >= 0){
      //Then initialise the matrix with random numbers with normal distribution
      myMatrix = randomMatrix(rows, columns, x);
    }

    else if (type == 'Z'){
      //Initialize the matrix with zeroes
      myMatrix = zeroMatrix(rows, columns);
    }
  }
  //Define another overloaded constructor
  //This constructor will initialize a matrixPrometheus object taking a mat object as input
  matrixPrometheus::matrixPrometheus(const mat& aMatrix){
    myMatrix = aMatrix;
  }
  //Copy Constructor
  matrixPrometheus::matrixPrometheus(const matrixPrometheus& m){
    myMatrix = m.myMatrix;
  }
  //Input: 1-Dimensional vector
  //Returns: 1-Dimensional matrix
  matrixPrometheus::matrixPrometheus(const vector<double> &inputVector){
    unsigned short int col = inputVector.size();
    myMatrix = zeroMatrix(1, col) ;
    for(unsigned short int i = 0; i < col; ++i){
      myMatrix.at(0,i) = inputVector.at(i);
    }
  }
  
  //Sets targetValue equal to target, takes inputVector(1D) and sets it equal to myMatrix(2D);
  matrixPrometheus::matrixPrometheus(double target, const vector<double>& inputVector, int columnBreak){
    int i = 0;
    targetValue = target;
    for(unsigned int j = 0; j < inputVector.size(); ++j){
      //When j is not 0 and is divisible by columnBreak, we can increase i by 1
      //Put the first element of the vector at the targetValue
      //targetValue = inputVector[j].front();
      //Erase that value from the original vector.
      //inputVector[j].erase(inputVector[j].begin());
      if(j != 0 && j % columnBreak == 0){
        ++j;
      }
      myMatrix.at(i,j) = inputVector[j];
    }
  }
  //Destructor
  matrixPrometheus::~matrixPrometheus(){
    //Call destructor from armadillo library
    myMatrix.~mat();
  }
  //print function only for debugging
  void matrixPrometheus::print(){
    //print the matrix
    this->myMatrix.print();
  }

  matrixPrometheus operator-(double number, const matrixPrometheus& theObject){
    number - theObject.myMatrix;
    return theObject;
  }
  matrixPrometheus operator*(double number, const matrixPrometheus& theObject){
    number * theObject.myMatrix;
    return theObject;
  }
  matrixPrometheus matrixPrometheus::operator+(double number){
    return matrixPrometheus(this->myMatrix + number);
  }
  matrixPrometheus matrixPrometheus::operator-(double number){
    return matrixPrometheus(this->myMatrix - number);
  }
  matrixPrometheus matrixPrometheus::operator*(double number){
    return matrixPrometheus(this->myMatrix * number);
  }
  matrixPrometheus matrixPrometheus::operator/(double number){
    return matrixPrometheus(this->myMatrix / number);
  }
  matrixPrometheus matrixPrometheus::operator-(const matrixPrometheus &other)const{
    return matrixPrometheus(this->myMatrix - other.myMatrix);
  }
  matrixPrometheus matrixPrometheus::operator+(const matrixPrometheus &other)const{
    return matrixPrometheus (this->myMatrix + other.myMatrix);
  }
  matrixPrometheus matrixPrometheus::operator/(const matrixPrometheus &other)const{
    return matrixPrometheus (this->myMatrix / other.myMatrix);
  }
  //Double overloading the multiplication operator
  matrixPrometheus matrixPrometheus::operator*(const matrixPrometheus &other)const{
    return matrixPrometheus(this->myMatrix * other.myMatrix);
  }
  //Overloading the assignment operator
  matrixPrometheus& matrixPrometheus::operator=(const matrixPrometheus &other){
    //let the armadillo library handle this.
    this->myMatrix = other.myMatrix;
    return *this;
  }

  matrixPrometheus& matrixPrometheus::operator+=(const matrixPrometheus &other){
    //let the armadillo library handle this as well
    this->myMatrix += other.myMatrix;
    return *this;
  }

  //Transposition Function
  matrixPrometheus matrixPrometheus::transpose() const{
    //Transpose the matrix and return a pointer to the transposed matrix. But, do not make changes to the original matrix
    return matrixPrometheus(this->myMatrix.t());
  }

  matrixPrometheus sigmoid(const matrixPrometheus &object){
    //Take a matrixPrometheus as input and spit out the evaluation of the sigmoid function
    matrixPrometheus newObject(arma::pow((1.00 + arma::exp(- object.myMatrix)), -1));
    return newObject;
  }
  //We could have used armadillos vector objects for this conversion, but I don't want to import another data type
  //from their library
  //Input: a matrix of type matrixPrometheus
  //Output: A *pointer* to a vector of all the concatenated rows of the matrixPrometheus object
  const std::vector<double> vectorise(const matrixPrometheus &object){
    //make a vector of double values
    std::vector<double> v;
    //Write a for loop to concatenate all the rows
    //For all the values, add them to the vector;
    for(unsigned int i = 0; i < object.myMatrix.n_rows; ++i){
      for(unsigned int j = 0; j < object.myMatrix.n_cols; ++j){
        v.push_back(object.myMatrix.at(i,j));
      }
    }
    return v;
  }

  matrixPrometheus chainMultiply(const matrixPrometheus &first, const matrixPrometheus &second, const matrixPrometheus &third){
    return matrixPrometheus(first.myMatrix * second.myMatrix * third.myMatrix);
  }

  int matrixPrometheus::argmax(){
    //Find the index of the max value in the matrix
    return this->myMatrix.index_max();
  }

  matrixPrometheus matrixPrometheus::getTarget(){
    matrixPrometheus A(1,1, 'Z');
    A.myMatrix.at(0,this->targetValue) = 0.99;
    A.myMatrix.at(0,this->targetValue-1) = 0.99;
    return A;
  }
}//matrixPrometheus namespace
