
use meta_tools_min::*;
use proc_macro_tools::quote::{ ToTokens, TokenStreamExt };
use proc_macro_tools::syn::parse::*;
use proc_macro_tools::syn::spanned::Spanned;
use proc_macro_tools::*;
use std::collections::HashMap;
use iter_tools::{ /* Itertools, */ process_results };
use convert_case::{Case, Casing};

pub type Result< T > = std::result::Result< T, syn::Error >;

///
/// Descriptor of a function with a body, body of which is not parsed.
///

pub struct FnQuick
{
  pub attrs : Vec< syn::Attribute >,
  pub vis : syn::Visibility,
  pub sig : syn::Signature,
  pub block : Option< proc_macro2::TokenStream >,
}

impl quote::ToTokens for FnQuick
{
  fn to_tokens( &self, tokens : &mut proc_macro2::TokenStream )
  {

    for attr in self.attrs.iter()
    {
      attr.to_tokens( tokens );
    }

    self.sig.to_tokens( tokens );

    match &self.block
    {
      Some( block ) => tokens.append( proc_macro2::Group::new( proc_macro2::Delimiter::Brace, block.to_token_stream() ) ),
      None => tokens.append( proc_macro2::Punct::new( ';', proc_macro2::Spacing::Alone ) ),
    }

  }
}

///
/// Descriptor of element of options.
///

pub enum Element
{
  Fn( FnQuick ),
  #[ allow( dead_code ) ]
  Signature( FnQuick ),
  Field( syn::Field ),
}

