/*
    Appellation: vnode <module>
    Contrib: @FL03
*/
//! # Virtual Node ([`Vnode`])
//!
//! A virtual speaks to a virtualized workspace automatically allocated by the runtime during
//! the initialization process. These objects are responsible for maintaining their internal
//! _plants_, or engines, equipping them with everything needed to successfully execute a given
//! workload.
//!

mod impl_neural;
mod impl_vnode;

use crate::ctx::ActorContext;
use crate::drivers::{Driver, NeuralPlant, RawDriver, WolframPlant};
use crate::mem::TopoLedger;
use crate::operators::{Agent, Observer, Operator, OperatorKind};
use crate::surface::{PointKind, SurfaceNetwork};
use crate::traits::Actor;
use crate::types::VirtualMemoryAnalysis;
use rstmt::nrt::{LPR, Triad, Triads};
use rstmt::{Aspn as Note, PitchMod};

use ndarray::Array2;
use num_traits::{Float, FromPrimitive, NumAssign, ToPrimitive};
use scsys::Id;
use std::collections::HashMap;
use std::time::Instant;

/// a type alias denoting a [VNode] equipped with a [NeuralPlant]
pub type NeuralNode<T = f32> = VNode<NeuralPlant<T>, T>;
/// a type alias denoting a [VNode] equipped with a [WolframPlant]
pub type WolframNode<T = f32> = VNode<WolframPlant, T>;

/// This struct manifests the partitions automatically allocated by the runtime as virtualized
/// workspaces responsible for maintaining their plant's. Their capabilities are defined by the
/// current operator, allowing a node to become an observer, agent, etc
///
/// Node's equip their plants (or drivers) with everything needed to successfully execute a
/// given transaction, providing storage and networking capabilities while abstracting away
/// platform-specific logic. This enables plant's to retain their topological and mathematical
/// properties and reduces overhead in the process.
///
/// VNode's are rely on a topological memory system to facilitate various operations. This
/// memory system is responsible for storing and managing the topological features of the
/// plant, such as the current state, critical points, and navigational history.
///
/// Additionally, each instance is equipped with its own neural network designed to
/// _materialize_ the surface of the node's headspace. This process specifically speaks to the
/// dynamic configuration of a neural network with respect to a topological entity. This
/// provides the system with an additional degree of freedom when it comes to learning as
/// certain engines are capable of learning the _rules_ of headspace leaving the surface to
/// predict and learn more traditional patterns.
#[derive(Clone, Debug)]
pub struct VNode<D, T = f32>
where
    D: RawDriver<Triad>,
{
    pub(crate) id: Id,
    /// a fully-managed topological unit of compute
    pub(crate) driver: D,
    /// the node operator
    pub(crate) operator: Operator<T>,
    /// Topological memory system
    pub(crate) store: TopoLedger<T>,
    /// Neural surface network for behavior learning within the current headspace
    pub(crate) surface: Option<SurfaceNetwork<T>>,
    /// Training momentum for surface network
    pub(crate) prev_weight_changes: Option<(Array2<T>, Array2<T>)>,
    /// Critical points in tonal space (parameter name -> pitch class)
    pub(crate) critical_points: HashMap<String, usize>,
    /// Reference to previously visited tonics
    pub(crate) tonic_history: Vec<usize>,
    /// Last context calculation
    pub(crate) last_context: Option<(ActorContext<T>, Instant)>,
}

