use proc_macro::TokenStream;
use zyn::ToTokens;
use zyn::ext::AttrExt;
use zyn::ext::FieldsExt;
use zyn::format_ident;
use zyn::parse_input;
use zyn::syn::Index;
use zyn::syn::Lit;
use zyn::syn::spanned::Spanned;

mod sync;

#[derive(zyn::Attribute, Clone, Copy)]
#[zyn("sync_block", about = "Convenient derivation for synchronous blocks")]
struct SyncBlockConfig
{
    #[zyn(default)]
    tagged: bool,
}

#[zyn::attribute]
pub fn sync_block(#[zyn(input)] item: zyn::syn::ItemStruct, args: zyn::Args) -> zyn::TokenStream
{
    let config = SyncBlockConfig {
        tagged: args.iter().any(|x| x.as_flag() == "tagged"),
    };
    use sync::SyncBlockImpl;
    zyn::zyn!(
        @sync_block_impl(item = item, config = config)
    )
}

#[zyn::derive("BlockIO", attributes(input, output), debug = "pretty")]
pub fn block_io(
    #[zyn(input)] ident: zyn::Extract<zyn::syn::Ident>,
    #[zyn(input)] generics: zyn::Extract<zyn::syn::Generics>,
    #[zyn(input)] fields: zyn::Fields,
) -> zyn::TokenStream
{
    let ident = ident.inner();
    let (impl_generics, type_generics, where_clause) = generics.split_for_impl();

    zyn::zyn!(
        impl {{impl_generics}} oxydsp_flowgraph::block::BlockIO for {{ ident.clone() }} {{ type_generics }}
            {{ where_clause }}
        {
            @block_io_get_inputs(fields = fields.clone())
            @block_io_get_outputs(fields = fields.clone())
            @block_io_get_meta(ident = ident.clone(), fields = fields.clone())
        }
    )
}

#[zyn::element]
fn block_io_get_inputs(fields: zyn::syn::Fields) -> zyn::TokenStream
{
    let fields = fields.as_named().unwrap().named.clone();
    zyn::zyn!(
        fn get_inputs_mut(&mut self) -> Vec<&mut dyn oxydsp_flowgraph::io::edge::AnonymousIn>
        {
            let mut acc = vec![];
            use oxydsp_flowgraph::block::BlockInput;
            @for (field in fields.iter().filter(|x| x.attrs.iter().any(|x| x.is("input"))).enumerate())
            {
                acc.extend(self.{{field.1.ident}}.get_inputs_mut());
            }
            acc
        }

        fn get_inputs(&self) -> Vec<&dyn oxydsp_flowgraph::io::edge::AnonymousIn>
        {
            let mut acc = vec![];
            use oxydsp_flowgraph::block::BlockInput;
            @for (field in fields.iter().filter(|x| x.attrs.iter().any(|x| x.is("input"))).enumerate())
            {
                acc.extend(self.{{field.1.ident}}.get_inputs());
            }
            acc
        }
    )
}

#[zyn::element]
fn block_io_get_outputs(fields: zyn::syn::Fields) -> zyn::TokenStream
{
    let fields = fields.as_named().unwrap().named.clone();
    zyn::zyn!(
        fn get_outputs_mut(&mut self) -> Vec<&mut dyn oxydsp_flowgraph::io::edge::AnonymousOut>
        {
            let mut acc = vec![];
            use oxydsp_flowgraph::block::BlockOutput;
            @for (field in fields.iter().filter(|x| x.attrs.iter().any(|x| x.is("output"))).enumerate())
            {
                acc.extend(self.{{field.1.ident}}.get_outputs_mut());
            }
            acc
        }

        fn get_outputs(&self) -> Vec<&dyn oxydsp_flowgraph::io::edge::AnonymousOut>
        {
            let mut acc = vec![];
            use oxydsp_flowgraph::block::BlockOutput;
            @for (field in fields.iter().filter(|x| x.attrs.iter().any(|x| x.is("output"))).enumerate())
            {
                acc.extend(self.{{field.1.ident}}.get_outputs());
            }
            acc
        }
    )
}

#[zyn::element]
fn block_io_get_meta(ident: zyn::syn::Ident, fields: zyn::syn::Fields) -> zyn::TokenStream
{
    let fields = fields.as_named().unwrap().named.clone();
    zyn::zyn!(
        fn get_block_name(&self) -> &'static str
        {
            return {{ ident.to_string() }};
        }

        fn get_input_names(&self) -> Vec<&'static str>
        {
            let mut output = Vec::new();
            @for (field in fields.iter().filter(|x| x.attrs.iter().any(|x| x.is("input"))).enumerate())
            {
                output.push({{ field.1.ident.clone().unwrap().to_string() }});
            }
            return output;
        }
        fn get_output_names(&self) -> Vec<&'static str>
        {
            let mut output = Vec::new();
            @for (field in fields.iter().filter(|x| x.attrs.iter().any(|x| x.is("output"))).enumerate())
            {
                output.push({{ field.1.ident.clone().unwrap().to_string() }});
            }
            return output;
        }

        fn get_output_type_names(&self) -> Vec<&'static str>
        {
            let mut output = Vec::new();
            @for (field in fields.iter().filter(|x| x.attrs.iter().any(|x| x.is("output"))).enumerate())
            {
                output.push(self.{{ field.1.ident.clone() }}.get_type_name());
            }
            return output;
        }
    )
}

#[zyn::element]
fn out_inner_type(ty: zyn::syn::Type) -> zyn::TokenStream
{
    let out_ty = match ty
    {
        zyn::syn::Type::Path(type_path) => match &type_path.path.segments.last().unwrap().arguments
        {
            zyn::syn::PathArguments::AngleBracketed(args) => match args.args.first().unwrap()
            {
                zyn::syn::GenericArgument::Type(x) => Some(x.clone().to_token_stream()),
                _ => None,
            },
            _ => None,
        },
        _ => None,
    };

    if out_ty.is_none()
    {
        bail!("Output type must be a Out<T> type."; span = ty.span());
    }

    out_ty.unwrap()
}

// Generate
#[proc_macro]
pub fn generate_pop_iterable_tuple_impl(input: TokenStream) -> TokenStream
{
    let count = parse_input!(input as Lit);
    let count: usize = match count
    {
        Lit::Int(lit_int) => lit_int.base10_parse::<usize>().unwrap(),
        _ =>
        {
            return zyn::syn::Error::new(count.span(), "Must be an integer")
                .to_compile_error()
                .into();
        }
    };
    let generics = [
        format_ident!("A"),
        format_ident!("B"),
        format_ident!("C"),
        format_ident!("D"),
        format_ident!("E"),
        format_ident!("F"),
        format_ident!("G"),
        format_ident!("H"),
        format_ident!("I"),
        format_ident!("J"),
        format_ident!("K"),
        format_ident!("L"),
        format_ident!("M"),
        format_ident!("N"),
        format_ident!("O"),
        format_ident!("P"),
        format_ident!("Q"),
        format_ident!("R"),
        format_ident!("S"),
        format_ident!("T"),
        format_ident!("U"),
        format_ident!("V"),
        format_ident!("W"),
        format_ident!("X"),
        format_ident!("Y"),
        format_ident!("Z"),
    ];

    zyn::zyn!(
        impl<'a,
            @for (i in 0..count)
            {
                {{ generics[i] }}: 'static,
            }
        > PopIterable<'a> for (
            @for (i in 0..count)
            {
                &mut In<{{ generics[i] }}>,
            }
            )
        {
            type Output = (
            @for (i in 0..count)
            {
                InReader<'a, {{ generics[i] }}>,
            }
            );
            fn pop_iter(&'a mut self) -> PopIter<Self::Output>
            {
                @for (i in 0..count)
                {
                    let {{ i | ident:"reader_{}" }} = self.{{ Index::from(i) }}.read();
                }
               let len = [
                    @for (i in 0..count)
                    {
                        {{ i | ident:"reader_{}" }}.len(),
                    }
                ].into_iter().min().unwrap();
                PopIter {
                    popped: 0,
                    len,
                    reader: (
                        @for (i in 0..count)
                        {
                            {{ i | ident:"reader_{}" }},
                        }
                    ),
                }
             }
        }
    )
    .to_token_stream()
    .into()
}

#[proc_macro]
pub fn impl_iterator_for_pop_iter_tuple(input: TokenStream) -> TokenStream
{
    let count = parse_input!(input as Lit);
    let count: usize = match count
    {
        Lit::Int(lit_int) => lit_int.base10_parse::<usize>().unwrap(),
        _ =>
        {
            return zyn::syn::Error::new(count.span(), "Must be an integer")
                .to_compile_error()
                .into();
        }
    };
    let generics = [
        format_ident!("A"),
        format_ident!("B"),
        format_ident!("C"),
        format_ident!("D"),
        format_ident!("E"),
        format_ident!("F"),
        format_ident!("G"),
        format_ident!("H"),
        format_ident!("I"),
        format_ident!("J"),
        format_ident!("K"),
        format_ident!("L"),
        format_ident!("M"),
        format_ident!("N"),
        format_ident!("O"),
        format_ident!("P"),
        format_ident!("Q"),
        format_ident!("R"),
        format_ident!("S"),
        format_ident!("T"),
        format_ident!("U"),
        format_ident!("V"),
        format_ident!("W"),
        format_ident!("X"),
        format_ident!("Y"),
        format_ident!("Z"),
    ];

    zyn::zyn!(
        impl<'a,
            @for (i in 0..count)
            {
                {{ generics[i] }}: 'static,
            }
        > Iterator for PopIter<(
            @for (i in 0..count)
            {
                InReader<'a, {{ generics[i] }}>,
            }
            )>
        {
            type Item = (
            @for (i in 0..count)
            {
                Tagged<{{ generics[i] }}>,
            }
            );
            fn next(&mut self) -> Option<Self::Item>
            {
                if self.popped < self.len
                {
                    self.popped += 1;

                    Some((
                    @for (i in 0..count)
                    {
                        self.reader.{{ Index::from(i) }}.pop().unwrap(),
                    }
                    ))
                }
                else
                {
                    None
                }
             }
        }
    )
    .to_token_stream()
    .into()
}
// #[proc_macro]
// pub fn generate_push_iterable_tuple_impl(input: TokenStream) -> TokenStream
// {
//     let count = parse_input!(input as Lit);
//     let count: usize = match count
//     {
//         Lit::Int(lit_int) => lit_int.base10_parse::<usize>().unwrap(),
//         _ =>
//         {
//             return zyn::syn::Error::new(count.span(), "Must be an integer")
//                 .to_compile_error()
//                 .into();
//         }
//     };
//     let generics = [
//         format_ident!("A"),
//         format_ident!("B"),
//         format_ident!("C"),
//         format_ident!("D"),
//         format_ident!("E"),
//         format_ident!("F"),
//         format_ident!("G"),
//         format_ident!("H"),
//         format_ident!("I"),
//         format_ident!("J"),
//         format_ident!("K"),
//         format_ident!("L"),
//         format_ident!("M"),
//         format_ident!("N"),
//         format_ident!("O"),
//         format_ident!("P"),
//         format_ident!("Q"),
//         format_ident!("R"),
//         format_ident!("S"),
//         format_ident!("T"),
//         format_ident!("U"),
//         format_ident!("V"),
//         format_ident!("W"),
//         format_ident!("X"),
//         format_ident!("Y"),
//         format_ident!("Z"),
//     ];
//
//     let iterator_item = zyn::zyn!(
//         (
//             @for (i in 0..count)
//             {
//                 ({{ generics[i] }}, Option<Tag>),
//             }
//         )
//     )
//     .to_token_stream();
//
//     zyn::zyn!(
//         impl<'a,
//             @for (i in 0..count)
//             {
//                 {{ generics[i] }}: 'static,
//             }
//         > PushIterable<'a, {{ iterator_item }}> for (
//             @for (i in 0..count)
//             {
//                 &mut Out<{{ generics[i] }}>,
//             }
//             )
//         {
//             fn push_iter<I: Iterator<Item = {{ iterator_item }}>>(
//                 &'a mut self,
//                 mut iter: I,
//             ) -> bool
//             {
//                 @for (i in 0..count)
//                 {
//                     let {{ i | ident:"writer_{}" }} = self.{{ Index::from(i) }}.write();
//                 }
//                let len = [
//                     @for (i in 0..count)
//                     {
//                         {{ i | ident:"reader_{}" }}.len(),
//                     }
//                 ].into_iter().min().unwrap();
//
//                 for _ in 0..len
//                 {
//                     if let Some()
//                 }
//              }
//         }
//     )
//     .to_token_stream()
//     .into()
// }
