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} ===")
    
    # Trainingsloop mit max. 1000 Iterationen
    max_iterations = 1000
    epoch = 0
    network_trained = False
    start_weights = None
    final_weights = None
    bias = 0.0  # Initial bias value is set to 0.0
    bias_increment = 0.1  # Increment to change bias
    max_bias = 1.3  # Maximum bias
    min_bias = -1.3  # Minimum bias

    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 (einfache Schwellenwertfunktion)
            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} korrekt nach {epoch} Iterationen gelernt.")
        print(f"Startgewichte: {start_weights}")
        print(f"Endgewichte: {final_weights}")
        print(f"Bias nach {epoch} Iterationen: {bias}")
    else:
        # Wenn das Netzwerk nach 1000 Iterationen nicht gelernt hat, füge den Bias hinzu
        # und starte einen weiteren Trainingsdurchlauf mit dem neuen Bias.
        print(f"Das Netzwerk hat Wahrheitstabelle {table_index} nach {epoch} Iterationen nicht korrekt gelernt.")
        print("Erhöhe den Bias und versuche es erneut.")

        # Setze den Bias nach 1000 Iterationen neu
        bias = 0.1  # Setze den Bias zu Beginn auf 0.1
        epoch = 0  # Setze die Anzahl der Epochen zurück
        start_weights = np.random.rand(2)  # Zufällige Startgewichte für die erneute Trainingsrunde

        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 (einfache Schwellenwertfunktion)
                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

        print(f"Versuch mit neuem Bias ({bias}):")
        print(f"Total Iterationen: {epoch}")
        print(f"Startgewichte: {start_weights}")
        print(f"Endgewichte: {final_weights}")
        print(f"Endgültiger Bias: {bias}")

    # Wenn das Netzwerk das Lernen abgeschlossen hat, drucke die Endergebnisse
    if network_trained:
        print(f"\nEndgültige Ergebnisse für Wahrheitstabelle {table_index}:")
        print(f"Startgewichte: {start_weights}")
        print(f"Endgewichte: {final_weights}")
        print(f"Endgültiger Bias: {bias}")
    else:
        print(f"\nDas Netzwerk konnte Wahrheitstabelle {table_index} nicht lernen.")
