use std::sync::Arc;
use std::sync::atomic::AtomicBool;
use std::thread::JoinHandle;

use crossbeam_deque::Steal;
use crossbeam_deque::Worker;

use crate::block::GraphableBlock;
use crate::io::edge::BlockIOIndex;

#[macro_export]
macro_rules! flowgraph
{
    ($($x:ident),* $(,)?) =>
    {
        {
            let mut flowgraph = oxydsp_flowgraph::graph::FlowGraph::new();
            $(
                flowgraph.add_block($x);
            )*
            flowgraph
        }
    }
}

pub struct RunningGraph
{
    worker_handles: Vec<JoinHandle<()>>,
}

impl RunningGraph
{
    pub fn join(self)
    {
        self.worker_handles.into_iter().for_each(|j| {
            let _ = j.join();
        });
    }
}

pub struct FlowGraph
{
    blocks: Vec<Box<dyn GraphableBlock + Send + 'static>>,
}

impl FlowGraph
{
    pub fn new() -> Self
    {
        FlowGraph { blocks: vec![] }
    }

    pub fn add_block<T: GraphableBlock + Send + 'static>(&mut self, block: T)
    {
        block.get_inputs().iter().enumerate().for_each(|(i, x)| {
            x.set_index(BlockIOIndex {
                block_index: self.blocks.len(),
                port_index: i,
            })
        });
        block.get_outputs().iter().enumerate().for_each(|(i, x)| {
            x.set_index(BlockIOIndex {
                block_index: self.blocks.len(),
                port_index: i,
            })
        });
        self.blocks.push(Box::new(block));
    }

    // pub fn run(mut self, thread_count: usize) -> RunningGraph
    // {
    //     self.populate_edges();
    //
    //     let mut worker_queues = (0..thread_count).map(|_| vec![]).collect::<Vec<_>>();
    //
    //     for (i, block) in self.blocks.into_iter().enumerate()
    //     {
    //         worker_queues[i % thread_count].push(block);
    //     }
    //
    //     let running = Arc::new(AtomicBool::new(true));
    //     let worker_handles = worker_queues
    //         .into_iter()
    //         .map(|mut queue| {
    //             let running = running.clone();
    //             std::thread::spawn(move || {
    //                 'outer: while running.load(std::sync::atomic::Ordering::Relaxed)
    //                 {
    //                     for block in queue.iter_mut()
    //                     {
    //                         match block.work()
    //                         {
    //                             crate::block::BlockResult::Ok =>
    //                             {
    //                                 // Reschedule block
    //                             }
    //                             crate::block::BlockResult::Terminated =>
    //                             { // DROP BLOCK
    //                             }
    //                             crate::block::BlockResult::Exit =>
    //                             {
    //                                 println!("KILLING GRAPH");
    //                                 break 'outer;
    //                             }
    //                         }
    //                     }
    //                 }
    //                 running.store(false, std::sync::atomic::Ordering::Relaxed);
    //             })
    //         })
    //         .collect::<Vec<_>>();
    //     RunningGraph { worker_handles }
    // }

