#include "ibk.h"

IBk::IBk()
{
}
/**
 * @brief The countMinK function counts k minimum for k neighbours
 * @param column - Data for counting
 * @param k - how many minimum neighbours to count
 * @return vector - k-size vector of minimums
 */
std::vector <double> IBk::countMinK(std::vector <double> column, int k) {

  double min = column[0];
  std::vector <double> minVec;

  for (int i = 0; i < k; ++i) {
    for (size_t j = 0; j < column.size(); ++j) {
       if (min > column[j]) {
           min = column[j];
       }
    }
    minVec.push_back(min);
    column.erase(std::find(column.begin(), column.end(), min)); //erasing minimum value for further count
    min = column[0];
  }
  return minVec;
}

/**
 * @brief The standardizeNumeric function standardizes data
 * @param vec - Data to standardize
 * @return vector - standardized column
 */
std::vector <std::string> IBk::standardizeNumeric(std::vector <std::string> vec){

    std::string str;
    double pom;
    double standard;

    std::vector <double> vektor;
    std::vector <std::string> vektorGood;

    // transfers string to double
    for (size_t k = 0; k < vec.size(); ++k) {
        str = vec[k];
        pom = atof(str.c_str());
        vektor.push_back(pom);
    }

    double max = countMax(vektor);

    double min = countMin(vektor);

    //transfers double back to string and adds value to vector
    for (size_t k = 0; k < vektor.size(); ++k) {
       standard = (vektor[k] - min) / (max - min);
       std::ostringstream s;
       s << standard;
       std::string strn(s.str());
       vektorGood.push_back(strn);
       s.flush();
    }
    return vektorGood;
}

/**
 * @brief The Train function return data for testing
 * @param Data - Data to train
 * @return vector - trained Data
 */
std::vector < std::vector<std::string> > IBk::Train(std::vector < std::vector<std::string> > Data) {

   std::vector <std::string> vektor;
   std::vector <std::string> pom;

   std::string str;

   for (size_t i = 0; i < Data[0].size(); i++) {
     str = Data[0][i];
     std::vector<char> writable(str.begin(), str.end()); // transfers string to vector of chars

     if (isdigit(writable[0])){ // determine if first char in instance is digit
        for (size_t j = 0; j < Data.size(); j++) {
            pom.push_back(Data[j][i]);
        }
        vektor = standardizeNumeric(pom);

        for (size_t j = 0; j < vektor.size(); j++) {
            std::replace(Data[j].begin(), Data[j].end(), Data[j][i], vektor[j]); // replacing old values with new
        }
        pom.clear();

     }
       writable.clear();
   }
   return Data;
}

/**
 * @brief The countDistance function counts euklidian distance between test instance and train instances
 * @param Data - training data
 * @param testRow - instance to test
 * @return vector - counted distances
 */
std::vector <double> IBk::countDistance(std::vector < std::vector<std::string> > Data, std::vector <std::string> testRow){

    std::string str, strData, strTest;
    std::vector <double> pomDist, distances;
    double pomData, pomTest;
    double dist = 0;

    for (size_t i = 0; i < Data[0].size(); i++) {
     str = Data[0][i];
     std::vector<char> writable(str.begin(), str.end()); // transfers string to vector of chars

    for (size_t j = 0; j < Data.size(); j++) {
          if (isdigit(writable[0])){ // determine if first char in instance is digit

               strData = Data[j][i];
               pomData = atof(strData.c_str()); // transfers string to double

               strTest = testRow[i];
               pomTest = atof(strTest.c_str()); // transfers string to double

               dist = dist + (pow(fabs(pomTest - pomData), 2)); // counting euclidian distance

     } else {
                if (Data[j][i] == testRow[i]) {
                    dist = dist + 0;
                } else dist = dist + 1;

     }
     pomDist.push_back(dist);
     dist = 0;
    }
   }

    for (size_t j = 0; j < Data.size(); j++) {
        for (size_t i = 0; i < Data[0].size(); i++) {
            dist = dist + pomDist[i*Data.size()+j]; // counting rest of euclidian distance
        }
        dist = sqrt(dist);
        distances.push_back(dist);
        dist = 0;
    }
    return distances;
}

/**
 * @brief The Classify function classifies and tests given vector
 * @param test - testing data
 * @param k - how many neighbours
 * @return vector - classified classes
 */
//metoda pro klasifikaci dat
std::vector <std::string> IBk::Classify(std::vector < std::vector<std::string> > test, int k) {

    std::vector <std::string> pomTest, pomMinTest;
    std::vector <double> pomMin, distances;
    double min;

  for (size_t j = 0; j < test.size(); j++) {
    distances = countDistance(test, test[j]);

    if (k == 1) { // for one nearest neighbour
    min = countMin(distances);

    for (size_t i = 0; i < distances.size(); i++) {
        if (min == distances[i]) {
            pomTest.push_back(test[i][test[0].size()-1]); // saving value of predicted class based on nearest neighbour
            distances.clear();
      }
    }
        } else { // for k nearest neighbours
        pomMin = countMinK(distances, k);

    for (size_t i = 0; i < distances.size(); i++) {
        for (size_t j = 0; j < pomMin.size(); j++) {
            if (pomMin[j] == distances[i]) {
            pomMinTest.push_back(test[i][test[0].size()-1]); // saving values of predicted classes based on k nearest neighbour
            }
        }
    }
        pomTest.push_back(returnOneClassFromMany(pomMinTest)); // saving one predeicted class from k predicted classes
        pomMinTest.clear();
    }
  }
  return pomTest;
}
