import numpy as np
from itertools import product

# Parameters
lower_threshold = 0.8
upper_threshold = 1.2
learning_rate = 0.1
max_iterations = 500
bias_increment = 0.1
min_bias, max_bias = -0.9, 0.9

# XOR input data
inputs = np.array([[0, 0], [0, 1], [1, 0], [1, 1]])

# Generate all possible target tables
all_possible_targets = list(product([0, 1], repeat=4))

def train_network(inputs, targets, max_iterations, bias, weight_range=(-2, 2)):
    """Train network with a specific bias and weights range"""
    weights = np.random.uniform(*weight_range, size=2)
    epoch = 0

    while epoch < max_iterations:
        epoch += 1
        all_correct = True
        # Vectorized error calculation and weight updates
        for input_vector, target in zip(inputs, targets):
            weighted_sum = np.dot(input_vector, weights) + bias
            output = 1 if lower_threshold < weighted_sum < upper_threshold else 0
            error = target - output
            if error != 0:
                all_correct = False
                weights += learning_rate * error * input_vector
        if all_correct:
            return weights, epoch, True
    return weights, epoch, False

# Loop through all target tables and train
for table_index, targets in enumerate(all_possible_targets, start=1):
    print(f"\n=== Wahrheitstabelle {table_index}: Targets = {targets} ===")
    
    # Start with weights in the range [0, 2] and bias 0.0
    network_trained = False
    print(f"Attempt with weights between 0 and 2, bias 0.0:")
    final_weights, epoch, network_trained = train_network(inputs, targets, max_iterations, bias=0.0, weight_range=(0, 2))
    
    if network_trained:
        print(f"Network trained successfully in {epoch} epochs. Final weights: {final_weights}")
    else:
        print(f"Failed to train after {epoch} epochs. Moving to next training with bias adjustments.")

    # If not trained, try training with bias adjustments
    if not network_trained:
        print("Trying different biases between 0.1 and 1.3:")
        for bias in np.arange(0.1, max_bias + bias_increment, bias_increment):
            print(f"Attempt with bias {bias}:")
            final_weights, epoch, network_trained = train_network(inputs, targets, max_iterations, bias, weight_range=(0, 2))
            if network_trained:
                print(f"Network trained successfully in {epoch} epochs. Final weights: {final_weights}")
                break  # Stop if trained

    # If still not trained, try with negative weights and bias range -0.9 to 0.9
    if not network_trained:
        print("Increasing bias until max, then try decreasing with weights between -2 and 2.")
        for bias in np.arange(max_bias, min_bias - bias_increment, -bias_increment):
            print(f"Attempt with bias {bias}:")
            final_weights, epoch, network_trained = train_network(inputs, targets, max_iterations, bias, weight_range=(-2, 2))
            if network_trained:
                print(f"Network trained successfully in {epoch} epochs. Final weights: {final_weights}")
                break  # Stop if trained

    # If network still not trained, print the status
    if not network_trained:
        print(f"Network failed to train for truth table {table_index}.")
    else:
        print(f"Successfully trained network for table {table_index}. Final weights: {final_weights}")
