#![allow(dead_code)]

use std::time::{SystemTime, UNIX_EPOCH};
use bimoh::BimoH;

pub struct Node {
    pub activity: f32,
    pub d_i: f32,
    pub last_accessed: u64,
    pub id: u32,
}

pub struct Anillo {
    pub id: u8,
    pub nodes: Vec<Node>,
    pub memcons: Vec<u32>,
}

pub struct Atink {
    pub source: u32,
    pub target: u32,
    pub weight: f32,
}

pub struct MallaMulti {
    pub anillos: Vec<Anillo>,
    pub atinks: Vec<Atink>,
    pub bimoh: BimoH, // plasticidad fractal (Axioma TNEC-2/4)
}

impl MallaMulti {
    /// Crea una malla con N anillos, cada uno con M nodos.
    /// `ig_threshold`, `lambda` y `beta` controlan la plasticidad vía BimoH.
    pub fn new(num_rings: u8, num_nodes: usize, ig_threshold: f32, lambda: f32, beta: f32) -> Self {
        let mut anillos = Vec::new();
        for ring_id in 0..num_rings {
            let nodes: Vec<Node> = (0..num_nodes)
                .map(|_| Node { activity: 0.0, d_i: 0.5, last_accessed: 0, id: 0 })
                .collect();
            anillos.push(Anillo { id: ring_id, nodes, memcons: Vec::new() });
        }
        Self { anillos, atinks: Vec::new(), bimoh: BimoH::new(ig_threshold, lambda, beta) }
    }

    pub fn inject(&mut self, id: u32, ring: u8) {
        let idx = (id as usize) % self.anillos[ring as usize].nodes.len();
        let anillo = &mut self.anillos[ring as usize];
        let now = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs();
        anillo.nodes[idx].activity = (anillo.nodes[idx].activity + 0.3).min(1.0);
        anillo.nodes[idx].id = id;
        anillo.nodes[idx].last_accessed = now;
    }

    /// Propaga actividad Y actualiza plasticidad fractal vía BimoH.
    /// d_i(t+1) = d_i(t) - λ(1 - a_i(t)) + β Σ a_j(t)
    pub fn step(&mut self) {
        for anillo in &mut self.anillos {
            let len = anillo.nodes.len();
            let old: Vec<f32> = anillo.nodes.iter().map(|n| n.activity).collect();
            let mut new_act = vec![0.0f32; len];
            for i in 0..len {
                new_act[i] = (old[(i + len - 1) % len] + old[(i + 1) % len]) * 0.5;
            }
            for i in 0..len {
                anillo.nodes[i].activity = new_act[i];
                let left = old[(i + len - 1) % len];
                let right = old[(i + 1) % len];
                anillo.nodes[i].d_i = self.bimoh.update_d_i(anillo.nodes[i].d_i, old[i], (left, right));
            }
        }
    }

    pub fn activity_snapshot(&self) -> Vec<Vec<f32>> {
        self.anillos.iter().map(|a| a.nodes.iter().map(|n| n.activity).collect()).collect()
    }

    pub fn converged(snap1: &[Vec<f32>], snap2: &[Vec<f32>], epsilon: f32) -> bool {
        for (r1, r2) in snap1.iter().zip(snap2.iter()) {
            for (a, b) in r1.iter().zip(r2.iter()) {
                if (a - b).abs() > epsilon { return false; }
            }
        }
        true
    }

    /// Consolidación con threshold de actividad. La d_i se actualizó en step() vía BimoH.
    pub fn consolidate(&mut self, threshold: f32) -> Vec<u32> {
        let mut memcons = Vec::new();
        for anillo in &mut self.anillos {
            anillo.memcons.clear();
            for node in &anillo.nodes {
                if node.activity >= threshold && node.id != 0 {
                    memcons.push(node.id);
                    anillo.memcons.push(node.id);
                }
            }
        }
        memcons
    }

    /// Consolidación con complejidad = activity × (1 + d_i) para LCC.
    pub fn consolidate_with_activity(&mut self, threshold: f32) -> Vec<(u32, f32)> {
        let mut items = Vec::new();
        for anillo in &mut self.anillos {
            anillo.memcons.clear();
            for node in &anillo.nodes {
                if node.activity >= threshold && node.id != 0 {
                    let complexity = node.activity * (1.0 + node.d_i);
                    items.push((node.id, complexity));
                    anillo.memcons.push(node.id);
                }
            }
        }
        items
    }

