import cv2
import numpy as np
import math
import glob
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D


def plot(x, y, z,  title='', xlabel='', ylabel='', zlabel='',color_style_str='', label_str='', figure=None, axis=None):
    if figure is None:
        fig = plt.figure()
    else:
        fig = figure
    ax = axis

    #TODO: Add title, x_label, y_label, z_label to ax.

    #TODO: Scatter plot of data points with coordinates (x, y, z) with the corresponding color and label.

    handles, labels = ax.get_legend_handles_labels()

    unique = list(set(labels))
    handles = [handles[labels.index(u)] for u in unique]
    labels = [labels[labels.index(u)] for u in unique]

    ax.legend(handles, labels)

def preprocess(img):
    # Preprocess the given image img.
    # TODO: Convert the image to grayscale.
    # Hint: Check the function cvtColor in opencv.

    # TODO: Blur the image to remove the noise (apply a low pass filter 3x3).
    # Hint: Check the function blur in opencv.
    # Hint: Pass the kernel size as an array (3, 3)

    # TODO: Apply a threshold between 50 and 255 on the blurred image. The pixels having values less than 50 will be considered 0, and 255 otherwise.
    # Hint: Check the function threshold in opencv.
    # Hint: Use the type cv2.THRESH_BINARY with the type parameter.
    # Hint: This function has two return parameters. You can ignore the first one, and the second is the binary image.
    # Example: _, thresholded_img = cv2.threshold(....)

    thresholded_img = img
    return thresholded_img

def findContourArea(img):
    #This function finds the contours of a given image and returns it in the variable contours.
    #This function will not work correctly unless you preprocess the image properly as indicated.
    im2, contours, hierarchy = cv2.findContours(img, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)

    #TODO: Find the contour area of the given image (img)
    area = 0

    return area, contours

def findBoundingRectangleArea(img, contours):
    #This function tries to fit the minimum bounding rectangle for the given contours.

    bounding_rectangle = 0
    #TODO: Find the minimum bounding rectangle that can fit the given contours.
    #Hint: Check the function boundingRect in opencv

    #TODO (Optional): You can uncomment the following command(s) to show or display the bounded rectangle.
    #bounding_rectangle = cv2.rectangle(img.copy(), (x, y), (x + w, y + h), (0, 255, 0), 2)
    #cv2.imshow('Image_Rec', bounding_rectangle)
    #cv2.waitKey(0)

    #TODO: Find the area of the bounding rectangle
    area = 0
    return area, bounding_rectangle

def findBoundingCircleArea(img, contours):
    #This function tries to fit the minimum bounding circle for the given contours.

    bounding_circle = 0
    #TODO: Find the minimum enclosing circle that can fit the given contours.
    #Hint: Check the function minEnclosingCircle in opencv

    #TODO (Optional): You can uncomment the following command(s) to show or display the bounded circle.
    #bounding_circle = cv2.circle(img.copy(), center, radius, (0, 255, 0), 2)
    #cv2.imshow('Image_Circle', bounding_circle)
    #cv2.waitKey(0)

    # TODO: Find the area of the bounding circle
    area = 0
    return area, bounding_circle

def findBoundingTriangleArea(img, contours):

    #This function tries to fit the minimum bounding triangle for the given contours.
    bounding_triangle = 0
    # TODO: Find the minimum enclosing triangle that can fit the given contours.
    # Hint: Check the function minEnclosingTriangle in opencv and place its output in the variable x

    # TODO (Optional): You can uncomment the following command(s) to show or display the bounded triangle.
    #bounding_triangle = cv2.polylines(img.copy(), np.int32([x[1]]), True, (0, 255, 0), 2)
    #cv2.imshow('Image_Triangle', bounding_triangle)
    #cv2.waitKey(0)

    # TODO: Find the area of the bounding circle
    area = 0
    return area, bounding_triangle

def calculateDistance(x1, x2):

    #TODO: Calculate the Euclidean distance between the two vectors x1 and x2.
    distance = 0
    return distance

def MinimumDistanceClassifier(test_point, training_points):

    #TODO: Implement the minumum distance classifier. You have to classify the test_point whether it belongs to class 1, 2 or 3.
    classification = 0
    return classification

def NearestNeighbor(test_point, all_points):

    #TODO: Implement the Nearest Neighbour classifier. You have to classify the test_point whether it belongs to class 1, 2 or 3.
    classification = 0
    return classification

def KNN(test_point, all_points, k):

    #TODO: Implement the K-Nearest Neighbour classifier. You have to classify the test_point whether it belongs to class 1, 2 or 3.
    classification = 0
    return classification

