from typing import Self
import numpy as np
import random
from collections.abc import MutableSequence
from classes.feature_vector import FeatureVector

from classes.neural.neuron import Neuron
from classes.concept import Concept

class NeuralNetwork:


    def __init__(self, neurons: MutableSequence[Neuron] = None) -> None:
        if neurons is None:
            list_neurons = []
            list_neurons.append(Neuron())
            list_neurons.append(Neuron())
            list_neurons.append(Neuron())

            self.neurons = list_neurons
        else:
            self.neurons = neurons

    def classify(self, feature_vector: FeatureVector) -> bool:
        result = []
        for neuron in self.neurons:
            result.append(neuron.classify(feature_vector))
        
        interpritated : Concept = self.interpritate(result)
        if (interpritated != feature_vector.concept):
            return [interpritated, False]
        else:
            return [interpritated, True]
    
    # takes feature vectors to train and generates weights
    def learn(self, training_set: list[FeatureVector]):
    
        correct_amount = 0
        false_amount = 0
        for i in range(len(training_set)*100):
            fv_to_train = training_set[i%len(training_set)] # random.choice(training_set)
            classified_concept, correct = self.classify(fv_to_train)
            
            if not correct:
                false_amount += 1
                self.update_weights(self.neurons, fv_to_train, fv_to_train.get_concept(), classified_concept)
            else:
                correct_amount += 1

        #print(f"{i}:  {(correct_amount/(correct_amount + false_amount)) * 100}%")
        pass

    # for a sequence of 3 neurons interpretate their meaning
    def interpritate(self, res: MutableSequence[int]) -> Concept:
        if res == [0, 0, 0]:
            return Concept.STOP
        elif res == [0, 0, 1]:
            return Concept.RECHTS_VOR_LINKS  
        elif res == [0, 1, 0]:
            return Concept.LINKS_ABBIEGEN
        elif res == [0, 1, 1]:
            return Concept.RECHTS_ABBIEGEN
        elif res == [1, 0, 0]:
            return Concept.VORFAHRT_GEWAEHREN
        elif res == [1, 0, 1]:
            return Concept.VORFAHRT_STRASSE
        else:
            return Concept.UNKNOWN
        
    def interpritate_other_way(self, c: Concept) -> Concept:
        if c == Concept.STOP:
            return [0, 0, 0]
        elif c == Concept.RECHTS_VOR_LINKS:
            return [0, 0, 1]
        elif c == Concept.LINKS_ABBIEGEN:
            return [0, 1, 0]
        elif c == Concept.RECHTS_ABBIEGEN:
            return [0, 1, 1]
        elif c == Concept.VORFAHRT_GEWAEHREN:
            return [1, 0, 0]
        elif c == Concept.VORFAHRT_STRASSE:
            return [1, 0, 1]
        else:
            return [1, 1, 0]
        
    # def learn(self, training_set: list[FeatureVector]):
    # 
    # correct_amount = 0
    # false_amount = 0
    # for i in range(0, 15000):
    #     for j in training_set:
    #         fv_to_train = j
    #         classified_concept, correct = self.classify(fv_to_train)
                
    #         if not correct:
    #             false_amount += 1
    #             self.update_weights(self.neurons, fv_to_train, fv_to_train.get_concept(), classified_concept)
    #         else:
    #             correct_amount += 1

    #     print(f"{i}:  {(correct_amount/(correct_amount + false_amount)) * 100}%")
    # pass
    
    def update_weights(self, neuorns: MutableSequence[Neuron], x: FeatureVector, expected: Concept, computed: Concept) -> Concept:
        learn_rate = 1
        x.get_vector().append(1)


        t = self.interpritate_other_way(expected) #[1, 1, 1]
        y = self.interpritate_other_way(computed) #[1, 1, 1]

        for i, neuron in enumerate(neuorns):
            sigma = learn_rate * np.array(x.get_vector()) * (t[i]-y[i])
            neuron.add(sigma)
        
        x.get_vector().pop()

    def print_weights(neuorns: MutableSequence[int]):
        for i, neuron in neuorns:
            print(f"{i} and {neuron.get_weights()}")