import csv
import ast
import math

from classes.concept import Concept
from classes.feature_vector import FeatureVector

class Learner:

	def __init__(self, k_paramater: int) -> None:
		self.k_paramter = k_paramater

	# The training method, that changes the internal state of the learner such that 
	# it will classify examples of a similar set (i.e. the testSet better.
	# 
	# @param trainingSet contains feature vectors and corresponding concepts 
	# to provide experience to learn from  
	def learn(self, path_to_training_set: str):
		training_set = []
		with open(path_to_training_set, mode='r' ,newline='') as csv_file:
			reader = csv.reader(csv_file, delimiter=';')
			next(reader) # read from 2. row without header
			for row in reader:
				fv = FeatureVector(concept = row[1], features_list = ast.literal_eval(row[2]), loaded = True)
				training_set.append(fv)

		self.training_set = training_set

		return training_set
	
	# find the concept of the example from the internal knowledge of the lerner
	# this method must not consider example.getConcept() at all!!
	# 
	# @param example: is a feature vector
	# @return the concept of the examplke as learned by this before
	def classify(self, input_feature_vector):
		distances = []
		result: dict = {
			Concept.LINKS_ABBIEGEN: 0,
			Concept.RECHTS_ABBIEGEN: 0,
			Concept.RECHTS_VOR_LINKS: 0,
			Concept.STOP: 0,
			Concept.VORFAHRT_GEWAEHREN: 0,
			Concept.VORFAHRT_STRASSE: 0
		}
		for single_fv in self.training_set: 
			single_dist = self.euclid_distance(single_fv.get_vector(), input_feature_vector)
			distances.append((single_fv.get_concept(), single_dist))

		sorted_distances = sorted(distances, key=lambda tuple: tuple[1])
		interested_distances = sorted_distances[:self.k_paramter]

		for interested_fv in interested_distances:
			concept = self.string_to_enum(Concept, interested_fv[0])
			result[concept] += 1

		return result



	def euclid_distance(self, list_a, list_b):
		if len(list_a) != len(list_b):
			raise Exception("Both lists must equal in size!")
		
		sum = 0
		for i in range(0, len(list_a)):
			sum += (list_b[i] - list_a[i]) ** 2

		return math.sqrt(sum)
	
	def string_to_enum(self, enum_class, enum_string):
		try:
			# Split the string to get the enum member name
			_, member_name = enum_string.split('.')
			# Use getattr to get the enum member
			return getattr(enum_class, member_name)
		except (AttributeError, ValueError) as e:
			print(f"Error: {e}")
			return None
		
	def analyse(self, result, expectedConcept: Concept):
		sorted_dict_result = {key: value for key, value in sorted(result.items(), reverse=True, key=lambda item: item[1])}
		# for key, amount in sorted_dict_result.items():
		# 	probability = (amount/self.k_paramter) * 100
		# 	print(f" Probability of {key} is {probability}%")

		if next(iter(sorted_dict_result)) == expectedConcept:
			return 1
		else:
			return 0