#include "randomforest.h"

/**
 * @brief The ID3 constructor constructs class and set up parametrs
 * @param classes - map which holds class counts
 * @param head - containing names of atributes
 */
RandomForest::RandomForest(std::map <std::string, double> classes, std::vector <std::string> head)
{
    header = head;
    cls = classes;
}

/**
 * @brief The makeTree procedure making tree
 * @param firstColumn - holds ocurrances of instance
 * @param Data - Data for making the tree
 * @param column - which atribute is there to split
 * @param maxS - atribute with maximum information gain
 * @param kValue - how many atributes are considered to counting
 */
void RandomForest::makeTree(std::map<std::string, double> firstColumn, std::vector < std::vector<std::string> > Data, int column, std::string maxS, int kValue){

    double infoGain, pomProbabilities;
    int pom = 0;
    std::vector < std::vector<std::string> > dividedData;
    std::vector <probabilityPair> DataProbabilities;

    for(std::map<std::string, double>::iterator it = firstColumn.begin(); it != firstColumn.end(); ++it) {

      dividedData = divideWhole(Data, it->first, column);

      if (dividedData.size() != 1) {

      Tree.push_back(header[column]); // pushing name of atribute to Tree
      Tree.push_back(dividedData[0][column]); // pushing name of column to Tree

      DataProbabilities.clear();

      for (int i = 0; i < kValue; i++) {
          int randomAtr = rand() % (header.size()-1); // randomizing number
          infoGain = countInfoGain(dividedData, randomAtr, cls); // counting informatian gain
          DataProbabilities.push_back(std::make_pair(header[randomAtr], infoGain)); // pushing gain to vector with atribute name
       }

        maxS = max(DataProbabilities); //counting max gain

        pom = std::distance( header.begin(), std::find( header.begin(), header.end(), maxS ) ); //counting index of atribute

        for (size_t i = 0; i < DataProbabilities.size(); i++) {
            if (DataProbabilities[i].first == maxS) {
                pomProbabilities = DataProbabilities[i].second;
            } else pomProbabilities = 0;
        }

     if (pomProbabilities != countClassEntropy(cls) && maxS != header[column]) {

          std::map <std::string, double> mapForCounts;
          std::vector < std::vector<std::string> > dataToDevide;

          dataToDevide = returnDividedData(dividedData, pom);
          mapForCounts = countOccurances(dataToDevide);

          makeTree(mapForCounts, dividedData, pom, maxS, kValue); //making node of Tree

      } else Tree.push_back(dividedData[0][dividedData[0].size()-1]); // pushing name of instance to Tree
    } else {
          Tree.push_back(header[column]); // pushing name of atribute to Tree
          Tree.push_back(dividedData[0][column]); // pushing name of column to Tree
          Tree.push_back(dividedData[0][dividedData[0].size()-1]); // pushing name of instance to Tree
    }
   }
}

/**
 * @brief The Train function trains the data making whole tree
 * @param Data - Data for making the tree
 * @param numeberOfTrees - number of trees to be made
 * @param kValue - how many atributes are considered to counting
 * @return vector - tree
 */
std::vector<std::string> RandomForest::Train(std::vector < std::vector<std::string> > Data, int numberOfTrees, unsigned int kValue) {

    std::string maxS;

    Tree.clear();

    int pom, randomData, randomAtr;

    const int percentage = 66; // 66% is commonly used

    double infoGain;
    std::vector <probabilityPair> DataProbabilities;
    std::map <std::string, double> firstColumn;
    std::vector < std::vector<std::string> > dataToDevide;
    std::vector < std::vector<std::string> > DataForTree;

    double numberOfInstances = (double) countInstances(Data)/100 * (double) percentage; // counting number of instances for making Tree

    numberOfInstances = ceil(numberOfInstances);

   for (int j = 0; j < numberOfTrees; j++) {

    // determining data for tree  randomly
    for (int j = 0; j < numberOfInstances; j++) {
     randomData = rand() % (countInstances(Data)-1);
     DataForTree.push_back(Data[randomData]);
    }

    if (kValue > Data[0].size())
        kValue = header.size()-1;

    for (unsigned int i = 0; i < kValue; i++) {
       randomAtr = rand() % (header.size()-1); //randomize number
       infoGain = countInfoGain(DataForTree, randomAtr, cls); // counting informatian gain for random atribute
       DataProbabilities.push_back(std::make_pair(header[randomAtr], infoGain)); // pushing gain to vector with atribute name
    }

     maxS = max(DataProbabilities); //counting max gain

     pom = std::distance( header.begin(), std::find( header.begin(), header.end(), maxS ) ); // counting index of atribute

     dataToDevide = returnDividedData(DataForTree, pom);

     firstColumn = countOccurances(dataToDevide);

     DataProbabilities.clear();

     makeTree(firstColumn, DataForTree, pom, maxS, kValue); //rekursively making node of Tree
     DataForTree.clear();
     Tree.push_back("EndOfTree"); //pushing end of tree string for knowing where the Tree ends
   }
   return Tree;
}

/**
 * @brief The findClassInTree function finds class in tree
 * @param test - Data for finding class
 * @param pomHeader - index of atribute
 * @param pomTree - index of instance in tree
 * @param val - value, which to find
 * @param TreeLocal - tree, where to find
 * @return string - finded class or instance
 */