impl Parse for Element
{
  fn parse( input : ParseStream< '_ > ) -> Result< Self >
  {

    let attrs : Vec< syn::Attribute > = input.call( syn::Attribute::parse_outer )?;
    let vis : syn::Visibility = input.parse()?;

    let lookahead1 = input.lookahead1();
    if lookahead1.peek( syn::Token!{ fn } )
    {
      let sig : syn::Signature = input.parse()?;

      let lookahead2 = input.lookahead1();
      if lookahead2.peek( syn::token::Brace )
      {
        let input2;
        let _brace_token : syn::token::Brace = syn::braced!( input2 in input );
        let block : proc_macro2::TokenStream = input2.parse()?;
        let fn_desc = FnQuick
        {
          attrs,
          vis,
          sig,
          block : Some( block ),
        };
        return Ok( Element::Fn( fn_desc ) );
      }
      else
      {
        let fn_desc = FnQuick
        {
          attrs,
          vis,
          sig,
          block : None,
        };
        return Ok( Element::Fn( fn_desc ) );
      }
    }
    else
    {
      input.call( syn::Field::parse_named ).map( | mut e |
      {
        e.vis = vis;
        e.attrs = attrs;
        Element::Field( e )
      })
    }
  }
}

///
/// Descriptor of attribute options.
///

#[allow( dead_code )]
struct OptionsDescriptor
{
  attrs : Vec< syn::Attribute >,
  vis : syn::Visibility,
  ident : syn::Ident,
  generics: syn::Generics,
  brace_token : syn::token::Brace,
  methods_map : HashMap< String, FnQuick >,
  signatures_map : HashMap< String, FnQuick >,
  fields_map : HashMap< String, syn::Field >,
}

impl Parse for OptionsDescriptor
{
  fn parse( input : ParseStream< '_ > ) -> Result< Self >
  {
    let input2;
    let vis = input.parse()?;
    let ident = input.parse()?;
    let generics = input.parse()?;
    let brace_token = syn::braced!( input2 in input );
    let mut attrs = input2.call( syn::Attribute::parse_inner )?;
    let elements : syn::punctuated::Punctuated< Element, syn::Token!{ ; } > = input2.parse_terminated( Element::parse )?;
    let mut methods_map = hmap!{};
    let mut signatures_map = hmap!{};
    let mut fields_map = hmap!{};

    for attr in attrs.iter_mut()
    {
      attr.style = syn::AttrStyle::Outer;
    }

    for element in elements.into_iter()
    {
      match element
      {
        Element::Fn( f ) =>
        {
          methods_map.insert( f.sig.ident.to_string(), f );
        },
        Element::Signature( f ) =>
        {
          signatures_map.insert( f.sig.ident.to_string(), f );
        },
        Element::Field( f ) =>
        {
          let key = f.ident.as_ref().ok_or_else( || syn_err!( &f.clone(), "Field does not have name: {}", qt!{ #f } ) )?.to_string();
          fields_map.insert( key, f );
        },
      }
    }

    let result = OptionsDescriptor
    {
      vis,
      ident,
      generics,
      brace_token,
      attrs,
      methods_map,
      signatures_map,
      fields_map,
    };
    Ok( result )
  }
}

///
/// Getter descriptor.
///

#[ derive( Debug ) ]
pub struct AccessorDescriptor
{
  attr : proc_macro2::TokenStream,
  signature : proc_macro2::TokenStream,
  body : proc_macro2::TokenStream,
}

//

impl quote::ToTokens for AccessorDescriptor
{
  fn to_tokens( &self, tokens : &mut proc_macro2::TokenStream )
  {
    self.attr.to_tokens( tokens );
    self.signature.to_tokens( tokens );
    self.body.to_tokens( tokens );
  }
}

///
/// Generate a getter for a field.
///

fn getter_gen( name : &str, field : &syn::Field ) -> Result< AccessorDescriptor >
{

  let name_ident = syn::Ident::new( &name, field.span() );
  let ty = &field.ty;

  // tree_print!( ty );

  let ty_is_ref = matches!( ty, syn::Type::Reference( _ ) );

  let ty2 = if ty_is_ref
  {
    ty.to_token_stream()
  }
  else
  {
    qt!{ & #ty }
  };

  let attr = qt!
  {
    #[ inline ]
  };

  let signature = qt!
  {
    fn #name_ident( &self ) -> #ty2
  };

  let body = qt!
  {
    {
      &self.#name_ident
    }
  };

  let result = AccessorDescriptor
  {
    attr,
    signature,
    body,
  };

  Ok( result )
}

///
/// Generate a mutter for a field.
///

fn mutter_gen( name : &str, field : &syn::Field ) -> Result< AccessorDescriptor >
{

  let name_ident = syn::Ident::new( &name, field.span() );
  let name_mut_ident = syn::Ident::new( &format!( "{}_mut", name ), field.span() );
  let ty = &field.ty;

  // tree_print!( ty );

  let ty2 = qt!{ &mut #ty };

  let attr = qt!
  {
    #[ inline ]
  };

  let signature = qt!
  {
    fn #name_mut_ident( &mut self ) -> #ty2
  };

  let body = qt!
  {
    {
      &mut self.#name_ident
    }
  };

  let result = AccessorDescriptor
  {
    attr,
    signature,
    body,
  };

  Ok( result )
}

///
///
///

fn perform_gen( options_descriptor : &OptionsDescriptor ) -> ( proc_macro2::TokenStream, proc_macro2::TokenStream )
{

  let mut perform = qt!{};
  let mut attr_perform = qt!{};
  if let Some( perform_fn ) = options_descriptor.methods_map.get( "perform" )
  {
    let sig = &perform_fn.sig;
    attr_perform = qt!{ #[ perform( #sig ) ] };
    perform = qt!
    {
      #[ allow( unused_attributes ) ]
      #[ inline ]
      #perform_fn
    }
  }

  ( perform, attr_perform )
}

///
/// Options macro handler.
///

pub fn options( _attr : proc_macro::TokenStream, item : proc_macro::TokenStream ) -> Result< proc_macro2::TokenStream >
{

  let options_descriptor = match syn::parse::< OptionsDescriptor >( item )
  {
    Ok( syntax_tree ) => syntax_tree,
    Err( err ) => return Err( err ),
  };

  let name_ident = &options_descriptor.ident;
  let name_options_adapter_str = String::from( name_ident.to_string() ).from_case( Case::Snake ).to_case( Case::UpperCamel );
  let name_options_adapter_str = format!( "{}OptionsAdapter", name_options_adapter_str );
  let name_options_adapter_ident = syn::Ident::new( &name_options_adapter_str, name_ident.span() );
  let generics = &options_descriptor.generics;
  let attrs = &options_descriptor.attrs;

  let mut fields_define = Vec::< &syn::Field >::new();
  for ( _name, field ) in options_descriptor.fields_map.iter()
  {
    fields_define.push( field );
  }

  let ( perform, attr_perform ) = perform_gen( &options_descriptor );

  let getters = options_descriptor.fields_map.iter().map( | ( key, field ) | getter_gen( key, field ) );
  let getters : Vec< _ > = process_results( getters, | iter | iter.collect() )?;
  let getters_signatures : Vec< _ > = getters.iter().map( | e | e.signature.clone() ).collect();

  let mutters = options_descriptor.fields_map.iter().map( | ( key, field ) | mutter_gen( key, field ) );
  let mutters : Vec< _ > = process_results( mutters, | iter | iter.collect() )?;
  let mutters_signatures : Vec< _ > = mutters.iter().map( | e | e.signature.clone() ).collect();

  let result = qt!
  {

    pub mod #name_ident
    {

      // #[cfg( feature = "in_wtools" )]
      // use ::wtools::options::*;
      // #[cfg( not( feature = "in_wtools" ) )]
      use super::Former;

      #( #attrs )*
      #[ derive( Former, PartialEq, Debug ) ]
      #attr_perform
      pub struct Options #generics
      {
        #( #fields_define, )*
      }

      pub trait OptionsAdapter #generics
      {
        #( #getters_signatures ; )*
        #( #mutters_signatures ; )*
        #perform
      }

      impl #generics OptionsAdapter #generics for Options #generics
      {
        #( #getters )*
        #( #mutters )*
      }

      #[ inline ]
      pub fn former #generics() -> OptionsFormer #generics
      {
        Options::#generics::former()
      }

      /// Namespace of the module to include with `use module::*`.
      pub mod prelude
      {
        pub use super::OptionsAdapter as #name_options_adapter_ident;
      }

    }

    #[ inline ]
    pub fn #name_ident #generics () -> #name_ident::OptionsFormer #generics
    {
      #name_ident::former::#generics()
    }

  };

  Ok( result )
}
