/// A tag is an amount of data you can "attach" to a particular sample in stream.
/// Indeed you might need to mark sample, or bring information along with them sparsely :
/// Tags are often a rare occurence.
///
/// Example :
///
/// Timing error detection : A sample is the center of a symbol
/// There might be a Tag :
///
/// ted_symbol_center: 0.011 (Here the number might represent a error value)
///
/// But really, any kind of data could be referenced by a tag :
/// A float, A string like a json structure, or even a binary blob.
///
/// A tag is formed that way :
///
/// The TagKey :
/// | A unique key allocated at the begining,
/// | Which is tied to a human readable label configured
/// | ahead of runtime.
/// |
/// | And a type, which is used to constrain the type that the tag
/// | contains, to keep downcasting safe and less error prone.
///
/// The TagData :
/// | Then, when it is time to tag sa sample, a block uses that tag key
/// | to add a tag on a sample bundling the tag key, and the sepcific data.
///
///
/// Another block downstream could then check if a sample has been tagged with something
/// of interest with the tag key.
/// If so, the block can then retrieve the data on the tag.
///
/// Each sample can have multiple tags attached to it.
///
/// TagKey using a usize backed identifier is used instead of the more
/// classic way of using String-Value pairs to allow easier and faster
/// Comparisons, as well as much tighter and cleaner managment of tags
/// throughout the graph.
///
///
///
use std::any::Any;
use std::collections::HashMap;
use std::marker::PhantomData;
use std::ops::Deref;
use std::ops::DerefMut;
use std::sync::Arc;
use std::sync::RwLock;

/// Object to allocate tags and give a unique identifier per tag
struct TagAllocator
{
    // Counter to uniquely identify allocated tags
    counter: usize,

    // Keeps readable tag type and label(s) for the tags
    labels: HashMap<usize, (&'static str, TagLabel)>,
}

/// Label for a tag like : "symbol", "packet_start", "error"
struct TagLabel
{
    // TODO: Allow user customization of labels
    // maybe multiple labels
    _label: String,
}

/// Object from which TagKeys are obtained. This guarantees absence of collisions between tags
pub struct Tags
{
    allocator: Arc<RwLock<TagAllocator>>,
}

/// Used to anotate tags entries and retrieve them
#[derive(Clone)]
pub struct TagKey<T>
{
    key: usize,

    // Maybe used later to retrieve labels for example
    _owner: Arc<RwLock<TagAllocator>>,
    _phantom: PhantomData<T>,
}

/// Tags a particular sample within a specific stream
#[derive(Clone)]
pub(crate) struct TagSlot
{
    // Position of the sample this tag is tied to.
    // The position is in terms of the stream front index when the
    // sample was added
    pub position: usize,

    // TODO: Make it such that when a tag is duplicated, the data seems to be too:
    // When adding on a duplicate, it should not replicate on others, but without
    // requiring a deep copy.
    pub tag: Tag,
}

/// A Tag object containing TagKey-value pairs
#[derive(Clone)]
pub struct Tag
{
    data: Arc<RwLock<HashMap<usize, Arc<dyn Any + Send + Sync + 'static>>>>,
}

impl Tags
{
    /// Creates a new tag allocator
    pub fn new() -> Self
    {
        Self {
            allocator: Arc::new(RwLock::new(TagAllocator {
                counter: 0,
                labels: HashMap::default(),
            })),
        }
    }

    /// Allocates a new unique tag key
    pub fn allocate_tag<T>(&mut self, label: impl AsRef<str>) -> TagKey<T>
    {
        let k = self.allocator.write().unwrap().allocate_tag::<T>(label);
        TagKey {
            key: k,
            _owner: self.allocator.clone(),
            _phantom: Default::default(),
        }
    }
}

impl TagAllocator
{
    /// Allocates a new unique tag key
    pub fn allocate_tag<T>(&mut self, label: impl AsRef<str>) -> usize
    {
        let key = self.counter;
        self.labels.insert(
            self.counter,
            (
                std::any::type_name::<T>(),
                TagLabel {
                    _label: label.as_ref().to_owned(),
                },
            ),
        );
        self.counter += 1;
        key
    }
}

impl Default for Tags
{
    /// Creates a new tag allocator
    fn default() -> Self
    {
        Self::new()
    }
}

impl Tag
{
    /// Creates a new empty tag
    pub fn new() -> Self
    {
        Self {
            data: Default::default(),
        }
    }

    /// Creates a new tag with a (key, value) entry
    pub fn with_entry<T: 'static + Send + Sync>(key: TagKey<T>, value: T) -> Self
    {
        let new_tag = Self::default();
        new_tag.add_entry(key, value);
        new_tag
    }

    /// Creates a new tag, which is the combination of the given tags
    pub fn from_tags<const N: usize>(tag_opts: &[&Tag; N]) -> Tag
    {
        let new_tag = Self::default();
        {
            let mut writer = new_tag.data.write().unwrap();

            for tag in tag_opts.iter()
            {
                let reader = tag.data.read().unwrap();
                writer.extend(reader.iter().map(|x| (*x.0, x.1.clone())));
            }
        }
        new_tag
    }

