use std::collections::HashSet;

use proc_macro2::{Span, TokenStream};
use quote::{format_ident, quote};
use syn::{spanned::Spanned, Attribute, DeriveInput, Generics, Ident, Path, TypeParamBound};

/// Internal implementation of [`super::derive_no_chip_args`].
pub fn derive_no_chip_args_impl(input: DeriveInput) -> syn::Result<TokenStream> {
    let name = input.ident;
    let generics = input.generics;
    let module: Ident = input
        .attrs
        .iter()
        .find_map(|attr| attr.path().is_ident("support_module").then(|| attr.parse_args()))
        .unwrap_or(Ok(format_ident!("extractor_support")))?;
    // Split generics into (impl generics) (ty generics) (where clause)
    let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();

    Ok(quote! {
        impl #impl_generics #module::circuit::NoChipArgs for #name #ty_generics #where_clause {}
    })
}

/// Internal implementation of [`super::derive_circuit_initialization_from_scratch`].
pub fn derive_circuit_initialization_from_scratch_impl(
    input: DeriveInput,
) -> syn::Result<TokenStream> {
    let input_span = input.span();
    let name = input.ident;
    let mut generics = input.generics;

    let l = unique_layouter_ident(&generics);
    let f = find_field_param(&generics, &input.attrs, input_span)?;
    let other_params = find_annotated_params(&input.attrs)?;

    let self_bound = {
        let (_, ty_generics, _) = generics.split_for_impl();
        quote! {
            #name #ty_generics: crate::testing_utils::FromScratch<#f>
        }
    };
    let where_clause = generics.make_where_clause();

    let predicates = &mut where_clause.predicates;

    predicates.push(syn::parse2(self_bound)?);
    predicates.push(syn::parse2(quote! {
        #l: midnight_proofs::circuit::Layouter<#f>
    })?);
    predicates.push(syn::parse2(quote! {
        #f: ff::PrimeField
    })?);
    for param in other_params {
        predicates.push(syn::parse2(quote! {
            #param: crate::testing_utils::FromScratch<#f>
        })?);
    }

    let type_params = generics.type_params();
    let lifetimes = generics.lifetimes();
    let const_params = generics.const_params();
    let (_, ty_generics, where_clause) = generics.split_for_impl();

    let as_from_scratch = quote! {
        <#name #ty_generics as crate::testing_utils::FromScratch<#f>>
    };
    let midnight_proofs = format_ident!("midnight_proofs");

    let code = quote! {
        impl<#(#lifetimes,)* #l, #(#type_params,)* #(#const_params,)*>
            extractor_support::circuit::CircuitInitialization<#l> for #name #ty_generics
        #where_clause
        {
            type Config = #as_from_scratch::Config;
            type Args = ();
            type ConfigCols =
                [#midnight_proofs::plonk::Column<#midnight_proofs::plonk::Instance>; 2];
            type CS = #midnight_proofs::plonk::ConstraintSystem<#f>;
            type Error = #midnight_proofs::plonk::Error;

            fn new_chip(config: &Self::Config, _: Self::Args) -> Self {
                #as_from_scratch::new_from_scratch(config)
            }

            fn configure_circuit(
                meta: &mut Self::CS,
                instance_columns: &Self::ConfigCols,
            ) -> Self::Config {
                #as_from_scratch::configure_from_scratch(meta, instance_columns)
            }

            fn load_chip(
                &self,
                layouter: &mut #l,
                _config: &Self::Config,
            ) -> Result<(), Self::Error> {
                use crate::testing_utils::FromScratch;
                self.load_from_scratch(layouter)
            }

        }
    };
    log::debug!("generated:\n==============\n{code}\n==============");
    Ok(code)
}

fn find_annotated_params(attrs: &[Attribute]) -> syn::Result<Vec<Path>> {
    attrs
        .iter()
        .filter_map(|a| a.path().is_ident("from_scratch").then(|| a.parse_args::<Path>()))
        .collect()
}

fn unique_layouter_ident(generics: &Generics) -> syn::Ident {
    let idents = generics
        .type_params()
        .map(|t| &t.ident)
        .chain(generics.const_params().map(|c| &c.ident))
        .collect::<HashSet<_>>();
    let base = "__Layouter";
    std::iter::repeat(base)
        .enumerate()
        .map(|(n, base)| {
            if n == 0 {
                return format_ident!("{base}");
            }
            format_ident!("{base}{n}")
        })
        .find(|i| !idents.contains(&i))
        .unwrap()
}

enum FieldParam {
    Ident(Ident),
    Path(Path),
}

impl quote::ToTokens for FieldParam {
    fn to_tokens(&self, tokens: &mut TokenStream) {
        match self {
            FieldParam::Ident(ident) => ident.to_tokens(tokens),
            FieldParam::Path(path) => path.to_tokens(tokens),
        }
    }
}

impl PartialEq<syn::Ident> for FieldParam {
    fn eq(&self, other: &syn::Ident) -> bool {
        match self {
            FieldParam::Ident(ident) => ident == other,
            FieldParam::Path(_) => false,
        }
    }
}

fn find_field_param(
    generics: &Generics,
    attrs: &[Attribute],
    input_span: Span,
) -> syn::Result<FieldParam> {
    use TypeParamBound::Trait;
    // First look for types annotated with #[field].
    attrs
        .iter()
        .find_map(|a| {
            a.path().is_ident("field").then(|| a.parse_args::<Path>().map(FieldParam::Path))
        })
        // If none was annotated that way then check for types that declare satisfying
        // the Field trait.
        .or_else(|| {
            generics.type_params().find_map(|ty| {
                ty.bounds
                    .iter()
                    .any(|b| {
                        let Trait(trait_bound) = b else {
                            return false;
                        };
                        let ident = trait_bound.path.segments.last().map(|s| s.ident.to_string());
                        matches!(ident.as_deref(), Some("Field" | "PrimeField"))
                    })
                    .then(|| Ok(FieldParam::Ident(ty.ident.clone())))
            })
        })
        // Lastly default to being named 'F'
        .or_else(|| {
            generics
                .type_params()
                .find_map(|ty| (ty.ident == "F").then(|| Ok(FieldParam::Ident(ty.ident.clone()))))
        })
        .ok_or_else(|| {
            syn::Error::new(
                input_span,
                "Derived struct requires at least one type parameter implementing ff::PrimeField",
            )
        })?
}

#[cfg(test)]
mod tests {
    use super::*;
    use log::LevelFilter;
    use macro_expand::Context;
    use rstest::{fixture, rstest};
    use simplelog::{Config, TestLogger};

    #[fixture]
    fn ctx() -> Context<'static> {
        let _ = TestLogger::init(LevelFilter::Debug, Config::default());
        let mut ctx = Context::new();
        ctx.register_proc_macro_derive(
            "InitFromScratch".into(),
            |input| derive_circuit_initialization_from_scratch_impl(input).unwrap(),
            vec!["field".to_string(), "from_scratch".to_string()],
        )
        .register_proc_macro_derive(
            "NoChipArgs".into(),
            |input| derive_no_chip_args_impl(input).unwrap(),
            vec!["support_module".to_string()],
        );

        ctx
    }

    macro_rules! parse {
        ($s:expr) => {{
            let ts: proc_macro2::TokenStream = $s.parse().unwrap();
            ts
        }};
    }

    macro_rules! unparse {
        ($ts:expr) => {
            prettyplease::unparse(&syn::parse2($ts).unwrap())
        };
    }

    // Each #[should_panic] affects only the #[case] below it

    #[rstest]
    #[case::no_chip_args_default(
        "#[derive(NoChipArgs)] struct S {}",
        r"
        struct S {}
        impl extractor_support::circuit::NoChipArgs for S {}
        "
    )]
    #[case::no_chip_args_configured(
        "#[derive(NoChipArgs)] #[support_module(other_module)] struct S {}",
        r"
        struct S {}
        impl other_module::circuit::NoChipArgs for S {}
        "
    )]
    #[should_panic(expected = "unexpected token")]
    #[case::no_chip_args_fail_not_ident(
        "#[derive(NoChipArgs)] #[support_module(a + 2)] struct S {}",
        r"
        struct S {}
        "
    )]
    #[should_panic(
        expected = "Derived struct requires at least one type parameter implementing ff::PrimeField"
    )]
    #[case::no_type_param(
        "#[derive(InitFromScratch)] struct S {}",
        r"
        struct S {}
        impl<__Layouter> extractor_support::circuit::CircuitInitialization<__Layouter> for S 
        where 
            __Layouter: midnight_proofs::circuit::Layouter<F>,
            F: ff::PrimeField,
        {}
        "
    )]
    #[case::basic(
        r"
        #[derive(InitFromScratch)]
        struct S<F> { f: F }
        ",
        r"
        struct S<F> { f: F }
        impl<__Layouter, F> extractor_support::circuit::CircuitInitialization<__Layouter> for S<F> 
        where 
            S<F>: crate::testing_utils::FromScratch<F>,
            __Layouter: midnight_proofs::circuit::Layouter<F>,
            F: ff::PrimeField,
        {
            type Config = <S<F> as crate::testing_utils::FromScratch<F>>::Config;
            type Args = ();
            type ConfigCols =
                [midnight_proofs::plonk::Column<midnight_proofs::plonk::Instance>; 2];
            type CS = midnight_proofs::plonk::ConstraintSystem<F>;
            type Error = midnight_proofs::plonk::Error;

            fn new_chip(config: &Self::Config, _: Self::Args) -> Self {
                <S<F> as crate::testing_utils::FromScratch<F>>::new_from_scratch(config)
            }

            fn configure_circuit(
                meta: &mut Self::CS,
                instance_columns: &Self::ConfigCols,
            ) -> Self::Config {
                <S<F> as crate::testing_utils::FromScratch<F>>::configure_from_scratch(meta, instance_columns)
            }

            fn load_chip(
                &self,
                layouter: &mut __Layouter,
                _config: &Self::Config,
            ) -> Result<(), Self::Error> {
                use crate::testing_utils::FromScratch;
                self.load_from_scratch(layouter)
            }}
        "
    )]
    #[should_panic(
        expected = "Derived struct requires at least one type parameter implementing ff::PrimeField"
    )]
    #[case::layouter_type_name_collision(
        r"
        #[derive(InitFromScratch)]
        struct S<__Layouter> { f: __Layouter }
        ",
        r"
        struct S<__Layouter> { f: __Layouter }
        impl<__Layouter1, __Layouter> extractor_support::circuit::CircuitInitialization<__Layouter1> for S<__Layouter> 
        where 
            __Layouter: midnight_proofs::circuit::Layouter<F>,
            F: ff::PrimeField,
        {}
        "
    )]
    #[should_panic(
        expected = "Derived struct requires at least one type parameter implementing ff::PrimeField"
    )]
    #[case::no_field_impl_and_lifetime(
        r"
        #[derive(InitFromScratch)]
        struct S<'a> { f: &'a str }
        ",
        r"
        struct S<'a> { f: &'a str }
        impl<'a, __Layouter> extractor_support::circuit::CircuitInitialization<__Layouter> for S<'a> 
        where 
            __Layouter: midnight_proofs::circuit::Layouter<F>,
            F: ff::PrimeField,
        {}
        "
    )]
    #[case::with_lifetime(
        r"
        #[derive(InitFromScratch)]
        struct S<'a, F> { f: &'a F }
        ",
        r"
        struct S<'a, F> { f: &'a F }
        impl<'a, __Layouter, F> extractor_support::circuit::CircuitInitialization<__Layouter> for S<'a, F> 
        where 
            S<'a,F>: crate::testing_utils::FromScratch<F>,
            __Layouter: midnight_proofs::circuit::Layouter<F>,
            F: ff::PrimeField,
        {
            type Config = <S<'a, F> as crate::testing_utils::FromScratch<F>>::Config;
            type Args = ();
            type ConfigCols =
                [midnight_proofs::plonk::Column<midnight_proofs::plonk::Instance>; 2];
            type CS = midnight_proofs::plonk::ConstraintSystem<F>;
            type Error = midnight_proofs::plonk::Error;

            fn new_chip(config: &Self::Config, _: Self::Args) -> Self {
                <S<'a, F> as crate::testing_utils::FromScratch<F>>::new_from_scratch(config)
            }

            fn configure_circuit(
                meta: &mut Self::CS,
                instance_columns: &Self::ConfigCols,
            ) -> Self::Config {
                <S<'a, F> as crate::testing_utils::FromScratch<F>>::configure_from_scratch(meta, instance_columns)
            }

            fn load_chip(
                &self,
                layouter: &mut __Layouter,
                _config: &Self::Config,
            ) -> Result<(), Self::Error> {
                use crate::testing_utils::FromScratch;
                self.load_from_scratch(layouter)
            }}
        "
    )]
    #[case::with_lifetime_and_inline_bound(
        r"
        #[derive(InitFromScratch)]
        struct S<'a, F: Copy> { f: &'a F }
        ",
        r"
        struct S<'a, F: Copy> { f: &'a F }
        impl<'a, __Layouter, F: Copy> extractor_support::circuit::CircuitInitialization<__Layouter> for S<'a, F> 
        where 
            S<'a,F>: crate::testing_utils::FromScratch<F>,
            __Layouter: midnight_proofs::circuit::Layouter<F>,
            F: ff::PrimeField,
        {
            type Config = <S<'a, F> as crate::testing_utils::FromScratch<F>>::Config;
            type Args = ();
            type ConfigCols =
                [midnight_proofs::plonk::Column<midnight_proofs::plonk::Instance>; 2];
            type CS = midnight_proofs::plonk::ConstraintSystem<F>;
            type Error = midnight_proofs::plonk::Error;

            fn new_chip(config: &Self::Config, _: Self::Args) -> Self {
                <S<'a, F> as crate::testing_utils::FromScratch<F>>::new_from_scratch(config)
            }

            fn configure_circuit(
                meta: &mut Self::CS,
                instance_columns: &Self::ConfigCols,
            ) -> Self::Config {
                <S<'a, F> as crate::testing_utils::FromScratch<F>>::configure_from_scratch(meta, instance_columns)
            }

            fn load_chip(
                &self,
                layouter: &mut __Layouter,
                _config: &Self::Config,
            ) -> Result<(), Self::Error> {
                use crate::testing_utils::FromScratch;
                self.load_from_scratch(layouter)
            }}
        "
    )]
    #[allow(non_snake_case)]
    #[case::with_lifetime_and_field_type_not_named_F(
        r"
        #[derive(InitFromScratch)]
        struct S<'a, A: Field> { f: &'a A }
        ",
        r"
        struct S<'a, A: Field> { f: &'a A }
        impl<'a, __Layouter, A: Field> extractor_support::circuit::CircuitInitialization<__Layouter> for S<'a, A> 
        where 
            S<'a,A>: crate::testing_utils::FromScratch<A>,
            __Layouter: midnight_proofs::circuit::Layouter<A>,
            A: ff::PrimeField,
        {
            type Config = <S<'a, A> as crate::testing_utils::FromScratch<A>>::Config;
            type Args = ();
            type ConfigCols =
                [midnight_proofs::plonk::Column<midnight_proofs::plonk::Instance>; 2];
            type CS = midnight_proofs::plonk::ConstraintSystem<A>;
            type Error = midnight_proofs::plonk::Error;

            fn new_chip(config: &Self::Config, _: Self::Args) -> Self {
                <S<'a, A> as crate::testing_utils::FromScratch<A>>::new_from_scratch(config)
            }

            fn configure_circuit(
                meta: &mut Self::CS,
                instance_columns: &Self::ConfigCols,
            ) -> Self::Config {
                <S<'a, A> as crate::testing_utils::FromScratch<A>>::configure_from_scratch(meta, instance_columns)
            }

            fn load_chip(
                &self,
                layouter: &mut __Layouter,
                _config: &Self::Config,
            ) -> Result<(), Self::Error> {
                use crate::testing_utils::FromScratch;
                self.load_from_scratch(layouter)
            }}
        "
    )]
    #[allow(non_snake_case)]
    #[case::with_lifetime_and_primefield_type_not_named_F(
        r"
        #[derive(InitFromScratch)]
        struct S<'a, A: PrimeField> { f: &'a A }
        ",
        r"
        struct S<'a, A: PrimeField> { f: &'a A }
        impl<'a, __Layouter, A: PrimeField> extractor_support::circuit::CircuitInitialization<__Layouter> for S<'a, A> 
        where 
            S<'a,A>: crate::testing_utils::FromScratch<A>,
            __Layouter: midnight_proofs::circuit::Layouter<A>,
            A: ff::PrimeField,
        {
            type Config = <S<'a, A> as crate::testing_utils::FromScratch<A>>::Config;
            type Args = ();
            type ConfigCols =
                [midnight_proofs::plonk::Column<midnight_proofs::plonk::Instance>; 2];
            type CS = midnight_proofs::plonk::ConstraintSystem<A>;
            type Error = midnight_proofs::plonk::Error;

            fn new_chip(config: &Self::Config, _: Self::Args) -> Self {
                <S<'a, A> as crate::testing_utils::FromScratch<A>>::new_from_scratch(config)
            }

            fn configure_circuit(
                meta: &mut Self::CS,
                instance_columns: &Self::ConfigCols,
            ) -> Self::Config {
                <S<'a, A> as crate::testing_utils::FromScratch<A>>::configure_from_scratch(meta, instance_columns)
            }

            fn load_chip(
                &self,
                layouter: &mut __Layouter,
                _config: &Self::Config,
            ) -> Result<(), Self::Error> {
                use crate::testing_utils::FromScratch;
                self.load_from_scratch(layouter)
            }}
        "
    )]
    #[case::with_lifetime_and_field_type_configured_with_attribute(
        r"
        #[derive(InitFromScratch)]
        #[field(A)]
        struct S<'a,  A> { f: &'a A }
        ",
        r"
        struct S<'a, A> { f: &'a A }
        impl<'a, __Layouter,  A> extractor_support::circuit::CircuitInitialization<__Layouter> for S<'a, A> 
        where 
            S<'a,A>: crate::testing_utils::FromScratch<A>,
            __Layouter: midnight_proofs::circuit::Layouter<A>,
            A: ff::PrimeField,
        {
            type Config = <S<'a, A> as crate::testing_utils::FromScratch<A>>::Config;
            type Args = ();
            type ConfigCols =
                [midnight_proofs::plonk::Column<midnight_proofs::plonk::Instance>; 2];
            type CS = midnight_proofs::plonk::ConstraintSystem<A>;
            type Error = midnight_proofs::plonk::Error;

            fn new_chip(config: &Self::Config, _: Self::Args) -> Self {
                <S<'a, A> as crate::testing_utils::FromScratch<A>>::new_from_scratch(config)
            }

            fn configure_circuit(
                meta: &mut Self::CS,
                instance_columns: &Self::ConfigCols,
            ) -> Self::Config {
                <S<'a, A> as crate::testing_utils::FromScratch<A>>::configure_from_scratch(meta, instance_columns)
            }

            fn load_chip(
                &self,
                layouter: &mut __Layouter,
                _config: &Self::Config,
            ) -> Result<(), Self::Error> {
                use crate::testing_utils::FromScratch;
                self.load_from_scratch(layouter)
            }}
        "
    )]
    #[case::with_lifetime_and_where_bound(
        r"
        #[derive(InitFromScratch)]
        struct S<'a, F> where F: Copy { f: &'a F }
        ",
        r"
        struct S<'a, F> where F: Copy { f: &'a F }
        impl<'a, __Layouter, F> extractor_support::circuit::CircuitInitialization<__Layouter> for S<'a, F> 
        where 
            F: Copy,
            S<'a,F>: crate::testing_utils::FromScratch<F>,
            __Layouter: midnight_proofs::circuit::Layouter<F>,
            F: ff::PrimeField,
        {
            type Config = <S<'a, F> as crate::testing_utils::FromScratch<F>>::Config;
            type Args = ();
            type ConfigCols =
                [midnight_proofs::plonk::Column<midnight_proofs::plonk::Instance>; 2];
            type CS = midnight_proofs::plonk::ConstraintSystem<F>;
            type Error = midnight_proofs::plonk::Error;

            fn new_chip(config: &Self::Config, _: Self::Args) -> Self {
                <S<'a, F> as crate::testing_utils::FromScratch<F>>::new_from_scratch(config)
            }

            fn configure_circuit(
                meta: &mut Self::CS,
                instance_columns: &Self::ConfigCols,
            ) -> Self::Config {
                <S<'a, F> as crate::testing_utils::FromScratch<F>>::configure_from_scratch(meta, instance_columns)
            }

            fn load_chip(
                &self,
                layouter: &mut __Layouter,
                _config: &Self::Config,
            ) -> Result<(), Self::Error> {
                use crate::testing_utils::FromScratch;
                self.load_from_scratch(layouter)
            }}
        "
    )]
    #[case::with_lifetime_const_param_and_inline_bound(
        r"
        #[derive(InitFromScratch)]
        struct S<'a, F, const N: usize> where F: Copy { f: [&'a F; N] }
        ",
        r"
        struct S<'a, F, const N: usize> where F: Copy { f: [&'a F; N] }
        impl<'a, __Layouter, F, const N: usize> extractor_support::circuit::CircuitInitialization<__Layouter> for S<'a, F, N> 
        where 
            F: Copy,
            S<'a,F,N>: crate::testing_utils::FromScratch<F>,
            __Layouter: midnight_proofs::circuit::Layouter<F>,
            F: ff::PrimeField,
        {
            type Config = <S<'a, F, N> as crate::testing_utils::FromScratch<F>>::Config;
            type Args = ();
            type ConfigCols =
                [midnight_proofs::plonk::Column<midnight_proofs::plonk::Instance>; 2];
            type CS = midnight_proofs::plonk::ConstraintSystem<F>;
            type Error = midnight_proofs::plonk::Error;

            fn new_chip(config: &Self::Config, _: Self::Args) -> Self {
                <S<'a, F, N> as crate::testing_utils::FromScratch<F>>::new_from_scratch(config)
            }

            fn configure_circuit(
                meta: &mut Self::CS,
                instance_columns: &Self::ConfigCols,
            ) -> Self::Config {
                <S<'a, F, N> as crate::testing_utils::FromScratch<F>>::configure_from_scratch(meta, instance_columns)
            }

            fn load_chip(
                &self,
                layouter: &mut __Layouter,
                _config: &Self::Config,
            ) -> Result<(), Self::Error> {
                use crate::testing_utils::FromScratch;
                self.load_from_scratch(layouter)
            }}
        "
    )]
    #[case::with_field_type_from_assoc_type(
        r"
        trait CT { type Base; }
        #[derive(InitFromScratch)]
        #[field(C::Base)]
        struct S< C: CT> { f: C::Base }
        ",
        r"
        trait CT { type Base; }
        struct S<C: CT> { f: C::Base }
        impl<__Layouter, C: CT> extractor_support::circuit::CircuitInitialization<__Layouter> for S<C> 
        where 
            S<C>: crate::testing_utils::FromScratch<C::Base>,
            __Layouter: midnight_proofs::circuit::Layouter<C::Base>,
            C::Base: ff::PrimeField,
        {
            type Config = <S<C> as crate::testing_utils::FromScratch<C::Base>>::Config;
            type Args = ();
            type ConfigCols =
                [midnight_proofs::plonk::Column<midnight_proofs::plonk::Instance>; 2];
            type CS = midnight_proofs::plonk::ConstraintSystem<C::Base>;
            type Error = midnight_proofs::plonk::Error;

            fn new_chip(config: &Self::Config, _: Self::Args) -> Self {
                <S<C> as crate::testing_utils::FromScratch<C::Base>>::new_from_scratch(config)
            }

            fn configure_circuit(
                meta: &mut Self::CS,
                instance_columns: &Self::ConfigCols,
            ) -> Self::Config {
                <S<C> as crate::testing_utils::FromScratch<C::Base>>::configure_from_scratch(meta, instance_columns)
            }

            fn load_chip(
                &self,
                layouter: &mut __Layouter,
                _config: &Self::Config,
            ) -> Result<(), Self::Error> {
                use crate::testing_utils::FromScratch;
                self.load_from_scratch(layouter)
            }}
        "
    )]
    #[case::with_field_type_from_assoc_type(
        r"
        trait CT { type Base; }
        #[derive(InitFromScratch)]
        #[field(C::Base)]
        #[from_scratch(N)]
        struct S< C: CT,  N> { f: C::Base, n: N }
        ",
        r"
        trait CT { type Base; }
        struct S<C: CT, N> { f: C::Base, n: N }
        impl<__Layouter, C: CT, N> 
            extractor_support::circuit::CircuitInitialization<__Layouter> for S<C, N> 
        where 
            S<C,N>: crate::testing_utils::FromScratch<C::Base>,
            __Layouter: midnight_proofs::circuit::Layouter<C::Base>,
            C::Base: ff::PrimeField,
            N: crate::testing_utils::FromScratch<C::Base>
        {
            type Config = <S<C,N> as crate::testing_utils::FromScratch<C::Base>>::Config;
            type Args = ();
            type ConfigCols =
                [midnight_proofs::plonk::Column<midnight_proofs::plonk::Instance>; 2];
            type CS = midnight_proofs::plonk::ConstraintSystem<C::Base>;
            type Error = midnight_proofs::plonk::Error;

            fn new_chip(config: &Self::Config, _: Self::Args) -> Self {
                <S<C,N> as crate::testing_utils::FromScratch<C::Base>>::new_from_scratch(config)
            }

            fn configure_circuit(
                meta: &mut Self::CS,
                instance_columns: &Self::ConfigCols,
            ) -> Self::Config {
                <S<C,N> as crate::testing_utils::FromScratch<C::Base>>::configure_from_scratch(meta, instance_columns)
            }

            fn load_chip(
                &self,
                layouter: &mut __Layouter,
                _config: &Self::Config,
            ) -> Result<(), Self::Error> {
                use crate::testing_utils::FromScratch;
                self.load_from_scratch(layouter)
            }}
        "
    )]

    fn derive_macro_test(ctx: Context, #[case] input: &str, #[case] expected: &str) {
        let input = parse!(input);
        let expected = unparse!(parse!(expected));
        let output = ctx.transform(input);
        let formatted = unparse!(output);
        similar_asserts::assert_eq!(expected, formatted);
    }
}
