"""03 - Network Formation: Building Connections Step by Step

PHYSICS: Demonstrates how individual nodes create network-level coherence.
LEARNING: Understand network topology effects on structural stability.

This shows the emergence of collective behavior from individual dynamics.
"""

import numpy as np
import networkx as nx
import matplotlib.pyplot as plt
import matplotlib
import os

# Configurar fuente para mejor soporte de Unicode
matplotlib.rcParams['font.family'] = 'sans-serif'
matplotlib.rcParams['font.sans-serif'] = ['DejaVu Sans', 'Arial', 'sans-serif']
# Suprimir warnings de glifos faltantes
import warnings
warnings.filterwarnings('ignore', 'Glyph .* missing from font.*')


def simple_coherence_measure(G):
    """Compute a simple coherence measure based on network structure."""
    if G.number_of_nodes() == 0:
        return 0.0
    
    # Simple coherence: connectivity + phase alignment + frequency harmony
    connectivity = G.number_of_edges() / (G.number_of_nodes() * (G.number_of_nodes() - 1) / 2)
    
    # Phase coherence: how aligned are the phases?
    phases = [G.nodes[n].get('theta', 0) for n in G.nodes()]
    if len(phases) > 1:
        # Convert to unit vectors and compute alignment
        phase_vectors = np.array([[np.cos(p), np.sin(p)] for p in phases])
        mean_vector = np.mean(phase_vectors, axis=0)
        phase_coherence = np.linalg.norm(mean_vector)
    else:
        phase_coherence = 1.0
    
    # Frequency harmony: how similar are the frequencies?
    frequencies = [G.nodes[n].get('nf', 1.0) for n in G.nodes()]
    if len(frequencies) > 1:
        freq_std = np.std(frequencies)
        freq_harmony = 1.0 / (1.0 + freq_std)
    else:
        freq_harmony = 1.0
    
    # Combine measures
    total_coherence = (connectivity * phase_coherence * freq_harmony) ** (1/3)
    return total_coherence


