use std::{
    borrow::Cow,
    fmt::{self, Write},
    ops::Deref,
    sync::{
        atomic::{AtomicU64, Ordering},
        Arc, RwLock,
    },
};
use crate::{encoding::write_eof, Error, MetricsGroup, MetricsGroupSet};
#[derive(Debug, Default)]
pub struct Registry {
    schema_version: Arc<AtomicU64>,
    metrics: Vec<Arc<dyn MetricsGroup>>,
    prefix: Option<Cow<'static, str>>,
    labels: Vec<(Cow<'static, str>, Cow<'static, str>)>,
    sub_registries: Vec<Registry>,
}
impl Registry {
    pub fn sub_registry_with_prefix(&mut self, prefix: impl Into<Cow<'static, str>>) -> &mut Self {
        let prefix = self.prefix.to_owned().map(|p| p + "_").unwrap_or_default() + prefix.into();
        self.schema_version.fetch_add(1, Ordering::Relaxed);
        let sub_registry = Registry {
            schema_version: self.schema_version.clone(),
            metrics: Default::default(),
            prefix: Some(prefix),
            labels: self.labels.clone(),
            sub_registries: Default::default(),
        };
        self.sub_registries.push(sub_registry);
        self.sub_registries.last_mut().unwrap()
    }
    pub fn sub_registry_with_labels(
        &mut self,
        labels: impl IntoIterator<Item = (impl Into<Cow<'static, str>>, impl Into<Cow<'static, str>>)>,
    ) -> &mut Self {
        let mut all_labels = self.labels.clone();
        all_labels.extend(labels.into_iter().map(|(k, v)| (k.into(), v.into())));
        self.schema_version.fetch_add(1, Ordering::Relaxed);
        let sub_registry = Registry {
            schema_version: self.schema_version.clone(),
            prefix: self.prefix.clone(),
            labels: all_labels,
            metrics: Default::default(),
            sub_registries: Default::default(),
        };
        self.sub_registries.push(sub_registry);
        self.sub_registries.last_mut().unwrap()
    }
    pub fn sub_registry_with_label(
        &mut self,
        key: impl Into<Cow<'static, str>>,
        value: impl Into<Cow<'static, str>>,
    ) -> &mut Self {
        self.sub_registry_with_labels([(key, value)])
    }
    pub fn register(&mut self, metrics_group: Arc<dyn MetricsGroup>) {
        self.schema_version.fetch_add(1, Ordering::Relaxed);
        self.metrics.push(metrics_group);
    }
    pub fn register_all(&mut self, metrics_group_set: &impl MetricsGroupSet) {
        for group in metrics_group_set.groups_cloned() {
            self.register(group)
        }
    }
    pub fn register_all_prefixed(&mut self, metrics_group_set: &impl MetricsGroupSet) {
        let registry = self.sub_registry_with_prefix(metrics_group_set.name());
        registry.register_all(metrics_group_set)
    }
    pub fn encode_openmetrics_to_writer(&self, writer: &mut impl Write) -> fmt::Result {
        for group in &self.metrics {
            group.encode_openmetrics(writer, self.prefix.as_deref(), &self.labels)?;
        }
        for sub in self.sub_registries.iter() {
            sub.encode_openmetrics_to_writer(writer)?;
        }
        Ok(())
    }
    pub fn schema_version(&self) -> u64 {
        self.schema_version.load(Ordering::Relaxed)
    }
    pub fn encode_schema(&self, schema: &mut crate::encoding::Schema) {
        for group in &self.metrics {
            group.encode_schema(schema, self.prefix.as_deref(), &self.labels);
        }
        for sub in self.sub_registries.iter() {
            sub.encode_schema(schema);
        }
    }
    pub fn encode_values(&self, values: &mut crate::encoding::Values) {
        for group in &self.metrics {
            group.encode_values(values);
        }
        for sub in self.sub_registries.iter() {
            sub.encode_values(values);
        }
    }
}
pub trait MetricsSource: Send + 'static {
    fn encode_openmetrics(&self, writer: &mut impl std::fmt::Write) -> Result<(), Error>;
    fn encode_openmetrics_to_string(&self) -> Result<String, Error> {
        let mut s = String::new();
        self.encode_openmetrics(&mut s)?;
        Ok(s)
    }
}
impl MetricsSource for Registry {
    fn encode_openmetrics(&self, writer: &mut impl std::fmt::Write) -> Result<(), Error> {
        self.encode_openmetrics_to_writer(writer)?;
        write_eof(writer)?;
        Ok(())
    }
}
pub type RwLockRegistry = Arc<RwLock<Registry>>;
impl MetricsSource for RwLockRegistry {
    fn encode_openmetrics(&self, writer: &mut impl std::fmt::Write) -> Result<(), Error> {
        let inner = self.read().expect("poisoned");
        inner.encode_openmetrics(writer)
    }
}
impl MetricsSource for Arc<Registry> {
    fn encode_openmetrics(&self, writer: &mut impl std::fmt::Write) -> Result<(), Error> {
        Arc::deref(self).encode_openmetrics(writer)
    }
}