import os
import ast
import csv
import numpy as np
import random 
import multiprocessing

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


def knn(training = os.path.abspath(os.path.join(__file__, "..", "training.csv")), testing = os.path.abspath(os.path.join(__file__, "..", "testing.csv")), k_param = 3):
    from classes.learner import Learner

    testing_data = []
    with open(testing, 'r') as csv_file:
        reader = csv.reader(csv_file, delimiter=";")
        next(reader)
        for row in reader:
            testing_data.append((row[1], row[2]))

    learner = Learner(k_paramater=k_param)
    learner.learn(training)

    cnt = 0

    for test_feature_vector in testing_data:
        distances = learner.classify(ast.literal_eval(test_feature_vector[1]))
        cnt += learner.analyse(distances, Concept.identify_by_str(test_feature_vector[0].split('.')[1]))

    return [cnt, len(testing_data) - cnt, len(testing_data)] # richtig, falsch, amount of testing data


def decision_tree(training = os.path.abspath(os.path.join(__file__, "..", "training.csv")), testing = os.path.abspath(os.path.join(__file__, "..", "testing.csv")), cycles = 3):
    from classes.decision_tree import DecisionTree

    training_data = []
    with open(training, 'r') as csv_file:
        reader = csv.reader(csv_file, delimiter=";")
        next(reader)
        for row in reader:
            fv = FeatureVector(concept = Concept.identify_by_str(row[1].split(".")[1]), features_list = ast.literal_eval(row[2]), loaded = True)
            training_data.append(fv)

    tree = DecisionTree(children={}, cycles=cycles)
    tree.learn(training_data)

    cnt_data = 0
    cnt_correct = 0
    with open(testing, 'r') as csv_file:
        reader = csv.reader(csv_file, delimiter=";")
        next(reader)
        for row in reader:
            cnt_data += 1
            fv = FeatureVector(concept = Concept.identify_by_str(row[1].split(".")[1]), features_list = ast.literal_eval(row[2]), loaded = True)
            classified_concept = tree.classify(fv)
            cnt_correct += 1 if fv.concept == classified_concept else 0
            # print(f"{fv.concept} was classified as {classified_concept}")

    # print(f"classified {cnt_correct}/{cnt_data} correctly  ({round(cnt_correct/cnt_data*100, 3)}%)")
    return [cnt_correct, cnt_data - cnt_correct, cnt_data]  # richtig, falsch, amount of testing data

def neural_network(training_set, testing_set, epochs = 10):
    from classes.neural.network import NeuralNetwork
    from classes.neural.neuron import Neuron
    
    nn = NeuralNetwork(neurons=None)
    nn.learn(training_set, epochs)

    cnt_correct = 0
    for testing_fv in testing_set:
        classified_concept, _ = nn.classify(testing_fv)
        cnt_correct += 1 if testing_fv.concept == classified_concept else 0

    # print(f"{fv.concept} was classified as {classified_concept}")

    #print(f"classified {cnt_correct}/{cnt_data} correctly  ({round(cnt_correct/cnt_data*100, 3)}%)")
    return [cnt_correct, len(testing_set) - cnt_correct, len(testing_set)]  # richtig, falsch, amount of testing data

def repeat_n_times(n: int, num_training_vectors: int, epochs: int):
    cnt_correct = 0
    cnt_data = 0
    for _ in range(n):
        result = neural_network(num_training_vectors=num_training_vectors, epochs=epochs)
        cnt_correct += result[0]
        cnt_data += result[2]
    print(f"{epochs}: classified {cnt_correct}/{cnt_data} correctly  ({round(cnt_correct/cnt_data*100, 3)}%)")

def load_fv(path_to_training_data = os.path.abspath(os.path.join(__file__, "..", "all_feature_vectors.csv"))):
    training_data = []
    with open(path_to_training_data, 'r') as csv_file:
        reader = csv.reader(csv_file, delimiter=";")
        next(reader)
        for row in reader:
            fv_float = ast.literal_eval(row[3])

            fv = FeatureVector(concept = Concept.identify_by_str(row[2].split(".")[1]), features_list = [element * 0.01 for element in fv_float], loaded = True)
            training_data.append(fv)
    
    return training_data


from concurrent.futures import ProcessPoolExecutor
output = os.path.abspath(os.path.join(__file__, "..", "output.csv"))
def process_epoch(input, epoch, all_vectors):
    """Function to process a single epoch."""
    l = []
    for i in range(0, 101):
        training_set = random.sample(all_vectors, input)
        testing_set_size = int(input*0.15) # 15% test daten
        testing_set = random.sample(list(set(all_vectors) - set(training_set)), testing_set_size)
        
        # Replace `neural_network` with your actual function
        correct, false, ammount = neural_network(training_set, testing_set, epoch)
        #print(f"run {i} for epoch {epoch}: correct_percentage={(correct / ammount) * 100}%")
        l.append(false / ammount)

    print(f"finished {input} | {epoch}")
    avg = np.mean(l)
    with open(output, 'a') as csv_file: 
        writer = csv.writer(csv_file, delimiter=";", lineterminator="\n")
        writer.writerow([input, testing_set_size, epoch, avg, l])
    return avg

if __name__ == "__main__":
    all_vectors = load_fv()
    with open(output, 'w') as csv_file: 
        writer =csv.writer(csv_file, delimiter=";", lineterminator="\n")
        writer.writerow(["num_train_vectors", "num_test_vectors","num_epochs", "epochs_avg", "epochs_results"])

    for input in range(1000, int(len(all_vectors)*0.85), 5000):
        inp_res = []
        # Use ProcessPoolExecutor for parallelism
        with ProcessPoolExecutor(max_workers=12) as executor:
            # Submit tasks for parallel execution
            futures = [
                executor.submit(process_epoch, input, epoch, all_vectors)
                for epoch in range(1, 21, 1)
            ]
            # Collect results as they complete
            for future in futures:
                inp_res.append(future.result())

# if __name__ == "__main__":
#     all_vectors = load_fv()

#     for input in range(40000, len(all_vectors), 400): 
#         inp_res = []
#         for epoch in range(10, 1000, 10):
#             l = []
#             multiprocessing.Process
#             for test in range(0, 1):
#                 training_set = random.sample(all_vectors, input)
#                 testing_set = list(set(all_vectors)-set(training_set))

#                 # print(f"Start for {input} trainingsdata and test number {test} with epoch {epoch}")
#                 correct, false, ammount = neural_network(training_set, testing_set, epoch)
#                 print(f"Epoch {epoch}: correct_percentage={(correct/ammount)*100}%")
#                 l.append(false/ammount)

#             inp_res.append(np.mean(l))
        