def network_formation_demo():
    """Demonstrate step-by-step network formation and coherence evolution."""
    
    print("=" * 75)
    print(" " * 20 + "🕸️ NETWORK FORMATION DYNAMICS 🕸️")
    print("=" * 75)
    print()
    print("Building networks step by step and watching coherence emerge...")
    print("PHYSICS: Individual nodes → structural connections → collective coherence")
    print("INSIGHT: Network topology determines information flow and stability")
    print()
    
    # Create different network formation strategies
    n_nodes = 8
    
    def test_formation_strategy(strategy_name, formation_function, description):
        """Test a network formation strategy."""
        
        print(f"🏗️ {strategy_name}")
        print(f"   Strategy: {description}")
        
        # Start with isolated nodes
        G = nx.Graph()
        G.add_nodes_from(range(n_nodes))
        
        # Initialize node properties randomly but consistently
        np.random.seed(42)  # Reproducible
        for node in G.nodes():
            G.nodes[node]['theta'] = np.random.uniform(0, 2*np.pi)  # Random phases
            G.nodes[node]['nf'] = 1.0 + 0.2 * np.random.normal()    # Frequency variation
        
        print(f"   Initial coherence: {simple_coherence_measure(G):.3f}")
        
        # Apply formation strategy
        formation_function(G)
        
        final_coherence = simple_coherence_measure(G)
        print(f"   Final coherence: {final_coherence:.3f}")
        print(f"   Edges created: {G.number_of_edges()}")
        print(f"   Connectivity: {G.number_of_edges()}/{n_nodes*(n_nodes-1)//2} = {2*G.number_of_edges()/(n_nodes*(n_nodes-1)):.2%}")
        print()
        
        return G, final_coherence
    
    print("🔬 NETWORK FORMATION EXPERIMENTS")
    print("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━")
    print()
    
    results = {}
    
    # Strategy 1: Random connections
    def random_formation(G):
        """Add random edges until desired density."""
        np.random.seed(42)
        nodes = list(G.nodes())
        target_edges = n_nodes  # Average degree = 2
        
        while G.number_of_edges() < target_edges:
            i, j = np.random.choice(nodes, 2, replace=False)
            if not G.has_edge(i, j):
                G.add_edge(i, j)
    
    G1, coh1 = test_formation_strategy(
        "Random Formation",
        random_formation,
        "Connect nodes randomly until target density"
    )
    results["Random"] = coh1
    
    # Strategy 2: Preferential attachment (rich get richer)
    def preferential_formation(G):
        """Add edges preferentially to high-degree nodes."""
        nodes = list(G.nodes())
        edges_to_add = n_nodes
        
        for _ in range(edges_to_add):
            # Choose first node randomly
            i = np.random.choice(nodes)
            
            # Choose second node preferentially by degree
            degrees = dict(G.degree())
            if sum(degrees.values()) == 0:
                # No edges yet, connect randomly
                j = np.random.choice([n for n in nodes if n != i])
            else:
                # Preferential attachment
                weights = [degrees[n] + 1 for n in nodes if n != i and not G.has_edge(i, n)]
                if weights:
                    candidates = [n for n in nodes if n != i and not G.has_edge(i, n)]
                    weights = np.array(weights, dtype=float)
                    weights /= weights.sum()
                    j = np.random.choice(candidates, p=weights)
                else:
                    continue  # Skip if no valid candidates
            
            G.add_edge(i, j)
    
    G2, coh2 = test_formation_strategy(
        "Preferential Attachment",
        preferential_formation,
        "Rich nodes get richer (scale-free network)"
    )
    results["Preferential"] = coh2
    
    # Strategy 3: Phase-guided formation (TNFR-inspired)
    def phase_guided_formation(G):
        """Connect nodes with similar phases preferentially."""
        nodes = list(G.nodes())
        target_edges = n_nodes
        
        for _ in range(target_edges):
            # Find the pair with smallest phase difference
            best_pair = None
            min_phase_diff = float('inf')
            
            for i in nodes:
                for j in nodes:
                    if i < j and not G.has_edge(i, j):
                        phase_diff = abs(G.nodes[i]['theta'] - G.nodes[j]['theta'])
                        # Handle circular distance
                        phase_diff = min(phase_diff, 2*np.pi - phase_diff)
                        
                        if phase_diff < min_phase_diff:
                            min_phase_diff = phase_diff
                            best_pair = (i, j)
            
            if best_pair:
                G.add_edge(*best_pair)
    
    G3, coh3 = test_formation_strategy(
        "Phase-Guided Formation", 
        phase_guided_formation,
        "Connect nodes with similar phases (TNFR coupling)"
    )
    results["Phase-Guided"] = coh3
    
    # Strategy 4: Ring formation (structured)
    def ring_formation(G):
        """Form a ring structure for guaranteed connectivity."""
        nodes = list(G.nodes())
        # Create ring
        for i in range(len(nodes)):
            G.add_edge(nodes[i], nodes[(i+1) % len(nodes)])
        
        # Add a few random shortcuts
        for _ in range(2):
            i, j = np.random.choice(nodes, 2, replace=False)
            if not G.has_edge(i, j):
                G.add_edge(i, j)
    
    G4, coh4 = test_formation_strategy(
        "Ring + Shortcuts",
        ring_formation,
        "Ring topology with random shortcuts"
    )
    results["Ring"] = coh4
    
    print("📊 FORMATION STRATEGY COMPARISON")
    print("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━")
    print()
    
    # Rank strategies by final coherence
    sorted_results = sorted(results.items(), key=lambda x: x[1], reverse=True)
    
    print("🏆 COHERENCE RANKINGS:")
    for i, (strategy, coherence) in enumerate(sorted_results, 1):
        medal = "🥇" if i == 1 else "🥈" if i == 2 else "🥉" if i == 3 else "  "
        print(f"   {medal} {i}. {strategy:<20}: {coherence:.3f}")
    
    print()
    print("🎯 NETWORK FORMATION INSIGHTS:")
    print("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━")
    print()
    print("🏗️ FORMATION PRINCIPLES:")
    print("   • Structure emerges from connection strategy")
    print("   • Random → low coherence, high variance")
    print("   • Preferential → hubs, but potential fragility")
    print("   • Phase-guided → natural TNFR coupling")
    print("   • Structured → guaranteed connectivity")
    print()
    print("⚛️ TNFR PHYSICS:")
    print("   • Phase similarity enables stable coupling")
    print("   • Frequency harmony reduces structural stress")
    print("   • Network topology determines information flow")
    print("   • Coherence emerges from compatible connections")
    print()
    print("📊 FORMATION STRATEGIES:")
    print("   Random: Simple but unpredictable")
    print("   Preferential: Creates hubs and shortcuts")
    print("   Phase-guided: Mimics natural TNFR coupling")
    print("   Structured: Guarantees basic connectivity")
    print()
    print("🧠 REAL-WORLD APPLICATIONS:")
    print("   • Social networks form through similarity")
    print("   • Neural networks wire by activity patterns")
    print("   • Economic networks follow preferential attachment")
    print("   • Molecular networks use energy compatibility")
    print()
    
    # Show topology visualization
    if not os.path.exists('output'):
        os.makedirs('output')
    
    fig, axes = plt.subplots(2, 2, figsize=(12, 10))
    networks = [G1, G2, G3, G4]
    names = ["Random", "Preferential", "Phase-Guided", "Ring + Shortcuts"]
    coherences = [coh1, coh2, coh3, coh4]
    
    for i, (G, name, coh) in enumerate(zip(networks, names, coherences)):
        ax = axes[i//2, i%2]
        
        # Color nodes by phase
        phases = [G.nodes[n]['theta'] for n in G.nodes()]
        
        pos = nx.spring_layout(G, seed=42)
        nx.draw(G, pos, ax=ax, 
                node_color=phases, 
                node_size=300,
                cmap='hsv',
                with_labels=True,
                edge_color='gray',
                alpha=0.7)
        
        ax.set_title(f"{name}\nCoherence: {coh:.3f}", fontsize=12)
    
    plt.suptitle("🕸️ Network Formation Strategies", fontsize=16, fontweight='bold')
    plt.tight_layout()
    plt.savefig('output/network_formation_comparison.png', dpi=300, bbox_inches='tight')
    print("📊 Network topology visualization saved to: output/network_formation_comparison.png")
    
    best_strategy, best_coherence = sorted_results[0]
    print(f"🌟 Best formation strategy: {best_strategy} (coherence: {best_coherence:.3f})")
    print("🕸️ Network structure determines collective coherence!")


if __name__ == "__main__":
    network_formation_demo()