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
    all_epoch_outputs = []  # Store outputs of all epochs for debugging and transparency

    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

        epoch_outputs = []  # To store outputs of this epoch

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

            # 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)

            epoch_outputs.append((input_vector, output, target))  # Save each iteration's output

        all_epoch_outputs.append(epoch_outputs)

        # Ü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

        # Wenn Tabelle nach 100 Iterationen nicht gelernt wurde, setze neue zufällige Startgewichte
        if epoch % 100 == 0:
            print(f"Nicht funktionierende Startgewichte: {start_weights}")
            start_weights = np.random.rand(2)  # Setze neue Startgewichte

    if network_trained:
        print(f"Das Netzwerk hat Wahrheitstabelle {table_index} korrekt nach {epoch} Iterationen gelernt.")
        print(f"Die Working Startgewichte waren: {start_weights}")
        print(f"Die finalen Gewichte sind: {final_weights}")
    else:
        print(f"Das Netzwerk hat Wahrheitstabelle {table_index} nach {epoch} Iterationen nicht korrekt gelernt.")
        print("\nFinal Test Output übersprungen, da das Netzwerk nicht gelernt hat.")
        continue  # Skip final testing if the network didn't learn the truth table

    # Testen des Netzwerks nach den Lern-Iterationen
    print("\nFinal Test Output:")
    for input_vector, target in zip(inputs, targets):
        weighted_sum = np.dot(input_vector, final_weights)
        output = 1 if lower_threshold < weighted_sum < upper_threshold else 0
        print(f"Input: {input_vector}, Target: {target}, Output: {output}")

    # Optionally, print out the outputs of each epoch for transparency
    print("\nEpoch Outputs:")
    for epoch_index, epoch_outputs in enumerate(all_epoch_outputs):
        print(f"Epoch {epoch_index + 1}:")
        for input_vector, output, target in epoch_outputs:
            print(f"  Input: {input_vector}, Output: {output}, Target: {target}")