std::string RandomForest::findClassInTree(std::vector < std::vector<std::string> > test, int pomHeader, int pomTree, std::string val, std::vector <std::string> TreeLocal){

      TreeLocal.erase(TreeLocal.begin(), TreeLocal.begin() + pomTree); // deleting unnesecessary tree part

      pomHeader = std::distance( header.begin(), std::find( header.begin(), header.end(), val ) ); // counting index of atribute

      if (std::find(TreeLocal.begin(), TreeLocal.end(), test[0][pomHeader]) == TreeLocal.end()){

          std::vector <double> classPercentage;

          for(std::map<std::string, double>::iterator it = cls.begin(); it != cls.end(); ++it) {
           classPercentage.push_back(it->second);
          }

          double max;
          std::string maxS;

          max = countMax(classPercentage);

           for(std::map<std::string, double>::iterator it = cls.begin(); it != cls.end(); ++it) {
             if (max == it->second) { //checking if instance is class in Tree
                  maxS = it->first;
             }
           }

        return maxS;
      } else {

      pomTree = std::distance( TreeLocal.begin(), std::find(TreeLocal.begin(), TreeLocal.end(), test[0][pomHeader] ) ); // counting index of testing instance in Tree

      //if item cannot be found in tree
      if (pomTree != 0) {
       val = TreeLocal[pomTree+1];
      } else {
         std::vector <double> itPom;
         for(std::map<std::string, double>::iterator it = cls.begin(); it != cls.end(); ++it) {
             itPom.push_back(it->second);
         }
         double maxPom = countMax(itPom);
         for(std::map<std::string, double>::iterator it = cls.begin(); it != cls.end(); ++it) {
             if (maxPom == it->second)
                 val = it->first;
         }
      }

      for(std::map<std::string, double>::iterator it = cls.begin(); it != cls.end(); ++it) {
         if (val == it->first){
             return val;
         }
      }
    return findClassInTree(test, pomHeader, pomTree, val, TreeLocal); //rekursively calling function untill class is finded
      }
}

/**
 * @brief The Classify function classifies and tests given vector
 * @param test - data for testing
 * @param Tree - tree, where to test
 * @param numberOfTrees - number of trees to be made
 * @return vector - classified classes
 */
std::vector <std::string> RandomForest::Classify(std::vector < std::vector<std::string> > test, std::vector<std::string> Tree, int numberOfTrees) {

    std::vector <std::string> pomTest;

    int pomHeader, pomTree;
    std::string value, val;

    std::vector < std::vector<std::string> > oneRow;

    std::vector <std::string> TreeLocal;

    // deleting class from testing set
    for (size_t i = 0; i < test.size(); i++) {
       test[i].erase(test[i].begin() + test[0].size());
    }

for (int j = 0; j < numberOfTrees; j++) {

   TreeLocal.assign(Tree.begin(), std::find(Tree.begin(), Tree.end(), "EndOfTree"));

   Tree.erase(Tree.begin(), std::find(Tree.begin(), Tree.end(), "EndOfTree")+1);

   for (size_t i = 0; i < test.size(); i++) {

   oneRow.push_back(test[i]);

   pomHeader = std::distance( header.begin(), std::find( header.begin(), header.end(), TreeLocal[0] ) ); // counting index of atribute

   // determine if tested instances in not in Tree
   if (std::find(TreeLocal.begin(), TreeLocal.end(), oneRow[0][pomHeader]) == TreeLocal.end()){

       std::vector <double> classPercentage;

       for(std::map<std::string, double>::iterator it = cls.begin(); it != cls.end(); ++it) {
        classPercentage.push_back(it->second);
       }

       double max;
       std::string maxS;

       max = countMax(classPercentage);

        for(std::map<std::string, double>::iterator it = cls.begin(); it != cls.end(); ++it) {
          if (max == it->second) {
               maxS = it->first;
          }
        }

       pomTest.push_back(maxS);
       oneRow.clear();

   } else {

   pomTree = std::distance( TreeLocal.begin(), std::find( TreeLocal.begin(), TreeLocal.end(), oneRow[0][pomHeader] ) ); // counting index of testing instance in Tree

   val = TreeLocal[pomTree+1];

   for(std::map<std::string, double>::iterator it = cls.begin(); it != cls.end(); ++it) {
      if (val == it->first){ //checking if instance is class in Tree
          pomTest.push_back(val);
          oneRow.clear();
          value = val;
      }
   }

   if (val != value){
          pomTest.push_back(findClassInTree(oneRow, pomHeader, pomTree, val, TreeLocal));
          oneRow.clear();
      }
  }
 }
}

    std::vector <std::string> pomToCount;
    std::vector <std::string> pomTestLocal;

 // returning vector with predicted classes
 for (size_t j = 0; j < test.size(); j++) {
    for (int i = 0; i < numberOfTrees; i++) {
       pomToCount.push_back(pomTest[j+i*countInstances(test)]);
    }

    pomTestLocal.push_back(returnOneClassFromMany(pomToCount));
    pomToCount.clear();
 }
    return pomTestLocal;
}
