use std::mem::ManuallyDrop;
use std::mem::MaybeUninit;
use std::sync::Arc;
use std::sync::Mutex;

use crate::stream::StreamConsumer;
use crate::stream::StreamProducer;
use crate::stream::StreamReader;
use crate::stream::StreamWriter;
use crate::tag::TagSlot;
use crate::tag::Tagged;

pub mod edge;

use crate::io::edge::Edge;

/// Represents a input port for a block
pub struct In<T>
{
    stream: Option<StreamConsumer<T>>,
    tag_stream: Option<StreamConsumer<TagSlot>>,

    // Will rarely be accessed
    edge: Arc<Mutex<Edge>>,
}

/// Represents a output port for a block
pub struct Out<T>
{
    stream: Option<StreamProducer<T>>,
    tag_stream: Option<StreamProducer<TagSlot>>,

    // Will rarely be accessed
    edge: Arc<Mutex<Edge>>,
}

// Input Output interfaces

/// Output interface to write elements in a "push" fashion
pub struct OutPush<'a, T>
{
    data_writer: ManuallyDrop<StreamWriter<'a, T>>,
    tag_writer: ManuallyDrop<StreamWriter<'a, TagSlot>>,

    total_length: usize,
    written_data: usize,
    written_tags: usize,
    start_index: usize,
}

impl<'a, T> OutPush<'a, T>
{
    pub fn len(&self) -> usize
    {
        let data_slices = self.data_writer.slices();
        // This gives better performance !
        // Probably because it prevents claiming the whole buffer at once
        // But this is hacky. It should probably be managed at a lower level
        // ((data_slices.0.len() + data_slices.1.len()) / 2) - self.written_data

        data_slices.0.len() + data_slices.1.len() - self.written_data
    }

    pub fn is_empty(&self) -> bool
    {
        self.len() == 0
    }

    pub fn push(&mut self, data: Tagged<T>) -> Result<(), Tagged<T>>
    {
        if self.written_data >= self.total_length
        {
            return Err(data);
        }

        let data_slices = self.data_writer.slices_mut();
        let tag_slices = self.tag_writer.slices_mut();

        // Write a data
        let data_ref = {
            if self.written_data < data_slices.0.len()
            {
                &mut data_slices.0[self.written_data]
            }
            else
            {
                &mut data_slices.1[self.written_data - data_slices.0.len()]
            }
        };
        // Index of the taken element within the stream.
        *data_ref = MaybeUninit::new(data.0);
        let element_index = self.start_index + self.written_data;
        self.written_data += 1;

        // Check for corresponding tag
        let tag_ref = {
            if self.written_tags < tag_slices.0.len()
            {
                &mut tag_slices.0[self.written_tags]
            }
            else
            {
                &mut tag_slices.1[self.written_tags - tag_slices.0.len()]
            }
        };

        if let Some(tag) = data.1
        {
            *tag_ref = MaybeUninit::new(TagSlot {
                position: element_index,
                tag,
            });
            self.written_tags += 1;
        }
        Ok(())
    }
}