impl<D, T> VNode<D, T>
where
    D: RawDriver<Triad>,
{
    /// initialize a new virual node with a default driver
    pub fn new() -> Self
    where
        D: Default,
        T: Float + FromPrimitive + ToPrimitive,
    {
        let plant = D::default();
        Self::from_driver(plant)
    }
    /// Create a new virtual node with topological memory
    pub fn from_driver(plant: D) -> Self
    where
        T: Float + FromPrimitive + ToPrimitive,
    {
        let memory = TopoLedger::new();
        let id: Id;
        #[cfg(feature = "rand")]
        {
            id = Id::<u128>::random().map(|id| id as usize);
        }
        #[cfg(not(feature = "rand"))]
        {
            id = Id::atomic();
        }
        let mut vnode = VNode {
            id,
            critical_points: HashMap::new(),
            driver: plant,
            last_context: None,
            operator: Operator::Observer(Observer::new()),
            prev_weight_changes: None,
            store: memory,
            surface: None,
            tonic_history: Vec::new(),
        };

        // Initialize by recording the initial state
        vnode.record_state();

        vnode
    }
    /// initialize a new virtual node from the given headspace
    pub fn from_headspace(headspace: Triad) -> Self
    where
        D: Driver<Triad>,
        T: Float + FromPrimitive + ToPrimitive,
    {
        let plant = D::from_headspace(headspace);
        Self::from_driver(plant)
    }
    /// returns a copy of the node id
    pub const fn id(&self) -> Id {
        self.id
    }
    /// Return the triad class of the underlying plant
    pub fn class(&self) -> Triads {
        self.driver().headspace().class()
    }
    /// return an immutable reference to the critical points
    pub const fn critical_points(&self) -> &HashMap<String, usize> {
        &self.critical_points
    }
    /// return a mutable reference to the critical points
    pub const fn critical_points_mut(&mut self) -> &mut HashMap<String, usize> {
        &mut self.critical_points
    }
    /// return an immutable reference to the plant
    pub const fn driver(&self) -> &D {
        &self.driver
    }
    /// return a mutable reference to the plant
    pub const fn driver_mut(&mut self) -> &mut D {
        &mut self.driver
    }

    /// returns a mutable reference to the node's headspace
    #[inline]
    pub fn headspace_mut(&mut self) -> &mut Triad {
        self.driver.headspace_mut()
    }
    /// returns an immutable reference to the last context
    pub fn last_context(&self) -> Option<&(ActorContext<T>, Instant)> {
        self.last_context.as_ref()
    }
    /// returns a mutable reference to the last context
    pub fn last_context_mut(&mut self) -> Option<&mut (ActorContext<T>, Instant)> {
        self.last_context.as_mut()
    }
    /// return an immutable reference to the memory system
    pub const fn store(&self) -> &TopoLedger<T> {
        &self.store
    }
    /// return a mutable reference to the memory system
    pub const fn store_mut(&mut self) -> &mut TopoLedger<T> {
        &mut self.store
    }
    #[deprecated(since = "0.3.0", note = "use `store` instead")]
    pub const fn memory(&self) -> &TopoLedger<T> {
        &self.store
    }
    #[deprecated(since = "0.3.0", note = "use `store_mut` instead")]
    pub const fn memory_mut(&mut self) -> &mut TopoLedger<T> {
        &mut self.store
    }
    /// returns an immutable reference to the actor of the node
    pub const fn operator(&self) -> &Operator<T> {
        &self.operator
    }
    /// returns a mutable reference to the actor of the node
    pub const fn operator_mut(&mut self) -> &mut Operator<T> {
        &mut self.operator
    }
    /// returns an immutable reference to the surface network
    pub fn surface(&self) -> Option<&SurfaceNetwork<T>> {
        self.surface.as_ref()
    }
    /// returns a mutable reference to the surface network
    pub fn surface_mut(&mut self) -> Option<&mut SurfaceNetwork<T>> {
        self.surface.as_mut()
    }
    /// returns an immutable reference the tonic history
    pub const fn tonic_history(&self) -> &Vec<usize> {
        &self.tonic_history
    }
    /// returns a mutable reference to the history of the node
    pub const fn tonic_history_mut(&mut self) -> &mut Vec<usize> {
        &mut self.tonic_history
    }
    /// update the id and  return a mutable reference to the node
    pub fn set_id(&mut self, id: Id) -> &mut Self {
        self.id = id;
        self
    }
    /// update the current critical points and return a mutable reference to the node
    pub fn set_critical_points(&mut self, critical_points: HashMap<String, usize>) -> &mut Self {
        self.critical_points = critical_points;
        self
    }
    /// update the current driver and return a mutable reference to the node
    pub fn set_driver(&mut self, driver: D) -> &mut Self
    where
        T: Float + FromPrimitive + ToPrimitive,
    {
        self.driver = driver;
        self
    }
    /// update the current operator and return a mutable reference to the node
    pub fn set_operator(&mut self, operator: Operator<T>) -> &mut Self {
        self.operator = operator;
        self
    }
    /// update the current memory system and return a mutable reference to the node
    pub fn set_store(&mut self, store: TopoLedger<T>) -> &mut Self {
        self.store = store;
        self
    }
    /// update the current surface network and return a mutable reference to the node
    pub fn set_surface(&mut self, surface: Option<SurfaceNetwork<T>>) -> &mut Self {
        self.surface = surface;
        self
    }
    /// update the current tonic history and return a mutable reference to the node
    pub fn set_tonic_history(&mut self, tonic_history: Vec<usize>) -> &mut Self {
        self.tonic_history = tonic_history;
        self
    }
    /// consumes the node to create another with the given critical points
    pub fn with_critical_points(self, critical_points: HashMap<String, usize>) -> Self {
        Self {
            critical_points,
            ..self
        }
    }
    /// consumes the node to create another with the given driver
    pub fn with_driver(self, driver: D) -> Self {
        Self { driver, ..self }
    }
    /// consumes the node to create another with the given id
    pub fn with_id(self, id: Id) -> Self {
        Self { id, ..self }
    }
    /// consumes the node to create another with the given operator
    pub fn with_operator(self, operator: Operator<T>) -> Self {
        Self { operator, ..self }
    }
    /// consumes the node to create another with the given memory system
    pub fn with_store(self, store: TopoLedger<T>) -> Self {
        Self { store, ..self }
    }
    /// consumes the node to create another with the given surface network
    pub fn with_surface(self, surface: Option<SurfaceNetwork<T>>) -> Self {
        Self { surface, ..self }
    }
    /// consumes the node to create another with the given tonic history
    pub fn with_tonic_history(self, tonic_history: Vec<usize>) -> Self {
        Self {
            tonic_history,
            ..self
        }
    }
    /// consumes the node to create another with the given surface network
    /// add a critical point to the plant and return the previous value, if it exists.
    pub fn add_critical_point(&mut self, name: PointKind, pitch_class: usize) -> Option<usize> {
        self.critical_points_mut()
            .insert(name.to_string(), pitch_class.pmod())
    }
    /// compute the centroid of the headspace
    pub fn calculate_centroid(&self) -> Option<[T; 2]>
    where
        T: num_traits::Float + num_traits::FromPrimitive,
    {
        self.driver().headspace().centroid()
    }
    /// returns an immutable reference to the node's headspace
    pub fn headspace(&self) -> &Triad {
        self.driver().headspace()
    }
    /// Get the current operator mode
    pub const fn kind(&self) -> OperatorKind {
        match self.operator {
            Operator::Agent(_) => OperatorKind::Agent,
            Operator::Observer(_) => OperatorKind::Observer,
        }
    }
    /// Record a successful navigation from one point to another
    pub fn record_navigation(&mut self, origin: &Triad, transforms_used: &[LPR])
    where
        T: Float + FromPrimitive + ToPrimitive,
    {
        let to_triad = *self.driver().headspace();

        self.store_mut()
            .record_navigation(&origin.notes(), &to_triad.notes(), transforms_used);
    }
    /// Remove a critical point from the plant
    pub fn remove_critical_point(&mut self, name: PointKind) -> Option<usize> {
        self.critical_points_mut().remove(&name.to_string())
    }
    #[deprecated(since = "0.3.0", note = "use `total_critical_points` instead")]
    pub fn critical_point_count(&self) -> usize {
        self.critical_points().len()
    }
    #[deprecated(since = "0.3.0", note = "use `total_features` instead")]
    pub fn feature_count(&self) -> usize {
        self.store().count_features()
    }
    /// returns the number of critical points
    pub fn total_critical_points(&self) -> usize {
        self.critical_points().len()
    }
    /// Get the number of features in memory
    pub fn total_features(&self) -> usize {
        self.store().count_features()
    }
    /// Return the notes (alphabet) of the underlying plant
    pub fn get_notes(&self) -> [usize; 3] {
        self.driver().headspace().notes()
    }
    /// get the tonic (root) of the headspace
    pub fn get_tonic(&self) -> Note {
        let class = self.driver().headspace().root();
        let octave = self.driver().headspace().octave();
        Note::new(class, octave)
    }
    /// returns true if the node has initialized a surface network
    pub fn has_surface_network(&self) -> bool {
        self.surface().is_some()
    }
    /// Record the current state in memory
    pub fn record_state(&mut self) -> usize
    where
        T: Float + FromPrimitive + ToPrimitive,
    {
        let notes = self.get_notes();

        // Create a feature for the triad (dimension 2)
        let feature_id = self.store_mut().create_feature(2, notes.to_vec());

        // Calculate and record the tonic
        let tonic = self.driver().headspace().root();
        self.tonic_history.push(tonic);

        feature_id
    }
    /// set the last context of the node
    pub fn set_last_context(&mut self, context: ActorContext<T>) {
        self.last_context = Some((context, Instant::now()));
    }
    /// set the operator by kind
    pub fn set_operator_by_kind(&mut self, mode: OperatorKind)
    where
        T: core::iter::Sum + Float + FromPrimitive + NumAssign + ToPrimitive,
    {
        match mode {
            OperatorKind::Agent => {
                if !matches!(self.operator, Operator::Agent(_)) {
                    // Deactivate current operator if needed
                    if let Operator::Observer(observer) = &mut self.operator {
                        let _ = observer.on_deactivate(&self.driver, &mut self.store);
                    }

                    // Create new agent and activate it
                    let mut agent = Agent::new();
                    let _ = <Agent<T> as Actor<D, T>>::initialize(&mut agent);
                    let _ = agent.on_activate(&self.driver, &mut self.store);

                    self.operator = Operator::Agent(agent);
                    self.last_context = None; // Invalidate context cache
                }
            }
            OperatorKind::Observer => {
                if !matches!(self.operator, Operator::Observer(_)) {
                    // Deactivate current operator if needed
                    if let Operator::Agent(agent) = &mut self.operator {
                        let _ = agent.on_deactivate(&self.driver, &mut self.store);
                    }

                    // Create new observer and activate it
                    let mut observer = Observer::new();
                    let _ = <Observer<T> as Actor<D, T>>::initialize(&mut observer);
                    let _ = observer.on_activate(&self.driver, &mut self.store);

                    self.operator = Operator::Observer(observer);
                    self.last_context = None; // Invalidate context cache
                }
            }
        }
    }
}

