#!/usr/bin/env python3
# -*- coding: utf-8 -*-

"""
================================================================================
GRNexus - XOR Problem Example (Python)
================================================================================

This example demonstrates how to solve the classic XOR problem using GRNexus.
The XOR problem is a fundamental test for neural networks because it's not
linearly separable, requiring at least one hidden layer.

XOR Truth Table:
  Input A | Input B | Output
  --------|---------|--------
     0    |    0    |   0
     0    |    1    |   1
     1    |    0    |   1
     1    |    1    |   0

Author: GR Code Digital Solutions
License: GPL-3.0
================================================================================
"""

from grnexus import NeuralNetwork
from lib.grnexus_layers import DenseLayer
from lib.grnexus_activations import Tanh, Sigmoid

print("=" * 80)
print("GRNexus - XOR Problem Example")
print("=" * 80)
print()

# ============================================================================
# 1. PREPARE DATA
# ============================================================================
print("[1] Preparing XOR dataset...")

# XOR inputs
x_train = [
    [0.0, 0.0],  # Input: 0 XOR 0
    [0.0, 1.0],  # Input: 0 XOR 1
    [1.0, 0.0],  # Input: 1 XOR 0
    [1.0, 1.0]   # Input: 1 XOR 1
]

# XOR outputs
y_train = [
    [0.0],  # Output: 0
    [1.0],  # Output: 1
    [1.0],  # Output: 1
    [0.0]   # Output: 0
]

print(f"  Training samples: {len(x_train)}")
print(f"  Input features: {len(x_train[0])}")
print(f"  Output features: {len(y_train[0])}")
print()

# ============================================================================
# 2. BUILD MODEL
# ============================================================================
print("[2] Building neural network...")

model = NeuralNetwork(
    loss='mse',              # Mean Squared Error for regression
    optimizer='sgd',         # Stochastic Gradient Descent
    learning_rate=1.0,       # High learning rate for fast convergence
    name='xor_solver'
)

# Hidden layer: 8 neurons with Tanh activation
# More neurons help the network learn the XOR pattern better
model.add(DenseLayer(
    units=8,
    input_dim=2,
    activation=Tanh()
))

# Output layer: 1 neuron with Sigmoid activation
# Sigmoid outputs values in [0, 1], perfect for binary classification
model.add(DenseLayer(
    units=1,
    input_dim=8,
    activation=Sigmoid()
))

print()
model.summary()
print()

# ============================================================================
# 3. TRAIN MODEL
# ============================================================================
print("[3] Training model...")
print("  Epochs: 5000")
print("  Batch size: 4")
print()

history = model.train(
    x_train,
    y_train,
    epochs=5000,
    batch_size=4,
    verbose=False  # Set to True to see training progress
)

# Show training progress
initial_loss = history['loss'][0]
final_loss = history['loss'][-1]
improvement = ((initial_loss - final_loss) / initial_loss * 100)

print(f"  Initial loss: {initial_loss:.6f}")
print(f"  Final loss: {final_loss:.6f}")
print(f"  Improvement: {improvement:.2f}%")
print()

# ============================================================================
# 4. TEST MODEL
# ============================================================================
print("[4] Testing predictions...")
print()
print("  Input  | Expected | Predicted | Rounded | Correct?")
print("  -------|----------|-----------|---------|----------")

correct = 0
for i, input_data in enumerate(x_train):
    expected = y_train[i][0]
    prediction = model.predict([input_data])[0][0]
    rounded = round(prediction)
    is_correct = abs(rounded - expected) < 0.01
    if is_correct:
        correct += 1
    
    status = "✓" if is_correct else "✗"
    print(f"  {input_data} | {expected:.1f}      | {prediction:.6f}  | {rounded:.1f}     | {status}")

accuracy = (correct / len(x_train) * 100)
print()
print(f"  Accuracy: {accuracy:.2f}%")
print()

# ============================================================================
# 5. SAVE MODEL
# ============================================================================
print("[5] Saving model...")

