use crate::io::edge::AnonymousIn;
use crate::io::edge::AnonymousOut;
use crate::io::In;
use crate::io::Out;
use crate::io::edge::BlockIOIndex;

pub enum BlockResult
{
    // Signifies that the block can be scheduled again.
    Ok,

    // Signifies that the block finished its work
    // Running it again would be useless
    Terminated,

    // Kill graph
    Exit,
}

pub trait BlockInput
{
    fn get_inputs_mut(&mut self) -> Vec<&mut dyn AnonymousIn>;
    fn get_inputs(&self) -> Vec<&dyn AnonymousIn>;

    // Meta information
    fn get_types_names(&self) -> Vec<&'static str>;
}

pub trait BlockOutput
{
    fn get_outputs_mut(&mut self) -> Vec<&mut dyn AnonymousOut>;
    fn get_outputs(&self) -> Vec<&dyn AnonymousOut>;

    // Meta information
    fn get_types_names(&self) -> Vec<&'static str>;
}

pub trait BlockIO
{
    fn get_inputs_mut(&mut self) -> Vec<&mut dyn AnonymousIn>;
    fn get_outputs_mut(&mut self) -> Vec<&mut dyn AnonymousOut>;

    fn get_inputs(&self) -> Vec<&dyn AnonymousIn>;
    fn get_outputs(&self) -> Vec<&dyn AnonymousOut>;

    fn get_successors(&self) -> Vec<BlockIOIndex>
    {
        self.get_outputs()
            .iter()
            .map(|x| x.get_consumer_block().unwrap())
            .collect()
    }

    // Get all of the BlockIOIndices (block index + port) that
    // this blocks can send data to.
    // fn get_successors(&self) -> Vec<BlockIOIndex>;
    //
    // // Sets the index of the current blocks on the shared edges
    // fn set_index(&self, block_index: usize);
    //
    // // Number of input/output ports
    // fn input_count(&self) -> usize;
    // fn output_count(&self) -> usize;
    //
    // // Stream managment
    // fn set_anonymous_out_stream(&mut self, output_index: usize, producer: AnonymousStreamProducer);
    // fn set_anonymous_in_stream(&mut self, input_index: usize, consumer: AnonymousStreamConsumer);
    //
    // fn create_anonymous_stream_for(
    //     &mut self,
    //     output_index: usize,
    //     capacity: usize,
    // ) -> (AnonymousStreamProducer, AnonymousStreamConsumer);

    // Meta information
    fn get_block_name(&self) -> &'static str;
    fn get_input_names(&self) -> Vec<&'static str>;
    fn get_output_names(&self) -> Vec<&'static str>;
    fn get_output_type_names(&self) -> Vec<&'static str>;
}

pub trait Block
{
    fn work(&mut self) -> BlockResult;
}

// Represents the input, output, state types
// that a SyncBlock will have to interacti with
pub trait SyncBlockIO<'view>
{
    type StateView;
    type Input;
    type Output;
}

pub trait SyncBlock<'view>: SyncBlockIO<'view>
{
    fn sync_work(state: Self::StateView, input: Self::Input) -> Option<Self::Output>;
}

pub trait GraphableBlock: Block + BlockIO {}

impl<T> GraphableBlock for T where T: Block + BlockIO {}

impl<T: 'static> BlockInput for In<T>
{
    fn get_inputs_mut(&mut self) -> Vec<&mut dyn AnonymousIn>
    {
        vec![self]
    }

    fn get_inputs(&self) -> Vec<&dyn AnonymousIn>
    {
        vec![self]
    }

    fn get_types_names(&self) -> Vec<&'static str>
    {
        vec![std::any::type_name::<T>()]
    }
}

impl<I: BlockInput> BlockInput for Option<I>
{
    fn get_inputs_mut(&mut self) -> Vec<&mut dyn AnonymousIn>
    {
        if let Some(input) = self
        {
            input.get_inputs_mut()
        }
        else
        {
            vec![]
        }
    }

    fn get_inputs(&self) -> Vec<&dyn AnonymousIn>
    {
        if let Some(input) = self
        {
            input.get_inputs()
        }
        else
        {
            vec![]
        }
    }

    fn get_types_names(&self) -> Vec<&'static str>
    {
        if let Some(input) = self
        {
            input.get_types_names()
        }
        else
        {
            vec![]
        }
    }
}

impl<I: BlockInput, const N: usize> BlockInput for [I; N]
{
    fn get_inputs(&self) -> Vec<&dyn AnonymousIn>
    {
        let mut output = vec![];
        for input in self
        {
            output.extend(input.get_inputs());
        }
        output
    }

    fn get_inputs_mut(&mut self) -> Vec<&mut dyn AnonymousIn>
    {
        let mut output = vec![];
        for input in self
        {
            output.extend(input.get_inputs_mut());
        }
        output
    }

    fn get_types_names(&self) -> Vec<&'static str>
    {
        vec![std::any::type_name::<I>(); N]
    }
}

impl<T: 'static> BlockOutput for Out<T>
{
    fn get_outputs_mut(&mut self) -> Vec<&mut dyn AnonymousOut>
    {
        vec![self]
    }

    fn get_outputs(&self) -> Vec<&dyn AnonymousOut>
    {
        vec![self]
    }

    fn get_types_names(&self) -> Vec<&'static str>
    {
        vec![std::any::type_name::<T>()]
    }
}

impl<I: BlockOutput> BlockOutput for Option<I>
{
    fn get_outputs_mut(&mut self) -> Vec<&mut dyn AnonymousOut>
    {
        if let Some(output) = self
        {
            output.get_outputs_mut()
        }
        else
        {
            vec![]
        }
    }

    fn get_outputs(&self) -> Vec<&dyn AnonymousOut>
    {
        if let Some(output) = self
        {
            output.get_outputs()
        }
        else
        {
            vec![]
        }
    }

    fn get_types_names(&self) -> Vec<&'static str>
    {
        if let Some(input) = self
        {
            input.get_types_names()
        }
        else
        {
            vec![]
        }
    }
}

impl<I: BlockOutput, const N: usize> BlockOutput for [I; N]
{
    fn get_outputs_mut(&mut self) -> Vec<&mut dyn AnonymousOut>
    {
        let mut result = vec![];
        for output in self
        {
            result.extend(output.get_outputs_mut());
        }
        result
    }

    fn get_outputs(&self) -> Vec<&dyn AnonymousOut>
    {
        let mut result = vec![];
        for output in self
        {
            result.extend(output.get_outputs());
        }
        result
    }

    fn get_types_names(&self) -> Vec<&'static str>
    {
        vec![std::any::type_name::<I>(); N]
    }
}
