use quote::{ToTokens, TokenStreamExt};
use syn::parse_quote;

use crate::{
    ir::{EnumeratedTypedField, ModuleStructIR},
    utils::{self, expr::IntoExpr}
};

use super::parts_utils::UseSuperItem;

#[derive(syn_derive::ToTokens)]
pub struct ModuleModItem {
    mod_token: syn::token::Mod,
    mod_ident: syn::Ident,
    #[syn(braced)]
    braces: syn::token::Brace,
    #[syn(in = braces)]
    use_super: UseSuperItem,
    #[syn(in = braces)]
    item: ModuleImplItem
}

impl TryFrom<&'_ ModuleStructIR> for ModuleModItem {
    type Error = syn::Error;

    fn try_from(ir: &'_ ModuleStructIR) -> Result<Self, Self::Error> {
        Ok(Self {
            mod_token: Default::default(),
            mod_ident: ir.module_mod_ident(),
            use_super: UseSuperItem,
            braces: Default::default(),
            item: ir.try_into()?
        })
    }
}

#[derive(syn_derive::ToTokens)]
struct ModuleImplItem {
    impl_token: syn::token::Impl,
    trait_path: syn::Type,
    for_token: syn::token::For,
    module_path: syn::Ident,
    #[syn(braced)]
    braces: syn::token::Brace,
    #[syn(in = braces)]
    new_fn: NewModuleFnItem,
    #[syn(in = braces)]
    env_fn: EnvFnItem
}

impl TryFrom<&'_ ModuleStructIR> for ModuleImplItem {
    type Error = syn::Error;

    fn try_from(ir: &'_ ModuleStructIR) -> Result<Self, Self::Error> {
        Ok(Self {
            impl_token: Default::default(),
            trait_path: utils::ty::module(),
            for_token: Default::default(),
            module_path: ir.module_ident(),
            braces: Default::default(),
            new_fn: ir.try_into()?,
            env_fn: EnvFnItem
        })
    }
}

#[derive(syn_derive::ToTokens)]
struct NewModuleFnItem {
    sig: syn::Signature,
    #[syn(braced)]
    braces: syn::token::Brace,
    #[syn(in = braces)]
    #[to_tokens(|tokens, val| tokens.append_all(val))]
    fields: Vec<ModuleFieldItem>,
    #[syn(in = braces)]
    instance: ModuleInstanceItem
}

impl TryFrom<&'_ ModuleStructIR> for NewModuleFnItem {
    type Error = syn::Error;

    fn try_from(ir: &'_ ModuleStructIR) -> Result<Self, Self::Error> {
        let ty_contract_env = utils::ty::rc_contract_env();
        let env = utils::ident::env();
        let fields = ir.typed_fields()?;
        Ok(Self {
            sig: parse_quote!(fn new(#env: #ty_contract_env) -> Self),
            braces: Default::default(),
            fields: fields.iter().map(Into::into).collect(),
            instance: ir.try_into()?
        })
    }
}

#[derive(syn_derive::ToTokens)]
struct ModuleFieldItem {
    let_token: syn::token::Let,
    ident: syn::Ident,
    assign_token: syn::token::Eq,
    field_expr: syn::Expr,
    semi_token: syn::token::Semi
}

impl From<&'_ EnumeratedTypedField> for ModuleFieldItem {
    fn from(field: &'_ EnumeratedTypedField) -> Self {
        Self {
            let_token: Default::default(),
            ident: field.ident.clone(),
            assign_token: Default::default(),
            field_expr: utils::expr::module_component_instance(
                &field.ty,
                &utils::ident::env(),
                field.idx
            ),
            semi_token: Default::default()
        }
    }
}

#[derive(syn_derive::ToTokens)]
struct ModuleInstanceItem {
    self_token: syn::token::SelfType,
    #[syn(braced)]
    braces: syn::token::Brace,
    #[syn(in = braces)]
    values: syn::punctuated::Punctuated<ValueInitItem, syn::Token![,]>
}

