use crate::tests::*;
use proc_macro2::TokenStream;
use proptest::prelude::*;
use quote::{format_ident, quote};

#[derive(Debug)]
pub struct Packet {
    blocks: Vec<Struct>,
    payload: Option<Struct>,
    pub name: String,
}

impl Arbitrary for Packet {
    type Parameters = ();

    type Strategy = BoxedStrategy<Self>;

    fn arbitrary_with(_: ()) -> Self::Strategy {
        (
            prop::collection::vec(Struct::arbitrary_with((Target::Block, 0)), 1..5),
            prop::option::of(Struct::arbitrary_with((Target::Payload, 0))),
        )
            .prop_map(move |(blocks, payload)| Packet {
                name: gen_name(false),
                blocks,
                payload,
            })
            .boxed()
    }
}

fn variant_name<S: AsRef<str>>(name: S) -> String {
    name.as_ref()
        .split("::")
        .map(|s| {
            let mut chars = s.trim().chars();
            match chars.next() {
                Some(first) => first.to_uppercase().chain(chars).collect(),
                None => String::new(),
            }
        })
        .collect::<Vec<String>>()
        .join("")
}
impl Generate for Packet {
    type Options = ();
    fn declaration(&self, _: Self::Options) -> TokenStream {
        let blocks = self
            .blocks
            .iter()
            .map(|b| b.declaration(Target::Block))
            .collect::<Vec<TokenStream>>();
        let payload = self
            .payload
            .as_ref()
            .map(|p| p.declaration(Target::Payload))
            .unwrap_or_default();
        quote! {
            #(#blocks)*
            #payload
        }
    }
    fn instance(&self, _: Self::Options) -> TokenStream {
        let blocks = self
            .blocks
            .iter()
            .map(|b| {
                let instance = b.instance(Target::Block);
                let name = format_ident!("{}", variant_name(&b.name));
                quote! {
                    Block::#name(#instance)
                }
            })
            .collect::<Vec<TokenStream>>();
        let payload = self
            .payload
            .as_ref()
            .map(|p| {
                let payload = p.instance(Target::Payload);
                let name = format_ident!("{}", variant_name(&p.name));
                quote! { Some( Payload::#name(#payload) ) }
            })
            .unwrap_or(quote! { None });
        quote! {
            Packet::new(
                vec![#(#blocks,)*],
                #payload
            )
        }
    }
}
