#include "id3.h"

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

/**
 * @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
 */
void ID3::makeTree(std::map<std::string, double> firstColumn, std::vector < std::vector<std::string> > Data, int column, std::string maxS){

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

  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 (size_t i = 0; i < header.size()-1; i++) {
      infoGain = countInfoGain(dividedData, i, cls); // counting informatian gain
      DataProbabilities.push_back(std::make_pair(header[i], 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

   if (DataProbabilities[pom-1].second != countClassEntropy(cls)) {

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

        makeTree(mapForCounts, dividedData, pom, maxS); //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
 * @return vector - tree
 */
//metoda pro trenovani dat
std::vector<std::string> ID3::Train(std::vector < std::vector<std::string> > Data) {

  std::string maxS;

  Tree.clear();

  int pom;

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

  for (size_t i = 0; i < header.size()-1; i++) {
     infoGain = countInfoGain(Data, i, cls); // counting informatian gain
     DataProbabilities.push_back(std::make_pair(header[i], 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(Data, pom);

   firstColumn = countOccurances(dataToDevide);

   DataProbabilities.clear();

   makeTree(firstColumn, Data, pom, maxS); //rekursively making node of Tree

   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 ID3::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

    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){ //checking if instance is class in Tree
           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 testToCompare - actual value in test data
 * @return vector - classified classes
 */
std::vector <std::string> ID3::Classify(std::vector < std::vector<std::string> > test, std::vector<std::string> Tree, std::vector <std::string> testToCompare) {

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

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

     std::vector < std::vector<std::string> > oneRow;
     std::vector <std::string> TreeLocal = Tree;

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

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

    oneRow.push_back(test[i]);

    value = testToCompare[i];

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

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

    val = Tree[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();
       }
   }
     return pomTest;
}
