use alloc::sync::Arc;
use super::ParamId;
use crate::module::{ADModule, Module, ModuleMapper, ModuleVisitor, Param};
use burn_tensor::{
    backend::{ADBackend, Backend},
    Tensor,
};
#[cfg(feature = "std")]
mod threading {
    pub(super) use std::collections::HashMap;
    pub(super) use std::sync::{Mutex, RwLock};
    pub(super) use std::thread::ThreadId;
    #[inline(always)]
    pub(super) fn get_thread_current_id() -> ThreadId {
        std::thread::current().id()
    }
}
#[cfg(not(feature = "std"))]
mod threading {
    pub(super) use burn_common::stub::{Mutex, RwLock, ThreadId};
    pub(super) use hashbrown::HashMap;
    #[inline(always)]
    pub(super) fn get_thread_current_id() -> ThreadId {
        panic!("Current thread id is not available")
    }
}
use threading::*;
#[derive(Clone, Debug)]
pub struct RunningState<V> {
    id: ParamId,
    values: Arc<Mutex<HashMap<ThreadId, V>>>,
    value: Arc<RwLock<V>>,
}
impl<const D: usize, B: Backend> Module<B> for RunningState<Tensor<B, D>> {
    type Record = Param<Tensor<B, D>>;
    fn visit<V: ModuleVisitor<B>>(&self, visitor: &mut V) {
        let tensor = self.value.read().unwrap();
        visitor.visit(&self.id, &tensor)
    }
    fn map<M: ModuleMapper<B>>(self, mapper: &mut M) -> Self {
        let mut tensor = self.value.write().unwrap();
        let tensor_out = mapper.map(&self.id, tensor.clone());
        *tensor = tensor_out;
        core::mem::drop(tensor);
        self
    }
    fn into_record(self) -> Self::Record {
        self.sync();
        let tensor = self.value.read().unwrap();
        Param::new(self.id, tensor.clone())
    }
    fn load_record(mut self, record: Self::Record) -> Self {
        let mut tensor = self.value.write().unwrap();
        *tensor = record.value.to_device(&tensor.device());
        self.id = record.id;
        core::mem::drop(tensor);
        self
    }
}
impl<const D: usize, B: Backend> RunningState<Tensor<B, D>> {
    pub fn new(value: Tensor<B, D>) -> Self {
        Self {
            id: ParamId::new(),
            values: Arc::new(Mutex::new(HashMap::new())),
            value: Arc::new(RwLock::new(value)),
        }
    }
    pub fn with_id(id: ParamId, value: Tensor<B, D>) -> Self {
        Self {
            id,
            values: Arc::new(Mutex::new(HashMap::new())),
            value: Arc::new(RwLock::new(value)),
        }
    }
    pub fn from_record(record: Param<Tensor<B, D>>) -> Self {
        Self {
            id: record.id,
            values: Arc::new(Mutex::new(HashMap::new())),
            value: Arc::new(RwLock::new(record.value)),
        }
    }
    pub fn update(&self, value: Tensor<B, D>) {
        let thread_id = get_thread_current_id();
        let mut map = self.values.lock().unwrap();
        if map.contains_key(&thread_id) {
            self.update_value(&mut map);
        }
        map.insert(thread_id, value);
    }
    pub fn value(&self) -> Tensor<B, D> {
        let value = self.value.read().unwrap();
        value.clone()
    }
    pub fn value_sync(&self) -> Tensor<B, D> {
        let thread_id = get_thread_current_id();
        let mut map = self.values.lock().unwrap();
        if map.contains_key(&thread_id) {
            self.update_value(&mut map);
        }
        let value = self.value.read().unwrap();
        value.clone()
    }
    fn sync(&self) {
        let mut map = self.values.lock().unwrap();
        if !map.is_empty() {
            self.update_value(&mut map);
        }
    }
    fn update_value(&self, map: &mut HashMap<ThreadId, Tensor<B, D>>) {
        let mut value_updated = None;
        let mut counter = 0;
        for (_key, tensor) in map.drain() {
            counter += 1;
            value_updated = match value_updated {
                Some(current) => Some(tensor.add(current)),
                None => Some(tensor),
            };
        }
        if let Some(value) = value_updated {
            let value = value.div_scalar(counter);
            let mut value_old = self.value.write().unwrap();
            *value_old = value;
        }
    }
}
impl<const D: usize, B: ADBackend> ADModule<B> for RunningState<Tensor<B, D>> {
    type InnerModule = RunningState<Tensor<B::InnerBackend, D>>;
    fn valid(&self) -> Self::InnerModule {
        self.sync();
        let value = self.value();
        RunningState::with_id(self.id.clone(), value.inner())
    }
}