impl<'a, T> Drop for OutPush<'a, T>
{
    fn drop(&mut self)
    {
        let data_writer =
            unsafe { ManuallyDrop::<StreamWriter<'a, T>>::take(&mut self.data_writer) };
        let tag_writer =
            unsafe { ManuallyDrop::<StreamWriter<'a, TagSlot>>::take(&mut self.tag_writer) };
        tag_writer.produce(self.written_tags);
        data_writer.produce(self.written_data);
    }
}

pub struct InIter<'a, T>
{
    data_reader: ManuallyDrop<StreamReader<'a, T>>,
    tag_reader: ManuallyDrop<StreamReader<'a, TagSlot>>,

    total_length: usize,
    total_tag_length: usize,
    read_data: usize,
    read_tags: usize,
    start_index: usize,
}

impl<'a, T> Iterator for InIter<'a, T>
{
    type Item = Tagged<T>;

    fn next(&mut self) -> Option<Self::Item>
    {
        if self.read_data >= self.total_length
        {
            return None;
        }

        let data_slices = self.data_reader.slices_mut();
        let tag_slices = self.tag_reader.slices_mut();

        // Take a data
        // SAFETY:
        // All takable should contain a valid element: guarteed by the queue
        // We strictly monotonicly take all elements, in order, in the slices.
        // No two same indices should be taken.
        // We cound the number of taken elemnts and consume the correct amount from the queue
        let data = {
            if self.read_data < data_slices.0.len()
            {
                unsafe { data_slices.0[self.read_data].take() }
            }
            else
            {
                unsafe { data_slices.1[self.read_data - data_slices.0.len()].take() }
            }
        };
        // Index of the taken element within the stream.
        let element_index = self.start_index + self.read_data;
        self.read_data += 1;

        // Check for corresponding tag
        let mut tag = None;
        if self.read_tags < self.total_tag_length
        {
            let tag_ref = {
                if self.read_tags < tag_slices.0.len()
                {
                    &mut tag_slices.0[self.read_tags]
                }
                else
                {
                    &mut tag_slices.1[self.read_tags - tag_slices.0.len()]
                }
            };

            // SAFETY:
            // Same as before : strictly monotic access in the tag slices
            if unsafe { tag_ref.peek().position == element_index }
            {
                // The next tag in line is tagging the just-poped element.
                // We get it
                tag = Some(unsafe { tag_ref.take().tag });
                self.read_tags += 1;
            }
        }

        Some(Tagged::new(data, tag))
    }

    fn size_hint(&self) -> (usize, Option<usize>)
    {
        let len =
            self.data_reader.slices().0.len() + self.data_reader.slices().1.len() - self.read_data;
        (len, Some(len))
    }
}

impl<'a, T> Drop for InIter<'a, T>
{
    fn drop(&mut self)
    {
        let data_reader =
            unsafe { ManuallyDrop::<StreamReader<'a, T>>::take(&mut self.data_reader) };
        let tag_reader =
            unsafe { ManuallyDrop::<StreamReader<'a, TagSlot>>::take(&mut self.tag_reader) };
        tag_reader.consume(self.read_tags);
        data_reader.consume(self.read_data);
    }
}

impl<'a, T> ExactSizeIterator for InIter<'a, T> {}

impl<T: 'static> In<T>
{
    pub fn iter<'a>(&'a mut self) -> InIter<'a, T>
    {
        let first_index = self.stream.as_ref().unwrap().first_index();
        let data_reader = self.stream.as_mut().unwrap().read_takable();
        let total_length = data_reader.slices().0.len() + data_reader.slices().1.len();
        let tag_reader = self.tag_stream.as_mut().unwrap().read_takable();
        let total_tag_length = tag_reader.slices().0.len() + tag_reader.slices().1.len();

        InIter {
            data_reader: ManuallyDrop::new(data_reader),
            tag_reader: ManuallyDrop::new(tag_reader),
            read_data: 0,
            read_tags: 0,
            total_length,
            total_tag_length,
            start_index: first_index,
        }
    }
}

impl<T: 'static> Out<T>
{
    pub fn write_push<'a>(&'a mut self) -> OutPush<'a, T>
    {
        let first_index = self.stream.as_ref().unwrap().first_index();
        let data_writer = self.stream.as_mut().unwrap().write();
        let total_length = data_writer.slices().0.len() + data_writer.slices().1.len();

        let tag_writer = self.tag_stream.as_mut().unwrap().write();

        OutPush {
            data_writer: ManuallyDrop::new(data_writer),
            tag_writer: ManuallyDrop::new(tag_writer),
            total_length,
            written_data: 0,
            written_tags: 0,
            start_index: first_index,
        }
    }

    /// Pushes an iterator to the output, sending the maximum amount of elements
    /// to the output.
    ///
    /// It will not consume the iterator more than what can be sent.
    ///
    /// ```
    /// let writer = output.write();
    ///
    /// // Send only 42s to the output
    /// writer.push_iter(std::iter::repeat(42));
    /// ```
    pub fn push_iter<I: Iterator<Item = Tagged<T>>>(&mut self, mut iter: I) -> bool
    {
        let mut pusher = self.write_push();
        let mut len = pusher.len();

        while len > 0
        {
            len -= 1;
            match iter.next()
            {
                Some(element) => {let _ = pusher.push(element); },
                None => return false,
            }
        }

        return false;
    }

    /// Meta information
    /// Returns a string of the type of the output
    pub fn get_type_name(&self) -> &'static str
    {
        std::any::type_name::<T>()
    }
}

/// Creates a stream that can then be used to link blocks
///
/// ```rust
/// let (output, input) = oxydsp-flowgraph::io::stream();
///
/// let writer = output.write();
/// let reader = input.read();
///
/// // ...
/// ```
pub fn stream<T>() -> (Out<T>, In<T>)
{
    let edge = Arc::new(Mutex::new(Edge::default()));
    (
        Out {
            stream: None,
            tag_stream: None,
            edge: edge.clone(),
        },
        In {
            stream: None,
            tag_stream: None,
            edge,
        },
    )
}

pub fn streams<T, const N: usize>() -> ([Out<T>; N], [In<T>; N])
{
    // Ugly simultanous initialization
    let mut ins: [_; N] = std::array::from_fn(|_| None);
    let mut outs: [_; N] = std::array::from_fn(|_| None);

    ins.iter_mut()
        .zip(outs.iter_mut())
        .for_each(|(input, output)| {
            let (newout, newin) = stream();
            *input = Some(newin);
            *output = Some(newout);
        });

    let ins_some: [_; N] = std::array::from_fn(|i| ins[i].take().unwrap());
    let outs_some: [_; N] = std::array::from_fn(|i| outs[i].take().unwrap());

    (outs_some, ins_some)
}

// --------------------
// Iterator facilites
// --------------------

// An iterator type to push data to output(s)
// pub struct PopIter<T>
// {
//     len: usize,
//     popped: usize,
//     reader: T,
// }

// Type on which data can be popped from
// pub trait PopIterable<'a>
// {
//     type Output;
//
//     /// Returns an iterator on the input elements :
//     ///
//     /// ```
//     /// (&mut input_a, &mut input_b, &mut input_c).pop_iter().for_each(|(a, b, c)| println!("Got {a}, {b} and {c} !"));
//     /// ```
//     fn pop_iter(&'a mut self) -> PopIter<Self::Output>;
// }

// impl<'a, T: 'static> PopIterable<'a> for In<T>
// {
//     type Output = InReader<'a, T>;
//     fn pop_iter(&'a mut self) -> PopIter<InReader<'a, T>>
//     {
//         let reader = self.read();
//         PopIter {
//             popped: 0,
//             len: reader.len(),
//             reader,
//         }
//     }
// }
//
// generate_pop_iterable_tuple_impl! {1}
// generate_pop_iterable_tuple_impl! {2}
// generate_pop_iterable_tuple_impl! {3}
// generate_pop_iterable_tuple_impl! {4}
// generate_pop_iterable_tuple_impl! {5}
// generate_pop_iterable_tuple_impl! {6}
// generate_pop_iterable_tuple_impl! {7}
// generate_pop_iterable_tuple_impl! {8}
// generate_pop_iterable_tuple_impl! {9}
// generate_pop_iterable_tuple_impl! {10}
// generate_pop_iterable_tuple_impl! {11}
// generate_pop_iterable_tuple_impl! {12}
//
// impl<'a, T> Iterator for PopIter<InReader<'a, T>>
// {
//     type Item = Tagged<T>;
//
//     fn next(&mut self) -> Option<Self::Item>
//     {
//         self.reader.pop()
//     }
// }

// impl_iterator_for_pop_iter_tuple! {1}
// impl_iterator_for_pop_iter_tuple! {2}
// impl_iterator_for_pop_iter_tuple! {3}
// impl_iterator_for_pop_iter_tuple! {4}
// impl_iterator_for_pop_iter_tuple! {5}
// impl_iterator_for_pop_iter_tuple! {6}
// impl_iterator_for_pop_iter_tuple! {7}
// impl_iterator_for_pop_iter_tuple! {8}
// impl_iterator_for_pop_iter_tuple! {9}
// impl_iterator_for_pop_iter_tuple! {10}
// impl_iterator_for_pop_iter_tuple! {11}
// impl_iterator_for_pop_iter_tuple! {12}