model.save('python/xor_model.nexus')
print("  Model saved to: xor_model.nexus")
print("  This model can be loaded in Ruby too!")
print()

# ============================================================================
# 6. DEMONSTRATE CROSS-LANGUAGE COMPATIBILITY
# ============================================================================
print("[6] Model inspection...")
print()

NeuralNetwork.inspect_model('python/xor_model.nexus')

# ============================================================================
# 7. BONUS: VISUALIZE DECISION BOUNDARY
# ============================================================================
print()
print("[7] Decision boundary visualization (text-based)...")
print()
print("  1.0 |")

# Create a grid and test predictions
for y in range(10, -1, -1):
    y_val = y / 10.0
    print(f"  {y_val:.1f} |", end="")
    
    for x in range(11):
        x_val = x / 10.0
        prediction = model.predict([[x_val, y_val]])[0][0]
        
        # Use different characters based on prediction
        if prediction < 0.3:
            char = '·'  # Predict 0
        elif prediction > 0.7:
            char = '█'  # Predict 1
        else:
            char = '▒'  # Uncertain
        
        print(char, end="")
    print()

print("      " + "-" * 11)
print("      0.0       1.0")
print()
print("  Legend: · = 0 (low), ▒ = uncertain, █ = 1 (high)")
print()

# ============================================================================
# 8. BONUS: INTERACTIVE TESTING
# ============================================================================
print("[8] Interactive testing...")
print()
print("  Try your own inputs!")
print()

test_cases = [
    [0.0, 0.0],
    [0.0, 1.0],
    [1.0, 0.0],
    [1.0, 1.0],
    [0.5, 0.5],  # Interesting edge case
    [0.2, 0.8],  # Another edge case
]

for test_input in test_cases:
    prediction = model.predict([test_input])[0][0]
    result = "1" if prediction > 0.5 else "0"
    print(f"  {test_input[0]:.1f} XOR {test_input[1]:.1f} = {result} (confidence: {abs(prediction - 0.5) * 200:.1f}%)")

print()

# ============================================================================
# 9. DEMONSTRATE LOADING IN PYTHON
# ============================================================================
print("[9] Testing model reload...")
print()

# Load the saved model
loaded_model = NeuralNetwork.load('ruby/xor_model.nexus')
print("  Model loaded successfully!")
print()

# Test with the loaded model
print("  Testing loaded model:")
test_input = [1.0, 0.0]
original_pred = model.predict([test_input])[0][0]
loaded_pred = loaded_model.predict([test_input])[0][0]

print(f"    Input: {test_input}")
print(f"    Original model prediction: {original_pred:.6f}")
print(f"    Loaded model prediction: {loaded_pred:.6f}")
print(f"    Match: {'✓' if abs(original_pred - loaded_pred) < 0.0001 else '✗'}")
print()

# ============================================================================
# SUMMARY
# ============================================================================
print("=" * 80)
print("SUMMARY")
print("=" * 80)
print()
print("✓ XOR problem solved successfully!")
print(f"✓ Model trained with {len(history['loss'])} epochs")
print(f"✓ Final accuracy: {accuracy:.2f}%")
print("✓ Model saved and ready for cross-language use")
print()
print("Next steps:")
print("  1. Try loading this model in Ruby:")
print("     ruby -e \"require_relative 'ruby/grnexus'; m = GRNexus::NeuralNetwork.load('xor_model.nexus')\"")
print()
print("  2. Experiment with different architectures:")
print("     - More hidden neurons (try 8 or 16)")
print("     - Different activations (ReLU, GELU, Swish)")
print("     - Multiple hidden layers")
print()
print("  3. Try other classic problems:")
print("     - AND, OR, NAND gates")
print("     - Iris classification")
print("     - MNIST digits")
print()
print("  4. Explore advanced features:")
print("     - Add Dropout for regularization")
print("     - Use BatchNormalization")
print("     - Try different optimizers (adam, rmsprop)")
print("     - Add callbacks (EarlyStopping, ReduceLROnPlateau)")
print()
print("=" * 80)
print()
print("Happy learning with GRNexus! 🚀")
print()
