import sys
import os

from pathlib import Path
from matplotlib import pyplot as plt

script_path = os.path.dirname(os.path.realpath(__file__))
sys.path.append('\\'.join(script_path.split('\\')[:-2])+'\\functions')
from functions import distance, normalize


class KNearestNeighbors:
    """
    class that generates k nearest neighbor model with logistic rating
    """
    def __init__(self):
        """
        init function
        """
        self.k = 1
        self.dataset = {}
        self.labels = {}
        self.normalization_method = 'min_max'


    def fit(self, dataset, labels):
        """
        set the data set and its lables for the model
        """
        self.dataset = dataset
        self.labels = labels
        self.normalization_n = []
        self.normalization_d = []
        self.first_title = list(self.dataset.keys())[0]
        for ind in range(len(self.dataset[self.first_title])):
            self.normalize_features(self.dataset, ind)

    def predict(self, unknown):
        """
        predect the label for all unknowns in unknown dict
        """
        for title in unknown:
            for ind in range(len((unknown[list(unknown.keys())[0]]))):
                unknown[title][ind] = (unknown[title][ind] - self.normalization_n[ind]) / (self.normalization_d[ind])
        print(unknown)
        unknown_labels = {}
        for title in unknown:
            neighbors = self.k_neighbors(unknown[title], self.dataset, self.k)
            unknown_labels[title] = self.rate(neighbors, self.labels)
        return unknown_labels

    def normalize_features(self, data_dict, ind):
        """
        normalize the data set features useing the given method
        """
        pre_norm_list = []
        for title in data_dict:
            pre_norm_list.append(data_dict[title][ind])
        if self.normalization_method == 'min_max':
            mini, maxi, norm_list = normalize.min_max_normalize(pre_norm_list)
            self.normalization_n.append(mini)
            self.normalization_d.append(maxi - mini)
        elif self.normalization_method == 'z_score':
            mean, var, norm_list = normalize.z_score_normalize(pre_norm_list)
            self.normalization_n.append(mean)
            self.normalization_d.append(var)
        elif self.normalization_method == 'none':
            norm_list = pre_norm_list[:]
            self.normalization_n.append(0)
            self.normalization_d.append(1)
        for i, title in enumerate(data_dict):
            data_dict[title][ind] = norm_list[i]

    def k_neighbors(self, unknown, dataset, k):
        """
        generate the closest neighbors list
        """
        distances = []
        for title in dataset:
            point = dataset[title]
            distance_to_point = distance.euclidean_distance(point, unknown)
            distances.append([distance_to_point, title])
        distances.sort()
        neighbors = distances[0:k]
        return neighbors

    def rate(self, neighbors, labels):
        """
        generate rating for the new unknown
        """
        num_good = 0
        num_bad = 0
        for neighbor in neighbors:
            title = neighbor[1]
            if labels[title] == 0:
                num_bad += 1
            elif labels[title] == 1:
                num_good += 1
        if num_good > num_bad:
            return 1
        else:
            return 0
    def get_normalization(self):
        """
        """
        print('Use change_normalization method to set the normalization method to use')
        print('min_max')
        print('z_score')
        print('none')
        print (f'the model uses {self.normalization_method} normalization now')
        return self.normalization_method

    def change_normalization(self, arg):
        if arg == 'min_max':
            self.normalization_method = 'min_max'
        elif arg == 'z_score':
            self.normalization_method = 'z_score'
        elif arg == 'none':
            self.normalization_method = 'none'
        else:
            print('unknown normalization method')

    def ploter(self):
        """
        plot method for two features only
        """
        if len(self.dataset[self.first_title]) != 2:
            print('plot is only avilable for two features')
            return
        x_axis = []
        y_axis = []
        for title in self.dataset:
            x_axis.append(self.dataset[title][0])
            y_axis.append(self.dataset[title][1])
        plt.plot(x_axis, y_axis, 'o')
        plt.show()

movies = {'1': [1, 1],
          '2': [2, 2],
          '3': [3, 4]}
labels = {'1': 1,
          '2': 1,
          '3': 0}
unknown = {'4': [2, 4]}
model = KNearestNeighbors()
model.change_normalization('none')
print(model.get_normalization())
model.fit(movies, labels)
print(model.predict(unknown))
model.ploter()