#AUTHOR: Eric Becerril-Blas <becere1@unlv.nevada.edu>
import pandas as pd
import numpy as np
import math
from numpy import linalg as LA
import matplotlib.pyplot as plt
from sklearn.neighbors import NearestNeighbors
from statistics import mode
from sklearn.metrics.pairwise import cosine_similarity
from scipy.spatial import distance
#Global Variables
# Create Dataframe for MNIST training data-------------
MNISTtrain_df = pd.read_csv("MNIST_training.csv")

# Create Dataframe for MNIST test data-----------------
MNISTtest_df = pd.read_csv("MNIST_test.csv")


def get_minkowski_distance_of_the_third_degree(data1, data2):
    """
        Returns minkowski distance of the third degree of the data
            data1: np.array of the test data row
            data2: np.array of a row in the train data
    """
    dist = distance.minkowski(data1, data2, 3)
    return(dist)


def compute_mode(numbers):
    """
        statistic mode function throws an exception so built this to handle that
    """
    counts = {}
    maxcount = 0
    for number in numbers:
        if number not in counts:
            counts[number] = 0
        counts[number] += 1
        if counts[number] > maxcount:
            maxcount = counts[number]

    for number, count in counts.items():
        if count == maxcount:
            return(number)


def get_distances(test_row,data,labels):
    """
        Function used to return a dataframe with the labels and the distances
        test_row: numpy.array passed of one row from the test data
        data: the entire training data
        labels: the labels of the training data
    """
    distances = [] # Empty array which will hold the distances
    for index, row in data.iterrows():
        # iterate through the training data
        dist = get_minkowski_distance_of_the_third_degree(test_row, row.values) # Calculate the distance
        distances.append(dist) # Append this distance value to the distances array
    columns = ['distance'] # declare column name
    df1 = pd.DataFrame(data=distances, columns=columns) # Make reduced data into a dataframe
    df = pd.concat([labels, df1], axis=1) # Make a new dataframe with the labels now added to it
    df = df.sort_values(by=['distance'], ascending=True) # sort these values
    df = df.reset_index() # reset indices
    return(df)

def graph(A,K):
    """
        Graphs the Accuracies vs K values
    """
    fig, ax = plt.subplots()
    ax.plot(K, A)

    ax.set(xlabel='K-Value', ylabel='Accuracy (%)',
           title='Accuracy vs. K-Value')
    ax.grid()

    fig.savefig("AccurVSk.png")

def main():
    train_labels = MNISTtrain_df.iloc[:, 0] # label data for training dataset
    train_images = MNISTtrain_df.drop('label', axis=1) # pixel values for image for training datatset

    test_labels = MNISTtest_df.iloc[:, 0] # label data for the test dataset
    test_images = MNISTtest_df.drop('label', axis=1) # pixel values for image for test dataset

    neighbors = [] #empty neighbors array... use this to populate neighbors
    K_vals = [] # empty array to hold K_vals
    Accuracies = [] # empty array to hold Accuracies

    for K in range(1, 15):
        K_vals.append(K)
        # For loop through various values of K
        num_correct = 0 # variable used to count the number of correct predictions
        for index, row in test_images.iterrows():
            # iterate through the image test dataset
            df = get_distances(row.values, train_images,train_labels) # Get teh distances
            for i in range(K): # for range in the k value
                neighbors.append(df['label'][i]) # append the label to the neighbors list
            prediction = compute_mode(neighbors) # Get the number that appears most as my prediction
            neighbors = [] # make the neighbors array empty again
            if test_labels[index] == prediction:
                #if test_labels matches the prediction label than increment the number correct
                num_correct = num_correct + 1
        accur = (num_correct/50) * 100 # Calculate the accuracy
        Accuracies.append(accur) # Append to accuracy array
        print('Accuracy of KNN was '+str(accur)+'% with k equal to '+str(K)) # Print to command line
    graph(Accuracies, K_vals)

if __name__== "__main__":
    main()
