use crate::{SolIdent, Spanned, Type, VariableDeclaration};
use proc_macro2::Span;
use std::{
    fmt,
    ops::{Deref, DerefMut},
};
use syn::{
    parse::{Parse, ParseStream},
    punctuated::Punctuated,
    Result, Token,
};
pub type ParameterList = Parameters<syn::token::Comma>;
pub type FieldList = Parameters<syn::token::Semi>;
#[derive(Clone, Default, PartialEq, Eq)]
pub struct Parameters<P>(Punctuated<VariableDeclaration, P>);
impl Default for &ParameterList {
    #[inline]
    fn default() -> Self {
        const NEW: &ParameterList = &ParameterList::new();
        NEW
    }
}
impl Default for &FieldList {
    #[inline]
    fn default() -> Self {
        const NEW: &FieldList = &FieldList::new();
        NEW
    }
}
impl<P> Deref for Parameters<P> {
    type Target = Punctuated<VariableDeclaration, P>;
    fn deref(&self) -> &Self::Target {
        &self.0
    }
}
impl<P> DerefMut for Parameters<P> {
    fn deref_mut(&mut self) -> &mut Self::Target {
        &mut self.0
    }
}
impl fmt::Display for ParameterList {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        for (i, param) in self.iter().enumerate() {
            if i > 0 {
                f.write_str(", ")?;
            }
            param.fmt(f)?;
        }
        Ok(())
    }
}
impl fmt::Display for FieldList {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        for (i, field) in self.iter().enumerate() {
            if i > 0 {
                f.write_str("; ")?;
            }
            field.fmt(f)?;
        }
        Ok(())
    }
}
impl<P> fmt::Debug for Parameters<P> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.debug_list().entries(self.iter()).finish()
    }
}
impl Parse for ParameterList {
    fn parse(input: ParseStream<'_>) -> Result<Self> {
        input
            .parse_terminated(VariableDeclaration::parse, Token![,])
            .map(Self)
    }
}
impl Parse for FieldList {
    fn parse(input: ParseStream<'_>) -> Result<Self> {
        let this = input.parse_terminated(VariableDeclaration::parse_with_name, Token![;])?;
        if this.is_empty() {
            Err(input.error("defining empty structs is disallowed"))
        } else if !this.trailing_punct() {
            Err(input.error("expected trailing semicolon"))
        } else {
            Ok(Self(this))
        }
    }
}
impl<P: Spanned> Spanned for Parameters<P> {
    fn span(&self) -> Span {
        self.0.span()
    }
    fn set_span(&mut self, span: Span) {
        self.0.set_span(span);
    }
}
impl<P> IntoIterator for Parameters<P> {
    type IntoIter = <Punctuated<VariableDeclaration, P> as IntoIterator>::IntoIter;
    type Item = <Self::IntoIter as Iterator>::Item;
    fn into_iter(self) -> Self::IntoIter {
        self.0.into_iter()
    }
}
impl<'a, P> IntoIterator for &'a Parameters<P> {
    type IntoIter = syn::punctuated::Iter<'a, VariableDeclaration>;
    type Item = <Self::IntoIter as Iterator>::Item;
    fn into_iter(self) -> Self::IntoIter {
        self.0.iter()
    }
}
impl<'a, P> IntoIterator for &'a mut Parameters<P> {
    type IntoIter = syn::punctuated::IterMut<'a, VariableDeclaration>;
    type Item = <Self::IntoIter as Iterator>::Item;
    fn into_iter(self) -> Self::IntoIter {
        self.0.iter_mut()
    }
}
impl<P: Default> FromIterator<VariableDeclaration> for Parameters<P> {
    fn from_iter<T: IntoIterator<Item = VariableDeclaration>>(iter: T) -> Self {
        Self(Punctuated::from_iter(iter))
    }
}
impl<P> Parameters<P> {
    pub const fn new() -> Self {
        Self(Punctuated::new())
    }
    pub fn eip712_signature(&self, mut name: String) -> String {
        name.reserve(2 + self.len() * 32);
        name.push('(');
        for (i, field) in self.iter().enumerate() {
            if i > 0 {
                name.push(',');
            }
            field.fmt_eip712(&mut name).unwrap();
        }
        name.push(')');
        name
    }
    pub fn names(
        &self,
    ) -> impl ExactSizeIterator<Item = Option<&SolIdent>> + DoubleEndedIterator + Clone {
        self.iter().map(|var| var.name.as_ref())
    }
    pub fn types(&self) -> impl ExactSizeIterator<Item = &Type> + DoubleEndedIterator + Clone {
        self.iter().map(|var| &var.ty)
    }
    pub fn types_mut(&mut self) -> impl ExactSizeIterator<Item = &mut Type> + DoubleEndedIterator {
        self.iter_mut().map(|var| &mut var.ty)
    }
    pub fn types_and_names(
        &self,
    ) -> impl ExactSizeIterator<Item = (&Type, Option<&SolIdent>)> + DoubleEndedIterator {
        self.iter().map(|p| (&p.ty, p.name.as_ref()))
    }
    pub fn type_strings(
        &self,
    ) -> impl ExactSizeIterator<Item = String> + DoubleEndedIterator + Clone + '_ {
        self.iter().map(|var| var.ty.to_string())
    }
    #[cfg(feature = "visit")]
    pub fn visit_types(&self, mut f: impl FnMut(&Type)) {
        for ty in self.types() {
            ty.visit(&mut f);
        }
    }
    #[cfg(feature = "visit-mut")]
    pub fn visit_types_mut(&mut self, mut f: impl FnMut(&mut Type)) {
        for ty in self.types_mut() {
            ty.visit_mut(&mut f);
        }
    }
}