import numpy as np
from itertools import product  # To generate all binary combinations

# Initialisierung der Schwellenwerte
lower_threshold = 0.8
upper_threshold = 1.2

# Lernrate
learning_rate = 0.1

# Trainingsdaten (Inputs für das XOR-Problem und andere)
inputs = [[0, 0], [0, 1], [1, 0], [1, 1]]

# Alle möglichen Zieltabellen (16 Kombinationen)
all_possible_targets = list(product([0, 1], repeat=4))

# Trainingsloop für jede mögliche Zieltabelle
for table_index, targets in enumerate(all_possible_targets, start=1):
    print(f"\n=== Wahrheitstabelle {table_index}: Targets = {targets} ===")
    
    # Initialisieren der Startwerte
    max_iterations = 200  # Maximal 200 Iterationen für jedes Bias
    epoch = 0
    network_trained = False
    start_weights = None
    final_weights = None
    bias = 0.0  # Initial Bias ist 0.0
    bias_increment = 0.1  # Bias-Inkrement
    max_bias = 1.3  # Maximaler Bias
    min_bias = -1.3  # Minimaler Bias
    
    # Erste Trainingsrunde ohne Bias-Erhöhung
    while epoch < max_iterations:
        epoch += 1
        all_correct = True  # Flag, um zu überprüfen, ob alle Ausgaben korrekt sind
        current_weights = np.random.rand(2)  # Zufällige Startgewichte

        if epoch == 1:  # Die erste Iteration nach Initialisierung
            start_weights = current_weights  # Speichere die Startgewichte

        for input_vector, target in zip(inputs, targets):
            # Berechnung der gewichteten Summe inkl. Bias
            weighted_sum = np.dot(input_vector, current_weights) + bias

            # Aktivierungsfunktion (Schwellenwertfunktion mit zwei Schwellenwerten)
            output = 1 if lower_threshold < weighted_sum < upper_threshold else 0

            # Fehlerberechnung
            error = target - output

            # Wenn ein Fehler vorliegt, dann weise die Gewichte an
            if error != 0:
                all_correct = False
                current_weights += learning_rate * error * np.array(input_vector)

        # Überprüfe, ob alle Ausgaben korrekt sind
        if all_correct:
            network_trained = True
            final_weights = current_weights  # Speichere die finalen Gewichte
            break  # Stoppe, wenn alle Ausgaben korrekt sind

    if network_trained:
        print(f"Das Netzwerk hat Wahrheitstabelle {table_index} erfolgreich nach {epoch} Iterationen gelernt.")
        print(f"Startgewichte: {start_weights}")
        print(f"Endgewichte: {final_weights}")
        print(f"Endgültiger Bias: {bias}")
        continue  # Zum nächsten Wahrheitstabelle

    # Wenn das Netzwerk nach 200 Iterationen nicht gelernt hat, füge den Bias hinzu
    print(f"Das Netzwerk hat Wahrheitstabelle {table_index} nach {epoch} Iterationen nicht korrekt gelernt.")
    print("Erhöhe den Bias und versuche es erneut.")

    # Bias erhöhen in Schritten bis max_bias und dann bis min_bias
    bias = 0.0
    for bias in np.arange(0.1, max_bias + bias_increment, bias_increment):  # Bias von 0.1 bis 1.3
        print(f"Versuch mit Bias {bias}:")
        epoch = 0
        start_weights = np.random.rand(2)  # Zufällige Startgewichte für die erneute Trainingsrunde
        network_trained = False  # Netzwerk muss erneut trainiert werden

        while epoch < max_iterations:
            epoch += 1
            all_correct = True
            current_weights = np.random.rand(2)  # Zufällige Startgewichte

            for input_vector, target in zip(inputs, targets):
                # Berechnung der gewichteten Summe inkl. Bias
                weighted_sum = np.dot(input_vector, current_weights) + bias

                # Aktivierungsfunktion (Schwellenwertfunktion mit zwei Schwellenwerten)
                output = 1 if lower_threshold < weighted_sum < upper_threshold else 0

                # Fehlerberechnung
                error = target - output

                # Wenn ein Fehler vorliegt, dann weise die Gewichte an
                if error != 0:
                    all_correct = False
                    current_weights += learning_rate * error * np.array(input_vector)

            # Überprüfe, ob alle Ausgaben korrekt sind
            if all_correct:
                network_trained = True
                final_weights = current_weights  # Speichere die finalen Gewichte
                break  # Stoppe, wenn alle Ausgaben korrekt sind

        if network_trained:
            break  # Das Netzwerk hat jetzt gelernt

    # Bias verringern und erneut testen, wenn das Netzwerk nicht erfolgreich war
    if not network_trained:
        print("Erhöhe den Bias bis zum maximalen Wert und versuche es dann mit abnehmendem Bias.")
        for bias in np.arange(max_bias, min_bias - bias_increment, -bias_increment):  # Bias von 1.3 bis -1.3
            print(f"Versuch mit Bias {bias}:")
            epoch = 0
            start_weights = np.random.rand(2)  # Zufällige Startgewichte für die erneute Trainingsrunde
            network_trained = False  # Netzwerk muss erneut trainiert werden

            while epoch < max_iterations:
                epoch += 1
                all_correct = True
                current_weights = np.random.rand(2)  # Zufällige Startgewichte

                for input_vector, target in zip(inputs, targets):
                    # Berechnung der gewichteten Summe inkl. Bias
                    weighted_sum = np.dot(input_vector, current_weights) + bias

                    # Aktivierungsfunktion (Schwellenwertfunktion mit zwei Schwellenwerten)
                    output = 1 if lower_threshold < weighted_sum < upper_threshold else 0

                    # Fehlerberechnung
                    error = target - output

                    # Wenn ein Fehler vorliegt, dann weise die Gewichte an
                    if error != 0:
                        all_correct = False
                        current_weights += learning_rate * error * np.array(input_vector)

                # Überprüfe, ob alle Ausgaben korrekt sind
                if all_correct:
                    network_trained = True
                    final_weights = current_weights  # Speichere die finalen Gewichte
                    break  # Stoppe, wenn alle Ausgaben korrekt sind

            if network_trained:
                break  # Das Netzwerk hat jetzt gelernt

    # Ausgabe der Ergebnisse nach der Anpassung des Bias
    print(f"Total Iterationen: {epoch}")
    print(f"Startgewichte: {start_weights}")
    print(f"Endgewichte: {final_weights}")
    print(f"Endgültiger Bias: {bias}")

    # Prüfen, ob das Netzwerk die Tabelle erfolgreich gelernt hat
    if network_trained:
        print(f"\nDas Netzwerk hat Wahrheitstabelle {table_index} erfolgreich gelernt!")
        print(f"Startgewichte: {start_weights}")
        print(f"Endgewichte: {final_weights}")
        print(f"Endgültiger Bias: {bias}")