    pub fn create_atinks(&mut self, window: u64) {
        let now = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs();
        self.atinks.clear();
        for anillo in &self.anillos {
            let recent: Vec<(u32, f32)> = anillo.nodes.iter()
                .filter(|n| n.id != 0 && (now - n.last_accessed) <= window
                        && n.d_i >= 1.8 && n.d_i <= 2.5)
                .map(|n| (n.id, n.d_i))
                .collect();
            for i in 0..recent.len() {
                for j in (i + 1)..recent.len() {
                    let weight = ((recent[i].1 + recent[j].1) / 5.0).clamp(0.1, 1.0);
                    self.atinks.push(Atink { source: recent[i].0, target: recent[j].0, weight });
                }
            }
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test] fn new_creates_three_rings() {
        let m = MallaMulti::new(3, 64, 0.1, 0.015, 0.07);
        assert_eq!(m.anillos.len(), 3);
        assert_eq!(m.anillos[0].nodes.len(), 64);
    }

    #[test] fn inject_sets_activity() {
        let mut m = MallaMulti::new(3, 64, 0.1, 0.015, 0.07);
        m.inject(42, 0);
        assert!(m.anillos[0].nodes[42 % 64].activity > 0.0);
    }

    #[test] fn inject_in_correct_ring() {
        let mut m = MallaMulti::new(3, 64, 0.1, 0.015, 0.07);
        m.inject(42, 0);
        m.inject(99, 1);
        assert!(m.anillos[0].nodes.iter().any(|n| n.activity > 0.0));
        assert!(m.anillos[1].nodes.iter().any(|n| n.activity > 0.0));
        assert!(m.anillos[2].nodes.iter().all(|n| n.activity < 0.01));
    }

    #[test] fn step_uses_bimoh_update_d_i() {
        let mut m = MallaMulti::new(3, 8, 0.1, 0.1, 0.2);
        m.inject(2, 0); m.inject(3, 0);
        m.step();
        // d_i = 0.5 - 0.1*(1-0.3) + 0.2*(0+0.3) = 0.5 - 0.07 + 0.06 = 0.49
        assert!((m.anillos[0].nodes[2].d_i - 0.49).abs() < 0.001);
        assert!((m.anillos[0].nodes[3].d_i - 0.49).abs() < 0.001);
        // BimoH está conectado: los parámetros vienen del objeto BimoH
        assert_eq!(m.bimoh.lambda, 0.1);
        assert_eq!(m.bimoh.beta, 0.2);
    }

    #[test] fn step_decreases_d_i_for_inactive() {
        let mut m = MallaMulti::new(3, 4, 0.1, 0.1, 0.01);
        let d0 = m.anillos[0].nodes[0].d_i;
        m.step();
        assert!(m.anillos[0].nodes[0].d_i < d0);
    }

    #[test] fn consolidate_returns_active_nodes() {
        let mut m = MallaMulti::new(3, 64, 0.1, 0.015, 0.07);
        m.inject(42, 0); m.inject(42, 0);
        assert!(!m.consolidate(0.3).is_empty());
        assert!(m.consolidate(0.7).is_empty());
    }

    #[test] fn consolidate_no_activity() {
        assert!(MallaMulti::new(3, 64, 0.1, 0.015, 0.07).consolidate(0.01).is_empty());
    }

    #[test] fn step_multiple_times_preserves_nodes() {
        let mut m = MallaMulti::new(3, 16, 0.1, 0.015, 0.07);
        m.inject(7, 0);
        for _ in 0..10 { m.step(); }
        assert_eq!(m.anillos[0].nodes.len(), 16);
    }

    #[test] fn bimoh_entropy_integrated() {
        let m = MallaMulti::new(3, 64, 0.1, 0.015, 0.07);
        let acts: Vec<f32> = m.anillos[0].nodes.iter().map(|n| n.activity).collect();
        let h = BimoH::entropy(&acts);
        assert!(h >= 0.0);
    }
}