impl<D, T> Default for VNode<D, T>
where
    D: Default + RawDriver<Triad>,
    T: Default + Float + FromPrimitive,
{
    fn default() -> Self {
        Self::new()
    }
}

impl<D, T> Eq for VNode<D, T>
where
    D: RawDriver<Triad>,
    T: PartialEq,
{
}

impl<D, T> PartialEq<VNode<D, T>> for VNode<D, T>
where
    D: RawDriver<Triad>,
    Operator<T>: PartialEq,
{
    fn eq(&self, other: &VNode<D, T>) -> bool {
        self.driver().headspace() == other.driver().headspace()
            && self.operator() == other.operator()
            && self.critical_points() == other.critical_points()
            && self.tonic_history() == other.tonic_history()
    }
}

impl<D, T> PartialEq<VNode<D, T>> for &VNode<D, T>
where
    D: RawDriver<Triad>,
    Operator<T>: PartialEq,
{
    fn eq(&self, other: &VNode<D, T>) -> bool {
        self.driver().headspace() == other.driver().headspace()
            && self.operator() == other.operator()
            && self.critical_points() == other.critical_points()
            && self.tonic_history() == other.tonic_history()
    }
}

impl<D, T> PartialEq<VNode<D, T>> for &mut VNode<D, T>
where
    D: RawDriver<Triad>,
    Operator<T>: PartialEq,
{
    fn eq(&self, other: &VNode<D, T>) -> bool {
        self.driver().headspace() == other.driver().headspace()
            && self.operator() == other.operator()
            && self.critical_points() == other.critical_points()
            && self.tonic_history() == other.tonic_history()
    }
}

impl<'a, D, T> PartialEq<&'a VNode<D, T>> for VNode<D, T>
where
    D: RawDriver<Triad>,
    Operator<T>: PartialEq,
{
    fn eq(&self, other: &&'a VNode<D, T>) -> bool {
        self.driver().headspace() == other.driver().headspace()
            && self.operator() == other.operator()
            && self.critical_points() == other.critical_points()
            && self.tonic_history() == other.tonic_history()
    }
}

impl<'a, D, T> PartialEq<&'a mut VNode<D, T>> for VNode<D, T>
where
    D: RawDriver<Triad>,
    Operator<T>: PartialEq,
{
    fn eq(&self, other: &&'a mut VNode<D, T>) -> bool {
        self.driver().headspace() == other.driver().headspace()
            && self.operator() == other.operator()
            && self.critical_points() == other.critical_points()
            && self.tonic_history() == other.tonic_history()
    }
}

impl<D, T> core::hash::Hash for VNode<D, T>
where
    D: RawDriver<Triad>,
    Operator<T>: core::hash::Hash,
{
    fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
        self.driver().headspace().hash(state);
        self.operator().hash(state);
    }
}
