from sklearn.svm import SVC
from sklearn import preprocessing
import numpy as np
from utils import dataset
import os
import random
import math
from collections import Counter

x_train = None
x_test = None

y_train = None
y_test = None

training_size = None
testing_size = None
overall_size = None

x_scaled = None

matrix = None

'''
Trains and tests a SVM with varying Kernels and Slack Variable Coefficients

'''
def automated_test():
	global x_train
	global x_test
	global y_train
	global y_test
	global x_scaled
	global training_size
	global testing_size
	print()
	pred_cats = []
	overall_best_accuracy = 0
	overall_best_kernel = None
	overall_best_slack_variable = None

	for k in ['linear', 'poly', 'rbf']:

		# Create a list and store the highest one each time
		max_accuracy = 0
		max_slack_variable = None

		# for i in [0.1, 0.3, 0.5, 0.7, 0.9, 1, 1.3, 1.5, 1.7, 2, 2.3, 2.5, 2.7, 3, 3.3, 3.5, 3.7, 3.9, 4, 10, 15, 20, 30, 50, 70, 100, 200, 300, 400, 500]:
		for i in [1, 100, 1000]:
			clf = SVC(C = i, cache_size = 1000, kernel = k)
			clf.fit(x_train, y_train)
			for j in range(len(x_train)):
				next = j + 1
				pred_cats.append(clf.predict(x_train[j: next]).item(0))
			scr = clf.score(x_test, y_test)

			if scr >= max_accuracy:
				max_accuracy = scr
				max_slack_variable = i

			if scr >= overall_best_accuracy:
				overall_best_accuracy = scr
				overall_best_kernel = k
				overall_best_slack_variable = i
			print(Counter(y_test))
			print(Counter(pred_cats))
			print("Kernel:", k , "Slack variable:", i, "Score:", scr)
		print()
		print("*********************************")
		print("Current Kernel:", k)
		print("Max Accuracy:", max_accuracy)
		print("Slack Variable for Max Accuracy:", max_slack_variable)
		print("*********************************")
		print()

	print("***************************************")
	print("OVERALL STATISTICS:")
	print("Overall best accuracy was:", overall_best_accuracy)
	print("Overall best accuracy occurred with kernel:", overall_best_kernel, "and Slack Variable", overall_best_slack_variable)
	print("***************************************")

	'''
    rock_count = 0
	for i in y_test:
		if i == 1:
			rock_count += 1

	accuracy = rock_count / len(y_test)
	print("Total number of Rock songs found in test set: " + str(rock_count))
	print("Total number of songs found in the test set: " + str(len(y_test)))
	print("Accuracy of a Naive Classifier that always picks rock: " + str(accuracy))'''


'''
Train and test a single SVM according to specific parameters
Can also print out the generated classes of the testing data and
compare it with the values in the classification vector
'''
def manual_test():
	global x_train
	global x_test
	global y_train
	global y_test
	global x_scaled
	global training_size
	global testing_size
	global matrix
	clf = SVC(C = 1, cache_size = 1000, kernel = "linear") # both linear and rbf appear to give an accuracy around 80%, decreasing the Slack Variable decreases accuracy
	clf.fit(x_train, y_train)

	# Individually test each of the points in the testing set
	answers = []
	for i in range(training_size,len(x_scaled)):
		next = i+1
		answer = clf.predict(x_scaled[i: next])
		answers.append(answer[0])

	print("Answers generated by the SVM, length =", len(answers))
	print(answers)

	print("Answers defined by the dataset, length = ", len(y_test))
	print(y_test)

	retrieve_song_names(matrix)

	correct = 0
	random_correct = 0
	test_rock_count = 0
	for i in range(0, len(y_test)):
		if answers[i] == y_test[i]:
			correct += 1
		if y_test[i] == 1:
			# incrememnt random_correct
			random_correct += 1
		if y_test[i] == 1:
			test_rock_count += 1


	print("The actual number of rock songs found in the testing set", test_rock_count)

	print("My Calculated percentage: " + str(correct/len(y_test)))
	scr = clf.score(x_test, y_test)
	print("The percentage calculated by sci-kit Learn: " + str(scr))
	print("The accuracy of a random classifer that always predicts Rock", ((random_correct/len(y_test) * 100)))


