import numpy as np

# Parameters
learning_rate = 0.1
max_iterations = 1000

# Activation function with dynamic thresholding
def activation_function(weighted_sum, threshold=0.5):
    return 1 if weighted_sum > threshold else 0

# Inputs for XOR truth table
inputs = np.array([[0, 0], [0, 1], [1, 0], [1, 1]])
targets = [0, 1, 1, 0]  # XOR Truth Table

# Initialize weights (hidden and output layers)
weights_input_to_hidden = np.random.rand(3)  # Weights for N1, N2, bias → N3
weights_hidden_to_output = np.random.rand(2)  # Weights for N3, bias → N4

# Training loop
epoch = 0
network_trained = False

while epoch < max_iterations:
    epoch += 1
    all_correct = True

    for input_vector, target in zip(inputs, targets):
        N1, N2 = input_vector
        bias = 1  # Bias input

        # Forward pass
        N3_input = np.dot(np.append(input_vector, bias), weights_input_to_hidden)
        N3 = activation_function(N3_input)  # Output of hidden neuron

        N4_input = (N3 * weights_hidden_to_output[0] +  # From hidden neuron
                    bias * weights_hidden_to_output[1])  # Bias contribution
        N4 = activation_function(N4_input)  # Final output

        # Error calculation
        error = target - N4

        # Weight updates if error exists
        if error != 0:
            all_correct = False

            # Update weights for N3 → N4
            weights_hidden_to_output[0] += learning_rate * error * N3
            weights_hidden_to_output[1] += learning_rate * error * bias

            # Update weights for N1, N2, bias → N3
            weights_input_to_hidden += learning_rate * error * np.append(input_vector, bias)

    if all_correct:
        network_trained = True
        break

# Results
if network_trained:
    print(f"The network learned the XOR truth table correctly after {epoch} iterations.")
else:
    print(f"The network failed to learn the XOR truth table after {epoch} iterations.")

# Testing the trained network
print("\nTesting the trained network:")
for input_vector, target in zip(inputs, targets):
    N1, N2 = input_vector
    bias = 1

    # Forward pass
    N3_input = np.dot(np.append(input_vector, bias), weights_input_to_hidden)
    N3 = activation_function(N3_input)

    N4_input = (N3 * weights_hidden_to_output[0] +
                bias * weights_hidden_to_output[1])
    N4 = activation_function(N4_input)

    print(f"Input: {input_vector}, Target: {target}, Output: {N4}")