    pub fn run(mut self, thread_count: usize) -> RunningGraph
    {
        self.populate_edges();

        let worker_queues = (0..thread_count)
            .map(|_| Worker::<Box<dyn GraphableBlock + Send + 'static>>::new_fifo())
            .collect::<Vec<_>>();

        worker_queues
            .iter()
            .cycle()
            .zip(self.blocks)
            .for_each(|(worker, block)| worker.push(block));
        let stealers = worker_queues
            .iter()
            .map(|x| x.stealer())
            .collect::<Vec<_>>();

        let running = Arc::new(AtomicBool::new(true));
        let worker_handles = worker_queues
            .into_iter()
            .map(|queue| {
                let stealers = stealers.clone();
                let running = running.clone();
                std::thread::spawn(move || {
                    'outer: while running.load(std::sync::atomic::Ordering::Relaxed)
                    {
                        // Try to get a job
                        let mut block = queue.pop().unwrap_or_else(|| {
                            std::iter::repeat_with(|| {
                                stealers
                                    .iter()
                                    .map(|stealer| stealer.steal_batch_and_pop(&queue))
                                    .collect::<Steal<_>>()
                                    .success()
                            })
                            .find(|x| x.is_some())
                            .unwrap()
                            .unwrap()
                        });

                        match block.work()
                        {
                            crate::block::BlockResult::Ok =>
                            {
                                // Reschedule block
                                queue.push(block);
                            }
                            crate::block::BlockResult::Terminated =>
                            { // DROP BLOCK
                            }
                            crate::block::BlockResult::Exit =>
                            {
                                println!("KILLING GRAPH");
                                break 'outer;
                            }
                        }
                    }
                    running.store(false, std::sync::atomic::Ordering::Relaxed);
                })
            })
            .collect::<Vec<_>>();
        RunningGraph { worker_handles }
    }

    fn populate_edges(&mut self)
    {
        for block_index in 0..self.blocks.len()
        {
            let outputs = self.blocks[block_index].get_outputs_mut();
            let mut rxs = vec![];

            for output in outputs.into_iter()
            {
                //let (tx, rx) = output.create_anonymous_stream(4096);
                let (tx, rx) = output.create_anonymous_stream(8192);
                //let (tx, rx) = output.create_anonymous_stream(65536);

                output.set_anonymous_stream(tx);
                rxs.push((
                    output
                        .get_consumer_block()
                        .expect("Non existent destination block."),
                    rx,
                ))
            }

            for (index, rx) in rxs.into_iter()
            {
                self.blocks[index.block_index].get_inputs_mut()[index.port_index]
                    .set_anonymous_stream(rx);
            }
        }
    }

    pub fn get_dot(&self) -> String
    {
        let mut node_string = String::new();

        for (i, block) in self.blocks.iter().enumerate()
        {
            // Block name

            // Input strings
            let mut input_string = String::new();
            let inputs = block.get_input_names();
            let len = inputs.len();
            if !inputs.is_empty()
            {
                input_string.push('{');
                for (j, input) in inputs.iter().enumerate()
                {
                    input_string.push_str(&format!("<i{}> {}", j, input));
                    if j != len - 1
                    {
                        input_string.push('|');
                    }
                }
                input_string.push_str("}|");
            }

            // Output strings
            let mut output_string = String::new();
            let outputs = block.get_output_names();
            let len = outputs.len();
            if !outputs.is_empty()
            {
                output_string.push_str("|{");
                for (j, output) in outputs.iter().enumerate()
                {
                    output_string.push_str(&format!("<o{}> {}", j, output));
                    if j != len - 1
                    {
                        output_string.push('|');
                    }
                }
                output_string.push('}');
            }

            node_string.push_str(&format!(
                "{}_{} [label=\"{{ {} {} {} }}\"];\n",
                block.get_block_name(),
                i,
                input_string,
                block.get_block_name(),
                output_string,
            ));
        }

        let mut edges_string = String::new();

        for (i, block) in self.blocks.iter().enumerate()
        {
            let outputs = block.get_successors();
            let output_types = block.get_output_type_names();
            let block_name = block.get_block_name();
            for (j, (output, output_type)) in outputs.iter().zip(output_types.iter()).enumerate()
            {
                let destination_block = output.block_index;
                let destination_block_name = self.blocks[destination_block].get_block_name();
                edges_string.push_str(&format!(
                    "{}_{}:o{} -> {}_{}:i{} [label=\"{}\"];\n",
                    block_name,
                    i,
                    j,
                    destination_block_name,
                    destination_block,
                    output.port_index,
                    output_type
                ));
            }
        }

        format!(
            "
            digraph G {{
                node [shape=record];
                rankdir=TB;
               {}
               {}
            }}
            ",
            node_string, edges_string
        )
    }
}

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