def get_class_from_file_name(file_name):
    return file_name.split("test\\")[1].split(".")[0]


def get_class_name(class_number):
    classes = ["", "Rectangle", "Circle", "Triangle"]
    return classes[int(class_number)]


def extract_features(img, class_number=None):
    #Given an image img, extract the following features.
    #1. The ratio between the figure area and the minimum enclosing rectangle.
    #2. The ratio between the figure area and the minimum enclosing circle.
    #3. The ratio between the figure area and the minimum enclosing triangle.

    area, contours = findContourArea(img)
    area1,_ = findBoundingRectangleArea(img, contours)
    area2,_ = findBoundingCircleArea(img, contours)
    area3,_ = findBoundingTriangleArea(img, contours)

    features = []
    #TODO: Extract the features and append the class_number (if given) in the begininning of each feature vector.
    return features

training_data = []
training_data_rec = []
training_data_circle = []
training_data_tri = []

for filename in glob.glob('images/rectangle/*.png'):
    img = cv2.imread(filename)
    img = preprocess(img)
    img_features = extract_features(img, 1)
    training_data.append(img_features)
    training_data_rec.append(img_features)

for filename in glob.glob('images/circle/*.png'):
    img = cv2.imread(filename)
    img = preprocess(img)
    img_features = extract_features(img, 2)
    training_data.append(img_features)
    training_data_circle.append(img_features)

for filename in glob.glob('images/triangle/*.png'):
    img = cv2.imread(filename)
    img = preprocess(img)
    img_features = extract_features(img, 3)
    training_data.append(img_features)
    training_data_tri.append(img_features)

training_data = np.asarray(training_data)
training_data_rec = np.asarray(training_data_rec)
training_data_circle = np.asarray(training_data_circle)
training_data_tri = np.asarray(training_data_tri)

# Visualization of features
fig = plt.figure()
ax = fig.add_subplot('111', projection='3d')

plot(training_data_rec[:, 1], training_data_rec[:, 2], training_data_rec[:, 3], title='Training Data',
     xlabel='Feature Rec.', ylabel='Feature Circle', zlabel='Feature Tri.', color_style_str='r', label_str = "Rectangle",
     figure=fig, axis=ax)

plot(training_data_circle[:, 1], training_data_circle[:, 2], training_data_circle[:, 3], title='Training Data',
     xlabel='Feature Rec.', ylabel='Feature Circle', zlabel='Feature Tri.', color_style_str='b', label_str = "Circle",
     figure=fig, axis=ax)

plot(training_data_tri[:, 1], training_data_tri[:, 2], training_data_tri[:, 3], title='Training Data',
     xlabel='Feature Rec.', ylabel='Feature Circle', zlabel='Feature Tri.', color_style_str='g', label_str = "Triangle",
     figure=fig, axis=ax)

plt.show()

# Don't modify this.
true_values = [3, 1, 1, 3, 3, 1, 1, 2, 3, 2]
index = 0
for filename in glob.glob('test/*.png'):
    #Read each image in the test directory, preprocess it and extract its features.
    img_original = cv2.imread(filename)
    img = preprocess(img_original)
    test_point = extract_features(img)
    print("Actual class :", get_class_name(true_values[index]))
    print("---------------------------------------")
    index += 1

    min_dist_prediction = MinimumDistanceClassifier(test_point, training_data)
    nn_prediction = NearestNeighbor(test_point, training_data)
    knn_prediction = KNN(test_point, training_data, 3)

    print("Minimum Distance Classifier Prediction   :", get_class_name(min_dist_prediction))
    print("Nearest Neighbour Prediction             :", get_class_name(nn_prediction))
    print("K-Nearest Neighbours Prediction          :", get_class_name(knn_prediction))
    print("===========================================================================")


    cv2.namedWindow('image', cv2.WINDOW_NORMAL)
    cv2.imshow('image', img_original)
    cv2.waitKey(0) # this means to go to the next image press any key
    cv2.destroyAllWindows()

#TODO: Calculate the accuracy of the three classifiers on the test set. You may need to add code in the previous for loop.
accuracy_min_distance = 0
accuracy_nn = 0
accuracy_knn = 0

print("Minimum Distance Classifier Accuracy: ", accuracy_min_distance, "%")
print("Nearest Neighbour Classifier Acccuracy: ", accuracy_nn, "%")
print("K-Nearest Neighbour Classifier Accuracy: ", accuracy_knn, "%")