#pragma once
#ifndef NEIGHBORS_H
#define NEIGHBORS_H


//#include <iostream>

#include <set>

#include <vector>
#include <numeric>
#include <limits>
#include <iterator>
#include <algorithm>
#include <map>

#include "matrix.h"
#include "abc.h"
#include "metrics.h"

using namespace metrics::pairwise;

namespace neighbors {

    struct Neighbor {
        vector<data_t> x;
        label_t y;
        data_t distance;
        size_t rank;
        size_t idx;
    };

    // KNeighborsClassifier class
    // Brut-force implementation of the KNN algorithm
    class KNeighborsClassifier : public ClassifierABC {
    private:
        //stores the training data
        Matrix<data_t> _data;
        //stores the training targets
        vector<label_t> _targets;
        //number of neighbors
        size_t  _n_neighbors;
        //which metric to use
        Metric _metric_type;
        //disable default c'tor
        //KNeighborsClassifier();

    protected:
        //fit the model (should be the data and targets const?)
        void _fit(const Matrix<data_t>& data, const vector<label_t>& targets);
        //predict the test (unknown) samples
        vector<label_t> _predict(const Matrix<data_t>& data) const;
        label_t _predict(const vector<data_t>& sample) const;

        vector<data_t> _cmp_distances(const vector<data_t>& sample) const;
    public:
        //number of neighbors getter
        const std::size_t& n_neighbors() const { return _n_neighbors; }
        //metric type getter
        const Metric&  metric_type() const { return _metric_type; }
        //setters
        KNeighborsClassifier& n_neighbors(size_t n_neighbors) { _n_neighbors = n_neighbors; return *this; }
        KNeighborsClassifier&  metric_type(Metric metric) { _metric_type = metric;  return *this; }

        //data getter
        const Matrix<data_t>&  data() const { return _data; }
        //targets getter
        const vector<label_t> targets() const { return _targets; }
        //c'tor
        KNeighborsClassifier(size_t  n_neighbors=3, Metric metric_type = Metric::EUCLIDEAN);
        
        vector<Neighbor> get_neighbors(const vector<data_t>& sample, size_t n_neighbors) const;

    };


}

#endif