use std:: ffi::CStr;
use std::ffi::CString;
use std::collections::BTreeSet;
use std::iter::FusedIterator;
use smallvec::SmallVec;
use crate::c_api::mts_labels_t;
use crate::errors::{Error, check_status};
impl mts_labels_t {
    pub(crate) fn null() -> mts_labels_t {
        mts_labels_t {
            internal_ptr_: std::ptr::null_mut(),
            names: std::ptr::null(),
            values: std::ptr::null(),
            size: 0,
            count: 0,
        }
    }
}
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(transparent)]
pub struct LabelValue(i32);
impl PartialEq<i32> for LabelValue {
    #[inline]
    fn eq(&self, other: &i32) -> bool {
        self.0 == *other
    }
}
impl PartialEq<LabelValue> for i32 {
    #[inline]
    fn eq(&self, other: &LabelValue) -> bool {
        *self == other.0
    }
}
impl std::fmt::Debug for LabelValue {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}", self.0)
    }
}
impl std::fmt::Display for LabelValue {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}", self.0)
    }
}
#[allow(clippy::cast_possible_wrap, clippy::cast_possible_truncation)]
impl From<u32> for LabelValue {
    #[inline]
    fn from(value: u32) -> LabelValue {
        assert!(value < i32::MAX as u32);
        LabelValue(value as i32)
    }
}
impl From<i32> for LabelValue {
    #[inline]
    fn from(value: i32) -> LabelValue {
        LabelValue(value)
    }
}
#[allow(clippy::cast_possible_wrap, clippy::cast_possible_truncation)]
impl From<usize> for LabelValue {
    #[inline]
    fn from(value: usize) -> LabelValue {
        assert!(value < i32::MAX as usize);
        LabelValue(value as i32)
    }
}
#[allow(clippy::cast_possible_truncation)]
impl From<isize> for LabelValue {
    #[inline]
    fn from(value: isize) -> LabelValue {
        assert!(value < i32::MAX as isize && value > i32::MIN as isize);
        LabelValue(value as i32)
    }
}
impl LabelValue {
    #[inline]
    pub fn new(value: i32) -> LabelValue {
        LabelValue(value)
    }
    #[inline]
    #[allow(clippy::cast_sign_loss)]
    pub fn usize(self) -> usize {
        debug_assert!(self.0 >= 0);
        self.0 as usize
    }
    #[inline]
    pub fn isize(self) -> isize {
        self.0 as isize
    }
    #[inline]
    pub fn i32(self) -> i32 {
        self.0
    }
}
pub struct Labels {
    pub(crate) raw: mts_labels_t,
}
unsafe impl Send for Labels {}
unsafe impl Sync for Labels {}
impl std::fmt::Debug for Labels {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        pretty_print_labels(self, "", f)
    }
}
pub(crate) fn pretty_print_labels(
    labels: &Labels,
    offset: &str,
    f: &mut std::fmt::Formatter<'_>
) -> std::fmt::Result {
    let names = labels.names();
    writeln!(f, "Labels @ {:p} {{", labels.raw.internal_ptr_)?;
    writeln!(f, "{}    {}", offset, names.join(", "))?;
    let widths = names.iter().map(|s| s.len()).collect::<Vec<_>>();
    for values in labels {
        write!(f, "{}    ", offset)?;
        for (value, width) in values.iter().zip(&widths) {
            write!(f, "{:^width$}  ", value.isize(), width=width)?;
        }
        writeln!(f)?;
    }
    writeln!(f, "{}}}", offset)
}
impl Clone for Labels {
    #[inline]
    fn clone(&self) -> Self {
        let mut clone = mts_labels_t::null();
        unsafe {
            check_status(crate::c_api::mts_labels_clone(self.raw, &mut clone)).expect("failed to clone Labels");
        }
        return unsafe { Labels::from_raw(clone) };
    }
}
impl std::ops::Drop for Labels {
    #[allow(unused_must_use)]
    fn drop(&mut self) {
        unsafe {
            crate::c_api::mts_labels_free(&mut self.raw);
        }
    }
}
impl Labels {
    #[inline]
    pub fn new<T, const N: usize>(names: [&str; N], values: &[[T; N]]) -> Labels
        where T: Copy + Into<LabelValue>
    {
        let mut builder = LabelsBuilder::new(names.to_vec());
        for entry in values {
            builder.add(entry);
        }
        return builder.finish();
    }
    #[inline]
    pub fn empty(names: Vec<&str>) -> Labels {
        return LabelsBuilder::new(names).finish()
    }
    #[inline]
    pub fn single() -> Labels {
        let mut builder = LabelsBuilder::new(vec!["_"]);
        builder.add(&[0]);
        return builder.finish();
    }
    #[inline]
    pub fn size(&self) -> usize {
        self.raw.size
    }
    #[inline]
    pub fn names(&self) -> Vec<&str> {
        if self.raw.size == 0 {
            return Vec::new();
        } else {
            unsafe {
                let names = std::slice::from_raw_parts(self.raw.names, self.raw.size);
                return names.iter()
                            .map(|&ptr| CStr::from_ptr(ptr).to_str().expect("invalid UTF8"))
                            .collect();
            }
        }
    }
    #[inline]
    pub fn count(&self) -> usize {
        return self.raw.count;
    }
    #[inline]
    pub fn is_empty(&self) -> bool {
        self.count() == 0
    }
    #[inline]
    pub fn contains(&self, label: &[LabelValue]) -> bool {
        return self.position(label).is_some();
    }
    #[inline]
    pub fn position(&self, value: &[LabelValue]) -> Option<usize> {
        assert!(value.len() == self.size(), "invalid size of index in Labels::position");
        let mut result = 0;
        unsafe {
            check_status(crate::c_api::mts_labels_position(
                self.raw,
                value.as_ptr().cast(),
                value.len(),
                &mut result,
            )).expect("failed to check label position");
        }
        return result.try_into().ok();
    }
    #[inline]
    pub fn union(
        &self,
        other: &Labels,
        first_mapping: Option<&mut [i64]>,
        second_mapping: Option<&mut [i64]>,
    ) -> Result<Labels, Error> {
        let mut output = mts_labels_t::null();
        let (first_mapping, first_mapping_count) = if let Some(m) = first_mapping {
            (m.as_mut_ptr(), m.len())
        } else {
            (std::ptr::null_mut(), 0)
        };
        let (second_mapping, second_mapping_count) = if let Some(m) = second_mapping {
            (m.as_mut_ptr(), m.len())
        } else {
            (std::ptr::null_mut(), 0)
        };
        unsafe {
            check_status(crate::c_api::mts_labels_union(
                self.raw,
                other.raw,
                &mut output,
                first_mapping,
                first_mapping_count,
                second_mapping,
                second_mapping_count,
            ))?;
            return Ok(Labels::from_raw(output));
        }
    }
    #[inline]
    pub fn intersection(
        &self,
        other: &Labels,
        first_mapping: Option<&mut [i64]>,
        second_mapping: Option<&mut [i64]>,
    ) -> Result<Labels, Error> {
        let mut output = mts_labels_t::null();
        let (first_mapping, first_mapping_count) = if let Some(m) = first_mapping {
            (m.as_mut_ptr(), m.len())
        } else {
            (std::ptr::null_mut(), 0)
        };
        let (second_mapping, second_mapping_count) = if let Some(m) = second_mapping {
            (m.as_mut_ptr(), m.len())
        } else {
            (std::ptr::null_mut(), 0)
        };
        unsafe {
            check_status(crate::c_api::mts_labels_intersection(
                self.raw,
                other.raw,
                &mut output,
                first_mapping,
                first_mapping_count,
                second_mapping,
                second_mapping_count,
            ))?;
            return Ok(Labels::from_raw(output));
        }
    }
    #[inline]
    pub fn iter(&self) -> LabelsIter<'_> {
        return LabelsIter {
            chunks: self.values().chunks_exact(self.raw.size)
        };
    }
    #[cfg(feature = "rayon")]
    #[inline]
    pub fn par_iter(&self) -> LabelsParIter<'_> {
        use rayon::prelude::*;
        return LabelsParIter {
            chunks: self.values().par_chunks_exact(self.raw.size)
        };
    }
    #[inline]
    pub fn iter_fixed_size<const N: usize>(&self) -> LabelsFixedSizeIter<N> {
        assert!(N == self.size(),
            "wrong label size in `iter_fixed_size`: the entries contains {} element \
            but this function was called with size of {}",
            self.size(), N
        );
        return LabelsFixedSizeIter {
            values: self.values()
        };
    }
    pub(crate) fn values(&self) -> &[LabelValue] {
        if self.count() == 0 || self.size() == 0 {
            return &[]
        } else {
            unsafe {
                std::slice::from_raw_parts(self.raw.values.cast(), self.count() * self.size())
            }
        }
    }
}
impl Labels {
    pub(crate) fn as_mts_labels_t(&self) -> mts_labels_t {
        return self.raw;
    }
    #[inline]
    pub unsafe fn from_raw(raw: mts_labels_t) -> Labels {
        assert!(!raw.internal_ptr_.is_null(), "expected mts_labels_t.internal_ptr_ to not be NULL");
        Labels {
            raw: raw,
        }
    }
}
impl std::cmp::PartialEq<Labels> for Labels {
    #[inline]
    fn eq(&self, other: &Labels) -> bool {
        self.names() == other.names() && self.values() == other.values()
    }
}
impl std::ops::Index<usize> for Labels {
    type Output = [LabelValue];
    #[inline]
    fn index(&self, i: usize) -> &[LabelValue] {
        let start = i * self.size();
        let stop = (i + 1) * self.size();
        &self.values()[start..stop]
    }
}
#[derive(Debug, Clone)]
pub struct LabelsIter<'a> {
    chunks: std::slice::ChunksExact<'a, LabelValue>,
}
impl<'a> Iterator for LabelsIter<'a> {
    type Item = &'a [LabelValue];
    #[inline]
    fn next(&mut self) -> Option<Self::Item> {
        self.chunks.next()
    }
    fn size_hint(&self) -> (usize, Option<usize>) {
        self.chunks.size_hint()
    }
}
impl<'a> ExactSizeIterator for LabelsIter<'a> {
    #[inline]
    fn len(&self) -> usize {
        self.chunks.len()
    }
}
impl<'a> FusedIterator for LabelsIter<'a> {}
impl<'a> IntoIterator for &'a Labels {
    type IntoIter = LabelsIter<'a>;
    type Item = &'a [LabelValue];
    #[inline]
    fn into_iter(self) -> Self::IntoIter {
        self.iter()
    }
}
#[cfg(feature = "rayon")]
#[derive(Debug, Clone)]
pub struct LabelsParIter<'a> {
    chunks: rayon::slice::ChunksExact<'a, LabelValue>,
}
#[cfg(feature = "rayon")]
impl<'a> rayon::iter::ParallelIterator for LabelsParIter<'a> {
    type Item = &'a [LabelValue];
    #[inline]
    fn drive_unindexed<C>(self, consumer: C) -> C::Result
    where
        C: rayon::iter::plumbing::UnindexedConsumer<Self::Item> {
        self.chunks.drive_unindexed(consumer)
    }
}
#[cfg(feature = "rayon")]
impl<'a> rayon::iter::IndexedParallelIterator for LabelsParIter<'a> {
    #[inline]
    fn len(&self) -> usize {
        self.chunks.len()
    }
    #[inline]
    fn drive<C: rayon::iter::plumbing::Consumer<Self::Item>>(self, consumer: C) -> C::Result {
        self.chunks.drive(consumer)
    }
    #[inline]
    fn with_producer<CB: rayon::iter::plumbing::ProducerCallback<Self::Item>>(self, callback: CB) -> CB::Output {
        self.chunks.with_producer(callback)
    }
}
#[derive(Debug, Clone)]
pub struct LabelsFixedSizeIter<'a, const N: usize> {
    values: &'a [LabelValue],
}
impl<'a, const N: usize> Iterator for LabelsFixedSizeIter<'a, N> {
    type Item = &'a [LabelValue; N];
    #[inline]
    fn next(&mut self) -> Option<Self::Item> {
        if self.values.is_empty() {
            return None
        }
        let (value, rest) = self.values.split_at(N);
        self.values = rest;
        return Some(value.try_into().expect("wrong size in FixedSizeIter::next"));
    }
    fn size_hint(&self) -> (usize, Option<usize>) {
        (self.len(), Some(self.len()))
    }
}
impl<'a, const N: usize> ExactSizeIterator for LabelsFixedSizeIter<'a, N> {
    #[inline]
    fn len(&self) -> usize {
        self.values.len() / N
    }
}
#[derive(Debug, Clone)]
pub struct LabelsBuilder {
    names: Vec<String>,
    values: Vec<LabelValue>,
}
impl LabelsBuilder {
    #[inline]
    pub fn new(names: Vec<&str>) -> LabelsBuilder {
        let n_unique_names = names.iter().collect::<BTreeSet<_>>().len();
        assert!(n_unique_names == names.len(), "invalid labels: the same name is used multiple times");
        LabelsBuilder {
            names: names.into_iter().map(|s| s.into()).collect(),
            values: Vec::new(),
        }
    }
    #[inline]
    pub fn reserve(&mut self, additional: usize) {
        self.values.reserve(additional * self.names.len());
    }
    #[inline]
    pub fn size(&self) -> usize {
        self.names.len()
    }
    #[inline]
    pub fn add<T>(&mut self, entry: &[T]) where T: Copy + Into<LabelValue> {
        assert_eq!(
            self.size(), entry.len(),
            "wrong size for added label: got {}, but expected {}",
            entry.len(), self.size()
        );
        let entry = entry.iter().copied().map(Into::into).collect::<SmallVec<[LabelValue; 16]>>();
        self.values.extend(&entry);
    }
    #[inline]
    pub fn finish(self) -> Labels {
        let mut raw_names = Vec::new();
        let mut raw_names_ptr = Vec::new();
        let mut raw_labels = if self.names.is_empty() {
            assert!(self.values.is_empty());
            mts_labels_t::null()
        } else {
            for name in &self.names {
                let name = CString::new(&**name).expect("name contains a NULL byte");
                raw_names_ptr.push(name.as_ptr());
                raw_names.push(name);
            }
            mts_labels_t {
                internal_ptr_: std::ptr::null_mut(),
                names: raw_names_ptr.as_ptr(),
                values: self.values.as_ptr().cast(),
                size: self.size(),
                count: self.values.len() / self.size(),
            }
        };
        unsafe {
            check_status(
                crate::c_api::mts_labels_create(&mut raw_labels)
            ).expect("invalid labels?");
        }
        return unsafe { Labels::from_raw(raw_labels) };
    }
}
#[cfg(test)]
mod tests {
    use super::*;
    #[test]
    fn labels() {
        let mut builder = LabelsBuilder::new(vec!["foo", "bar"]);
        builder.add(&[2, 3]);
        builder.add(&[1, 243]);
        builder.add(&[-4, -2413]);
        let idx = builder.finish();
        assert_eq!(idx.names(), &["foo", "bar"]);
        assert_eq!(idx.size(), 2);
        assert_eq!(idx.count(), 3);
        assert_eq!(idx[0], [2, 3]);
        assert_eq!(idx[1], [1, 243]);
        assert_eq!(idx[2], [-4, -2413]);
        let builder = LabelsBuilder::new(vec![]);
        let labels = builder.finish();
        assert_eq!(labels.size(), 0);
        assert_eq!(labels.count(), 0);
    }
    #[test]
    fn direct_construct() {
        let labels = Labels::new(
            ["foo", "bar"],
            &[
                [2, 3],
                [1, 243],
                [-4, -2413],
            ]
        );
        assert_eq!(labels.names(), &["foo", "bar"]);
        assert_eq!(labels.size(), 2);
        assert_eq!(labels.count(), 3);
        assert_eq!(labels[0], [2, 3]);
        assert_eq!(labels[1], [1, 243]);
        assert_eq!(labels[2], [-4, -2413]);
    }
    #[test]
    fn labels_iter() {
        let mut builder = LabelsBuilder::new(vec!["foo", "bar"]);
        builder.add(&[2, 3]);
        builder.add(&[1, 2]);
        builder.add(&[4, 3]);
        let idx = builder.finish();
        let mut iter = idx.iter();
        assert_eq!(iter.len(), 3);
        assert_eq!(iter.next().unwrap(), &[2, 3]);
        assert_eq!(iter.next().unwrap(), &[1, 2]);
        assert_eq!(iter.next().unwrap(), &[4, 3]);
        assert_eq!(iter.next(), None);
    }
    #[test]
    #[should_panic(expected = "'33 bar' is not a valid label name")]
    fn invalid_label_name() {
        LabelsBuilder::new(vec!["foo", "33 bar"]).finish();
    }
    #[test]
    #[should_panic(expected = "invalid labels: the same name is used multiple times")]
    fn duplicated_label_name() {
        LabelsBuilder::new(vec!["foo", "bar", "foo"]).finish();
    }
    #[test]
    #[should_panic(expected = "can not have the same label value multiple time: [0, 1] is already present at position 0")]
    fn duplicated_label_value() {
        let mut builder = LabelsBuilder::new(vec!["foo", "bar"]);
        builder.add(&[0, 1]);
        builder.add(&[0, 1]);
        builder.finish();
    }
    #[test]
    fn single_label() {
        let labels = Labels::single();
        assert_eq!(labels.names(), &["_"]);
        assert_eq!(labels.size(), 1);
        assert_eq!(labels.count(), 1);
    }
    #[test]
    fn indexing() {
        let labels = Labels::new(
            ["foo", "bar"],
            &[
                [2, 3],
                [1, 243],
                [-4, -2413],
            ]
        );
        assert_eq!(labels[1], [1, 243]);
        assert_eq!(labels[2], [-4, -2413]);
    }
    #[test]
    fn iter() {
        let labels = Labels::new(
            ["foo", "bar"],
            &[
                [2, 3],
                [1, 243],
                [-4, -2413],
            ]
        );
        let mut iter = labels.iter();
        assert_eq!(iter.next().unwrap(), &[2, 3]);
        assert_eq!(iter.next().unwrap(), &[1, 243]);
        assert_eq!(iter.next().unwrap(), &[-4, -2413]);
        assert_eq!(iter.next(), None);
    }
    #[test]
    fn debug() {
        let labels = Labels::new(
            ["foo", "bar"],
            &[
                [2, 3],
                [1, 243],
                [-4, -2413],
            ]
        );
        let expected = format!(
            "Labels @ {:p} {{\n    foo, bar\n     2    3   \n     1   243  \n    -4   -2413  \n}}\n",
            labels.as_mts_labels_t().internal_ptr_
        );
        assert_eq!(format!("{:?}", labels), expected);
    }
    #[test]
    fn union() {
        let first = Labels::new(["aa", "bb"], &[[0, 1], [1, 2]]);
        let second = Labels::new(["aa", "bb"], &[[2, 3], [1, 2], [4, 5]]);
        let mut first_mapping = vec![0; first.count()];
        let mut second_mapping = vec![0; second.count()];
        let union = first.union(&second, Some(&mut first_mapping), Some(&mut second_mapping)).unwrap();
        assert_eq!(union.names(), ["aa", "bb"]);
        assert_eq!(union.values(), [0, 1, 1, 2, 2, 3, 4, 5]);
        assert_eq!(first_mapping, [0, 1]);
        assert_eq!(second_mapping, [2, 1, 3]);
    }
    #[test]
    fn intersection() {
        let first = Labels::new(["aa", "bb"], &[[0, 1], [1, 2]]);
        let second = Labels::new(["aa", "bb"], &[[2, 3], [1, 2], [4, 5]]);
        let mut first_mapping = vec![0_i64; first.count()];
        let mut second_mapping = vec![0_i64; second.count()];
        let union = first.intersection(&second, Some(&mut first_mapping), Some(&mut second_mapping)).unwrap();
        assert_eq!(union.names(), ["aa", "bb"]);
        assert_eq!(union.values(), [1, 2]);
        assert_eq!(first_mapping, [-1, 0]);
        assert_eq!(second_mapping, [-1, 0, -1]);
    }
}