use std::any::Any;

use crate::{io::{In, Out}, stream::{self, StreamConsumer, StreamProducer}, tag::TagSlot};

/// Shared object between a block's input and output objects
/// so they can "communicate" and know about each other
#[derive(Default)]
pub struct Edge
{
    /// Represents the index of the block owning the Out end in the graph
    /// And the the output index within that block
    pub from: Option<BlockIOIndex>,

    /// Represents the index of the block owning the In end in the graph
    /// And the the input index within that block
    pub to: Option<BlockIOIndex>,
}

/// Reprensents the location of a port (input or output) in terms of
/// - The block index in which they exist
/// - Their input or output index within that block
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub struct BlockIOIndex
{
    pub block_index: usize,
    pub port_index: usize,
}

/// Trait to manipulate a block's input in a type agnostic/erased way
pub trait AnonymousIn
{
    /// Inform the input about the index of the blocks it's in, as well as its port index
    fn set_index(&self, index: BlockIOIndex);

    /// Returns None or the block index of the block, and the block port of the corresponding
    /// Out object
    fn get_producer_block(&self) -> Option<BlockIOIndex>;

    /// Sets the internal stream object
    fn set_anonymous_stream(&mut self, consumer: AnonymousStreamConsumer);
}

/// Trait to manipulate a block's output in a type agnostic/erased way
pub trait AnonymousOut
{
    /// Inform the output about the index of the blocks it's in, as well as its port index
    fn set_index(&self, index: BlockIOIndex);

    /// Sets the internal stream object
    fn set_anonymous_stream(&mut self, producer: AnonymousStreamProducer);

    /// Returns None or the block index of the block, and the block port of the corresponding
    /// In object
    fn get_consumer_block(&self) -> Option<BlockIOIndex>;

    /// Creates the stream with the correct corresponding type, in a type erased way.
    ///
    /// This delegation of stream creation is necessary to allow the graph to manipulate
    /// it, as it cannot know about the generic type of the stream.
    fn create_anonymous_stream(
        &self,
        capacity: usize,
    ) -> (AnonymousStreamProducer, AnonymousStreamConsumer);
}

impl<T: 'static> AnonymousIn for In<T>
{
    fn set_index(&self, index: BlockIOIndex)
    {
        self.edge.lock().unwrap().to = Some(index);
    }

    fn get_producer_block(&self) -> Option<BlockIOIndex>
    {
        self.edge.lock().unwrap().from
    }

    fn set_anonymous_stream(&mut self, consumer: AnonymousStreamConsumer)
    {
        let (stream, tag_stream) = consumer.downcast::<T>();
        self.stream = Some(stream);
        self.tag_stream = Some(tag_stream);
    }
}

impl<T: 'static> AnonymousOut for Out<T>
{
    fn set_index(&self, index: BlockIOIndex)
    {
        self.edge.lock().unwrap().from = Some(index);
    }

    fn get_consumer_block(&self) -> Option<BlockIOIndex>
    {
        self.edge.lock().unwrap().to
    }

    fn set_anonymous_stream(&mut self, producer: AnonymousStreamProducer)
    {
        let (stream, tag_stream) = producer.downcast::<T>();
        self.stream = Some(stream);
        self.tag_stream = Some(tag_stream);
    }

    // Delegate stream creation to Out object
    // which knows the stream type
    fn create_anonymous_stream(
        &self,
        capacity: usize,
    ) -> (AnonymousStreamProducer, AnonymousStreamConsumer)
    {
        let (tx, rx) = stream::bounded_queue::<T>(capacity);
        let (tx_tag, rx_tag) = stream::bounded_queue::<TagSlot>(capacity);
        ((tx, tx_tag).into(), (rx, rx_tag).into())
    }
}

/// StreamProducer object for data and tags stored in a type
/// agnostic/erased way.
///
/// This is needed for the graph system to manipulate and pass arround these objects
/// as they can't/don't know about the generic types of the stream objects
pub struct AnonymousStreamProducer
{
    inner: Box<dyn Any>,
    inner_tag: StreamProducer<TagSlot>,
}

/// StreamConsumer object for data and tags stored in a type
/// agnostic/erased way.
///
/// This is needed for the graph system to manipulate and pass arround these objects
/// as they can't/don't know about the generic types of the stream objects
pub struct AnonymousStreamConsumer
{
    inner: Box<dyn Any>,
    inner_tag: StreamConsumer<TagSlot>,
}

impl<T: 'static> From<(StreamProducer<T>, StreamProducer<TagSlot>)> for AnonymousStreamProducer
{
    fn from(value: (StreamProducer<T>, StreamProducer<TagSlot>)) -> Self
    {
        AnonymousStreamProducer {
            inner: Box::new(value.0),
            inner_tag: value.1,
        }
    }
}

impl<T: 'static> From<(StreamConsumer<T>, StreamConsumer<TagSlot>)> for AnonymousStreamConsumer
{
    fn from(value: (StreamConsumer<T>, StreamConsumer<TagSlot>)) -> Self
    {
        AnonymousStreamConsumer {
            inner: Box::new(value.0),
            inner_tag: value.1,
        }
    }
}

impl AnonymousStreamProducer
{
    pub(crate) fn downcast<T: 'static>(self) -> (StreamProducer<T>, StreamProducer<TagSlot>)
    {
        (
            *self.inner.downcast::<StreamProducer<T>>().unwrap(),
            self.inner_tag,
        )
    }
}

impl AnonymousStreamConsumer
{
    pub(crate) fn downcast<T: 'static>(self) -> (StreamConsumer<T>, StreamConsumer<TagSlot>)
    {
        (
            *self.inner.downcast::<StreamConsumer<T>>().unwrap(),
            self.inner_tag,
        )
    }
}