# Retrieve the name of the songs in the given matrix
def retrieve_song_names(matrix):
	global training_size
	global overall_size
	song_names = []
	# Some predefined range based on the subset that I was testing on - this was the testing subset in this case
	for i in range(training_size,overall_size):
		line = matrix[i]
		song_names.append(line[0])
	print(song_names)


# Read from the "csv_reader" and generate a matrix
def generate_full_matrix(csv_reader):
	matrix = []
	for line in csv_reader:
		if "song" in line or "genre" in line or len(line) == 0:
			# Ignore the head of the csv and any empty lines
			continue
		currentSong = []
		for i in range(0, len(line) -1):
			# check to see if we are working with the genre
			if i == 0:
				# Include the name of the song
				currentSong.append(line[0])
			elif i == 1:
				if line[i] == "Rock":
					# 1 is rock
					currentSong.append(1)
				else:
					# 0 is not rock
					currentSong.append(0)
			else:
				currentSong.append(float(line[i]))

		matrix.append(currentSong)

		# Shuffle the matrix before separating into the feature matrix and the classification vector
		random.shuffle(matrix)
	return matrix


def main():
	global x_train
	global x_test
	global y_train
	global y_test
	global x_scaled
	global training_size
	global testing_size
	global matrix
	global overall_size

	path = os.getcwd() + '/dataset/nl_features_subset.csv'
	# path = os.getcwd() + '/dataset/nl_features.csv'
	csv_reader = dataset.load_data(path)
	matrix = generate_full_matrix(csv_reader)

	x = []
	y = []
	# Separate the matrix into the feature matrix and the classification vector
	for line in matrix:

		'''
		Ordering found in the CSV - song name has already been removed at this time, and genre is either Rock (1) or Not rock (0)
		0. Song Name - include song name so that we can go backwards
		1. genre
		2. annotations
		3. syllables
		4. syll_per_line
		5. verb
		6. adj
		7. noun
		8. pre-det
		9. det
		10. prep
		11. pronoun
		12. pos
		13. conj
		14. cardinal
		15. adverb
		16. particle
		17. exist
		18. inj
		19. aux
		20. aa
		21. abab
		22. abba
		20. aa - rhyme scheme
		21. abab - rhyme scheme
		22. abba - rhyme scheme
		23. freq
		'''

		#TODO: Allow for different permutations of above to be ran, if we have to retrain the model each time
		# we can just manually modify this

		#Include all of the available features
		x.append([float(line[2]), float(line[3]), float(line[4]), float(line[5]), float(line[6]), float(line[7]), float(line[8]), float(line[9]), float(line[10]), float(line[11]), float(line[12]), float(line[13]), float(line[14]), float(line[15]), float(line[16]), float(line[17]), float(line[18]), float(line[19]), float(line[20]), float(line[21]), float(line[22]), float(line[23])])


		y.append(int(line[1]))
	x = np.asarray(x)
	y = np.asarray(y)

	# Scale the data matrix
	# Note: support vector machine algorithms are not scale invariant, so it is highly recommended to scale your data
	# For example, scale each attribute on the inmput vector X to [0,1] or [-1,1]
	x_scaled = preprocessing.scale(x)

	overall_size = len(x_scaled)

	# Training data should be 0 - training_size
	training_size = math.ceil(len(x_scaled) * .80)

	# Testing data should be testing_size (training_size + 1) - len(matrix)
	# I guess because of the inclusive, exclusive testing_size should just start where training_size ends
	testing_size = training_size

	print("The length of training size is:", training_size)
	print("The length of testing size is:", testing_size)

	# to be used for training - x_train should be used with y_train (they should correspond with eachother)
	x_train = x_scaled[:training_size]
	y_train = y[:training_size]

	# to be used for testing - x_test should be used with y_test (they should correspond with eachother)
	x_test = x_scaled[training_size:]
	y_test = y[training_size:]

	manual_test()

	automated_test()

if __name__ == "__main__":
	main()
