# classify.py
# ---------------
# Licensing Information:  You are free to use or extend this projects for
# educational purposes provided that (1) you do not distribute or publish
# solutions, (2) you retain this notice, and (3) you provide clear
# attribution to the University of Illinois at Urbana-Champaign
#
# Created by Justin Lizama (jlizama2@illinois.edu) on 10/27/2018
# Extended by Daniel Gonzales (dsgonza2@illinois.edu) on 3/11/2020

"""
This is the main entry point for MP5. You should only modify code
within this file -- the unrevised staff files will be used for all other
files and classes when code is run, so be careful to not modify anything else.

train_set - A Numpy array of 32x32x3 images of shape [7500, 3072].
            This can be thought of as a list of 7500 vectors that are each
            3072 dimensional.  We have 3072 dimensions because there are
            each image is 32x32 and we have 3 color channels.
            So 32*32*3 = 3072. RGB values have been scaled to range 0-1.

train_labels - List of labels corresponding with images in train_set
example: Suppose I had two images [X1,X2] where X1 and X2 are 3072 dimensional vectors
         and X1 is a picture of a dog and X2 is a picture of an airplane.
         Then train_labels := [1,0] because X1 contains a picture of an animal
         and X2 contains no animals in the picture.

dev_set - A Numpy array of 32x32x3 images of shape [2500, 3072].
          It is the same format as train_set

return - a list containing predicted labels for dev_set
"""

import numpy as np
from numpy import linalg as la


def trainPerceptron(train_set, train_labels, learning_rate, max_iter):
    # TODO: Write your code here
    # return the trained weight and bias parameters
    weights = np.zeros(train_set.shape[1])
    bias = 0
    i = 0
    while i < max_iter:
        for j in range(0, train_set.shape[0]):
            x = train_set[j].dot(weights)
            weight_gLoss = train_set[j] * learning_rate
            if x+bias <= 0 and train_labels[j] == 1:
                weights += weight_gLoss
                bias += learning_rate
                continue
            if x+bias > 0 and train_labels[j] == 0:
                weights -= weight_gLoss
                bias -= learning_rate
                continue
        i += 1
    return weights, bias


def classifyPerceptron(train_set, train_labels, dev_set, learning_rate, max_iter):
    # TODO: Write your code here
    # Train perceptron model and return predicted labels of development set
    weights, bias = trainPerceptron(train_set, train_labels, learning_rate, max_iter)
    output = []
    for im in dev_set:
        x = weights.dot(im)
        if x + bias <= 0: output.append(0)
        else: output.append(1)
    return output


def classifyKNN(train_set, train_labels, dev_set, k):
    # TODO: Write your code here
    once = True
    output = []
    for im in dev_set:
        possible_labels = [train_labels[i] for i in trainKNN(train_set, im, k)]
        unique_possible_labels = set(possible_labels)
        posCount = possible_labels.count
        if once:
            print("count: ", posCount, "neighbirs: ",possible_labels,"neighbirs 2: ",unique_possible_labels, "res: ", max(unique_possible_labels, key=posCount))
            once = False
        output.append(max(unique_possible_labels, key=posCount))
    return output


def trainKNN(train, test_row, k):
    neighbors = []
    indexed_norms = []
    spots = 0
    for train_row in train:
        indexed_norms.append((spots, la.norm(test_row-train_row, 2)))
        spots += 1
    indexed_norms.sort(key=lambda tup: tup[1])
    for i in range(k): neighbors.append(indexed_norms[i][0])
    return neighbors
