"""
Simpy-based Event-Driven Simulator for LEACH and LEACH-C Protocols.

This module wraps the LEACH and LEACH-C protocols in a discrete event simulation
framework using Simpy, allowing for fine-grained control over node movements,
cluster head elections, and communication events.

Key Features:
- Event-driven architecture using Simpy's Environment
- Discrete time steps for each protocol round
- Node mobility as separate events
- Metrics collection at defined intervals
"""

import simpy
from typing import List, Dict
from node import Node
from config import ENABLE_MOBILITY


class EventDrivenNetworkSimulator:
    """
    Lightweight event-driven simulator using Simpy framework.

    Uses discrete events for protocol rounds and mobility. Simpler than full
    concurrent process model - each round is one event with defined substeps.

    Args:
        protocol: Protocol instance (LEACH or LEACHC)
        nodes: List of Node objects
        round_duration: Simulated time per round (default 1.0)
    """

    def __init__(self, protocol, nodes: List[Node], round_duration: float = 1.0):
        self.env = simpy.Environment()
        self.protocol = protocol
        self.nodes = nodes
        self.round_duration = round_duration
        self.events_log = []

    def _log_event(self, event_type: str, round_num: int = 0, details: Dict = None):
        """Log a discrete event (DRY: single logging method)."""
        self.events_log.append({
            'time': self.env.now,
            'event': event_type,
            'round': round_num,
            **(details or {})
        })

    def _execute_round_event(self, round_num: int):
        """
        Execute one round as a discrete event.
        Substeps: elect CHs → form clusters → communicate → mobility
        """
        self.protocol.run_round()
        alive_count = sum(1 for n in self.nodes if n.is_alive)

        self._log_event('ROUND_COMPLETE', round_num, {
            'alive_nodes': alive_count,
            'avg_energy': sum(n.energy for n in self.nodes) / len(self.nodes) if self.nodes else 0
        })

        return alive_count > 0

    def simulation_process(self, num_rounds: int):
        """Simpy process: Execute all rounds as discrete events."""
        for round_num in range(num_rounds):
            yield self.env.timeout(self.round_duration)

            if not self._execute_round_event(round_num):
                break  # All nodes dead

    def run_simulation(self, num_rounds: int) -> Dict:
        """Run the event-driven simulation."""
        self.env.process(self.simulation_process(num_rounds))
        self.env.run()
        return self.protocol.get_metrics(num_rounds)

    def get_events_log(self) -> List[Dict]:
        """Get the event log."""
        return self.events_log


if __name__ == "__main__":
    """
    Demo: Event-driven simulation with Simpy.
    Shows how discrete events are managed by Simpy framework.
    """
    import random
    from leach import LEACH
    from config import FIELD_WIDTH, FIELD_HEIGHT, INITIAL_ENERGY

    print("=" * 70)
    print("SIMPY EVENT-DRIVEN SIMULATOR DEMONSTRATION")
    print("=" * 70)

    # Create test nodes
    random.seed(42)
    test_nodes = []
    for i in range(20):  # Small network for demo
        x = random.uniform(0, FIELD_WIDTH)
        y = random.uniform(0, FIELD_HEIGHT)
        test_nodes.append(Node(i, x, y, INITIAL_ENERGY))

    # Create Simpy-based simulator
    protocol = LEACH(test_nodes, probability_ch=0.05, packet_size=2000)
    simpy_sim = EventDrivenNetworkSimulator(protocol, test_nodes)

    print("\nInitializing event-driven simulator with Simpy...")
    print(f"Initial nodes: {len(test_nodes)}")
    print("Running 50 rounds with discrete event model...")

    metrics = simpy_sim.run_simulation(num_rounds=50)

    # Display results
    print(f"\nSimulation completed at time {simpy_sim.env.now}s")
    print(f"Total discrete events logged: {len(simpy_sim.events_log)}")
    print(f"Final alive nodes: {metrics['final_alive_nodes']}")
    print(f"First Dead Node (FDN): {metrics['first_dead_node_round']}")
    print(f"First Muted Round (FMR): {metrics['first_muted_round']}")
    print(f"DLBI: {metrics['dlbi']:.4f}")
    print(f"RSPI: {metrics['rspi']:.4f}")
