#include "neighbors.h"

#ifdef _MSC_VER
#define PPL_USE_PARALLEL_FOR
#include <ppl.h>
#endif

#include "common.h"

using namespace common;

namespace neighbors {

    KNeighborsClassifier::KNeighborsClassifier(size_t  n_neighbors, Metric metric_type)
        :_n_neighbors(n_neighbors), _metric_type(metric_type) {};


    inline void  KNeighborsClassifier::_fit(const Matrix<data_t>& data, const vector<label_t>& targets) {
        _data = data;
        _targets = targets;
    }

    vector<label_t> KNeighborsClassifier::_predict(const Matrix<data_t>& data) const {
        vector<label_t> targets_pred(data.n_rows());
#ifdef PPL_USE_PARALLEL_FOR
        concurrency::parallel_for(std::size_t(0), targets_pred.size(), [&](size_t  idx) {
            targets_pred[idx] = _predict(data(idx));
        });
#else
        for(size_t idx = 0; idx < targets_pred.size(); idx++) {
            targets_pred[idx] = _predict(data(idx));
        }
#endif
        return targets_pred;
    }

    vector<data_t> KNeighborsClassifier::_cmp_distances(const vector<data_t>& sample) const {
        //init the distance array
        vector<data_t> distance_vect(this->_data.n_rows());
        const data_t *dataptr = &(this->_data.data().front());
        const data_t* sampleptr = &sample.front();
		//refactor it!
        dist_fptr distance_func = distance(this->_metric_type == EUCLIDEAN ? SQUARED_EUCLIDEAN : this->_metric_type);

        for(size_t j = 0; j < this->_data.n_rows(); j++)
            distance_vect[j] = distance_func(sampleptr, dataptr + sample.size()*j, sample.size());
        return distance_vect;
    }


    label_t KNeighborsClassifier::_predict(const vector<data_t>& sample) const {
        vector<data_t> distance_vect = _cmp_distances(sample);
        //find k neighbors
        vector<std::size_t> k_neighbors = arg_ksmallest(distance_vect,
            this->_n_neighbors);
        for(size_t j = 0; j < k_neighbors.size(); j++)
            k_neighbors[j] = this->_targets[k_neighbors[j]];
        //voting
        return find_most_frequent(k_neighbors);
    }


    vector<Neighbor> KNeighborsClassifier::get_neighbors(const vector<data_t>& sample, size_t n_neighbors) const {
        size_t n_neighbors_ = n_neighbors > n_samples() ? n_samples() : n_neighbors;
        vector<Neighbor> res(n_neighbors_);
        vector<data_t> distance_vect = _cmp_distances(sample);
        //find k neighbors
        vector<std::size_t> neighbors_ind = arg_ksmallest(distance_vect, n_neighbors_);
		//refactor it!
		if (_metric_type == SQUARED_EUCLIDEAN || _metric_type == EUCLIDEAN) {
			for (size_t i = 0; i < distance_vect.size(); i++) {
				distance_vect[i] = std::sqrt(distance_vect[i]);
			}
		}
        for(size_t i = 0; i < n_neighbors_; i++) {
            size_t j = neighbors_ind[i];
            Neighbor neighbor;
            neighbor.x = _data(j);
            neighbor.y = _targets[j];
            neighbor.distance = distance_vect[j];
            neighbor.rank = i + 1;
            neighbor.idx = j;
            res[i] = neighbor;
        }
        return res;
    }

}