    /// Creates a new tag option, which is the combination of the given tag options
    ///
    /// If all the tag options are None, None is returned
    /// Otherwise it is Some of the combination of all of the tags which are Some
    pub fn from_tag_opts<const N: usize>(tag_opts: &[&Option<Tag>; N]) -> Option<Tag>
    {
        if tag_opts.iter().all(|t| t.is_none())
        {
            return None;
        }

        let new_tag = Self::default();
        {
            let mut writer = new_tag.data.write().unwrap();

            for tag in tag_opts.iter().filter(|t| t.is_some())
            {
                let reader = tag.as_ref().unwrap().data.read().unwrap();
                writer.extend(reader.iter().map(|x| (*x.0, x.1.clone())));
            }
        }
        Some(new_tag)
    }

    /// Adds a new entry in the tag. If it already exists, it is overwritten
    pub fn add_entry<T: 'static + Send + Sync>(&self, key: TagKey<T>, value: T)
    {
        self.data.write().unwrap().insert(key.key, Arc::new(value));
    }

    /// Retrieves an entry in tag. If ther is no such entry corresponding to the key,
    /// retruns None
    pub fn retrieve<T: 'static + Send + Sync>(&self, key: &TagKey<T>) -> Option<Arc<T>>
    {
        let element = self.data.read().unwrap().get(&key.key).cloned();

        // TODO: When available : downcast unchecked, the type should be guaranteed
        // by the TagKey's generic
        // (But there might be an issue if the key comes from somewhere else)
        element.map(|x| x.downcast().unwrap())
    }
}

impl Default for Tag
{
    fn default() -> Self
    {
        Self::new()
    }
}

pub trait TagValue: Clone {}
impl<T> TagValue for T where T: Clone {}

pub trait TagMergable<T>
{
    fn merge(&self, other: &T) -> Self;
}

impl TagMergable<Tag> for Tag
{
    fn merge(&self, other: &Self) -> Self
    {
        Self::from_tags(&[self, other])
    }
}

impl TagMergable<Option<Tag>> for Option<Tag>
{
    fn merge(&self, other: &Self) -> Self
    {
        Tag::from_tag_opts(&[self, other])
    }
}

impl<T: Clone> TagMergable<Option<Tag>> for Tagged<T>
{
    fn merge(&self, other: &Option<Tag>) -> Self
    {
        Tagged::new(self.0.clone(), self.1.merge(other))
    }
}

/// Represents a data, with a potential tag attached to it.
#[derive(Clone)]
pub struct Tagged<T>(pub T, pub Option<Tag>);

impl<T> Tagged<T>
{
    pub fn new(inner: T, tag: Option<Tag>) -> Self
    {
        Self(inner, tag)
    }

    pub fn has_tag(&self) -> bool
    {
        self.1.is_some()
    }

    pub fn strip(&mut self)
    {
        self.1 = None;
    }

    pub fn into_inner(self) -> T
    {
        self.0
    }

    pub fn tag(&mut self, tag: Tag) -> Option<Tag>
    {
        let t = self.1.take();
        self.1 = Some(tag);
        t
    }

    pub fn retrieve<D: Send + Sync + 'static>(&self, key: &TagKey<D>) -> Option<Arc<D>>
    {
        self.1.as_ref().and_then(|t| t.retrieve(key))
    }

    pub fn add_entry<D: Send + Sync + 'static>(&mut self, key: TagKey<D>, value: D)
    {
        self.1.get_or_insert(Tag::default()).add_entry(key, value);
    }
}

impl<T: Clone> Tagged<T>
{
    pub fn stripped(&self) -> Self
    {
        self.0.clone().into()
    }

    pub fn tagged(&self, tag: Tag) -> Self
    {
        (self.0.clone(), tag).into()
    }
}

impl<T> From<T> for Tagged<T>
{
    fn from(value: T) -> Self
    {
        Self::new(value, None)
    }
}

impl<T> From<(T, Tag)> for Tagged<T>
{
    fn from((value, tag): (T, Tag)) -> Self
    {
        Self::new(value, Some(tag))
    }
}

impl<T> From<(T, Option<Tag>)> for Tagged<T>
{
    fn from((value, tag): (T, Option<Tag>)) -> Self
    {
        Self::new(value, tag)
    }
}

impl<T> From<Tagged<T>> for (T, Option<Tag>)
{
    fn from(val: Tagged<T>) -> Self
    {
        (val.0, val.1)
    }
}

impl<T> DerefMut for Tagged<T>
{
    fn deref_mut(&mut self) -> &mut Self::Target
    {
        &mut self.0
    }
}

impl<T> Deref for Tagged<T>
{
    type Target = T;

    fn deref(&self) -> &Self::Target
    {
        &self.0
    }
}