impl TryFrom<&'_ ModuleStructIR> for ModuleInstanceItem {
    type Error = syn::Error;

    fn try_from(ir: &'_ ModuleStructIR) -> Result<Self, Self::Error> {
        let ident_underscored_env = utils::ident::underscored_env();
        let ident_env = utils::ident::env();
        let env_init = ValueInitItem::with_init(ident_underscored_env, ident_env.into_expr());

        Ok(Self {
            self_token: Default::default(),
            braces: Default::default(),
            values: ir
                .field_names()?
                .into_iter()
                .map(ValueInitItem::new)
                .chain(vec![env_init])
                .collect()
        })
    }
}

struct EnvFnItem;

impl ToTokens for EnvFnItem {
    fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
        let ty_contract_env = utils::ty::rc_contract_env();
        let m_env = utils::member::underscored_env();

        tokens.extend(quote::quote!(
            fn env(&self) -> #ty_contract_env {
                #m_env.clone()
            }
        ))
    }
}

#[derive(syn_derive::ToTokens)]
struct ValueInitItem {
    ident: syn::Ident,
    colon_token: Option<syn::Token![:]>,
    init_expr: Option<syn::Expr>
}

impl ValueInitItem {
    fn new(ident: syn::Ident) -> Self {
        Self {
            ident,
            colon_token: None,
            init_expr: None
        }
    }

    fn with_init(ident: syn::Ident, init_expr: syn::Expr) -> Self {
        Self {
            ident,
            colon_token: Some(Default::default()),
            init_expr: Some(init_expr)
        }
    }
}

#[cfg(test)]
mod test {
    use crate::test_utils;
    use quote::quote;

    use super::ModuleModItem;

    #[test]
    fn empty_module() {
        let module = test_utils::mock::empty_module_definition();
        let expected = quote!(
            mod __counter_pack_module {
                use super::*;

                impl Module for CounterPack {
                    fn new(env: odra::prelude::Rc<odra::ContractEnv>) -> Self {
                        Self { __env: env }
                    }

                    fn env(&self) -> odra::prelude::Rc<odra::ContractEnv> {
                        self.__env.clone()
                    }
                }
            }
        );
        let actual = ModuleModItem::try_from(&module).unwrap();
        test_utils::assert_eq(actual, expected);
    }

    #[test]
    fn counter_pack() {
        let module = test_utils::mock::module_definition();
        let expected = quote!(
            mod __counter_pack_module {
                use super::*;

                impl Module for CounterPack {
                    fn new(env: odra::prelude::Rc<odra::ContractEnv>) -> Self {
                        let counter0 =
                            <SubModule<Counter> as odra::module::ModuleComponent>::instance(
                                odra::prelude::Rc::clone(&env),
                                1u8
                            );
                        let counter1 =
                            <SubModule<Counter> as odra::module::ModuleComponent>::instance(
                                odra::prelude::Rc::clone(&env),
                                2u8
                            );
                        let counter2 =
                            <SubModule<Counter> as odra::module::ModuleComponent>::instance(
                                odra::prelude::Rc::clone(&env),
                                3u8
                            );
                        let counters = <Var<u32> as odra::module::ModuleComponent>::instance(
                            odra::prelude::Rc::clone(&env),
                            4u8
                        );
                        let counters_map =
                            <Mapping<u8, Counter> as odra::module::ModuleComponent>::instance(
                                odra::prelude::Rc::clone(&env),
                                5u8
                            );
                        Self {
                            counter0,
                            counter1,
                            counter2,
                            counters,
                            counters_map,
                            __env: env
                        }
                    }

                    fn env(&self) -> odra::prelude::Rc<odra::ContractEnv> {
                        self.__env.clone()
                    }
                }
            }
        );
        let actual = ModuleModItem::try_from(&module).unwrap();
        test_utils::assert_eq(actual, expected);
    }
}
