pub use proc_macro2_diagnostics::SpanDiagnosticExt;

use syn::{*, spanned::Spanned, punctuated::Punctuated, token::Comma};
use proc_macro2::{Span, TokenStream};
use crate::Result;

type TypeParamBounds = Punctuated<TypeParamBound, Token![+]>;

type WherePredicates = Punctuated<WherePredicate, Token![,]>;

pub trait PathExt {
    fn is(&self, global: bool, segments: &[&str]) -> bool;
    fn is_local(&self, segments: &[&str]) -> bool;
    fn is_global(&self, segments: &[&str]) -> bool;
    fn last_ident(&self) -> Option<&Ident>;
    fn generics(&self) -> Option<&Punctuated<GenericArgument, Comma>>;
}

pub trait TypeExt {
    fn strip_lifetimes(&mut self);
    fn with_stripped_lifetimes(&self) -> Type;
    fn replace_lifetimes(&mut self, with: Lifetime);
    fn with_replaced_lifetimes(&self, with: Lifetime) -> Type;
}

pub trait GenericsExt {
    fn add_type_bound(&mut self, bounds: TypeParamBound);
    fn add_type_bounds(&mut self, bounds: TypeParamBounds);
    fn replace(&mut self, ident: &Ident, with: &Ident);
    fn replace_lifetime(&mut self, n: usize, with: &Lifetime) -> bool;
    fn insert_lifetime(&mut self, lifetime: LifetimeParam);

    fn bounded_types(&self, bounds: TypeParamBounds) -> WherePredicates;
    fn parsed_bounded_types(&self, bounds: TokenStream) -> Result<WherePredicates>;
    fn add_where_predicates(&mut self, predicates: WherePredicates);
}

pub trait AstItemExt {
    fn respanned(&self, span: proc_macro2::Span) -> Self where Self: parse::Parse;
    fn respanned_tokens(&self, span: proc_macro2::Span) -> TokenStream;
}

#[macro_export]
macro_rules! quote_respanned {
    ($span:expr => $($t:tt)*) => ({
        use $crate::ext::AstItemExt;
        let tokens = quote_spanned!($span => $($t)*);
        tokens.respanned_tokens($span)
    })
}

pub use quote_respanned;

impl<T: quote::ToTokens> AstItemExt for T {
    fn respanned(&self, span: Span) -> T
        where Self: parse::Parse
    {
        syn::parse2(self.respanned_tokens(span)).unwrap()
    }

    fn respanned_tokens(&self, span: Span) -> TokenStream {
        self.to_token_stream()
            .into_iter()
            .map(|mut token| { token.set_span(span); token })
            .collect()
    }
}

impl GenericsExt for Generics {
    fn add_type_bound(&mut self, bound: TypeParamBound) {
        self.add_type_bounds(Some(bound).into_iter().collect());
    }

    fn add_type_bounds(&mut self, bounds: TypeParamBounds) {
        self.add_where_predicates(self.bounded_types(bounds))
    }

    fn replace(&mut self, ident: &Ident, with: &Ident) {
        IdentReplacer::new(ident, with).visit_generics_mut(self);
    }

    fn replace_lifetime(&mut self, n: usize, with: &Lifetime) -> bool {
        let lifetime_ident = self.lifetimes().nth(n)
            .map(|l| l.lifetime.ident.clone());

        if let Some(ref ident) = lifetime_ident {
            self.replace(ident, &with.ident);
        }

        lifetime_ident.is_some()
    }

    fn insert_lifetime(&mut self, lifetime: LifetimeParam) {
        self.params.insert(0, lifetime.into());
    }

    fn parsed_bounded_types(&self, bounds: TokenStream) -> Result<WherePredicates> {
        use syn::parse::Parser;
        use quote::ToTokens;

        let tokens = bounds.into_token_stream();
        TypeParamBounds::parse_separated_nonempty.parse2(tokens)
            .map(|bounds| self.bounded_types(bounds))
            .map_err(|e| e.span().error(format!("invalid type param bounds: {}", e)))
    }

    fn bounded_types(&self, bounds: TypeParamBounds) -> WherePredicates {
        self.type_params()
            .map(|ty| {
                let ident = &ty.ident;
                let bounds = bounds.respanned_tokens(ty.span());
                syn::parse2(quote_spanned!(ty.span() => #ident: #bounds))
            })
            .collect::<syn::Result<Vec<WherePredicate>>>()
            .expect("valid where predicates")
            .into_iter()
            .collect()
    }

    fn add_where_predicates(&mut self, predicates: WherePredicates) {
        for p in predicates {
            self.make_where_clause().predicates.push(p);
        }
    }
}

pub trait GenericExt {
    fn kind(&self) -> GenericKind;
}

pub trait GenericParamExt {
    fn ident(&self) -> &Ident;
}

pub trait Split2<A, B>: Sized + Iterator {
    fn split2(self) -> (Vec<A>, Vec<B>);
}

pub trait Split3<A, B, C>: Sized + Iterator {
    fn split3(self) -> (Vec<A>, Vec<B>, Vec<C>);
}

pub trait Split4<A, B, C, D>: Sized + Iterator {
    fn split4(self) -> (Vec<A>, Vec<B>, Vec<C>, Vec<D>);
}

pub trait Split6<A, B, C, D, E, F>: Sized + Iterator {
    fn split6(self) -> (Vec<A>, Vec<B>, Vec<C>, Vec<D>, Vec<E>, Vec<F>);
}

#[derive(Copy, Clone)]
#[non_exhaustive]
pub enum GenericKind {
    Lifetime,
    Type,
    Const,
    AssocType,
    AssocConst,
    Constraint,
    Unknown
}

impl PartialEq for GenericKind {
    fn eq(&self, other: &Self) -> bool {
        match (self, other) {
            (GenericKind::Lifetime, GenericKind::Lifetime) => true,
            (GenericKind::Type, GenericKind::Type) => true,
            (GenericKind::Const, GenericKind::Const) => true,
            (GenericKind::AssocType, GenericKind::AssocType) => true,
            (GenericKind::AssocConst, GenericKind::AssocConst) => true,
            (GenericKind::Constraint, GenericKind::Constraint) => true,
            (GenericKind::Lifetime, _) => false,
            (GenericKind::Type, _) => false,
            (GenericKind::Const, _) => false,
            (GenericKind::AssocType, _) => false,
            (GenericKind::AssocConst, _) => false,
            (GenericKind::Constraint, _) => false,
            (GenericKind::Unknown, _) => false,
        }
    }
}

impl PathExt for Path {
    fn is(&self, global: bool, segments: &[&str]) -> bool {
        if self.leading_colon.is_some() != global || self.segments.len() != segments.len() {
            return false;
        }

        for (segment, wanted) in self.segments.iter().zip(segments.iter()) {
            if segment.ident != wanted {
                return false;
            }
        }

        true
    }

    fn is_local(&self, segments: &[&str]) -> bool {
        self.is(false, segments)
    }

    fn is_global(&self, segments: &[&str]) -> bool {
        self.is(true, segments)
    }

    fn last_ident(&self) -> Option<&Ident> {
        self.segments.last().map(|p| &p.ident)
    }

    fn generics(&self) -> Option<&Punctuated<GenericArgument, Comma>> {
        self.segments.last().and_then(|last| {
            match last.arguments {
                PathArguments::AngleBracketed(ref args) => Some(&args.args),
                _ => None
            }
        })
    }
}

impl<A, B, I: IntoIterator<Item = (A, B)> + Iterator> Split2<A, B> for I {
    fn split2(self) -> (Vec<A>, Vec<B>) {
        let (mut first, mut second) = (vec![], vec![]);
        self.into_iter().for_each(|(a, b)| {
            first.push(a);
            second.push(b);
        });

        (first, second)
    }
}

impl<A, B, C, I: IntoIterator<Item = (A, B, C)> + Iterator> Split3<A, B, C> for I {
    fn split3(self) -> (Vec<A>, Vec<B>, Vec<C>) {
        let (mut first, mut second, mut third) = (vec![], vec![], vec![]);
        self.into_iter().for_each(|(a, b, c)| {
            first.push(a);
            second.push(b);
            third.push(c);
        });

        (first, second, third)
    }
}

impl<A, B, C, D, I: IntoIterator<Item = (A, B, C, D)> + Iterator> Split4<A, B, C, D> for I {
    fn split4(self) -> (Vec<A>, Vec<B>, Vec<C>, Vec<D>) {
        let (mut first, mut second, mut third, mut fourth) = (vec![], vec![], vec![], vec![]);
        self.into_iter().for_each(|(a, b, c, d)| {
            first.push(a);
            second.push(b);
            third.push(c);
            fourth.push(d);
        });

        (first, second, third, fourth)
    }
}

impl<A, B, C, D, E, F, I: IntoIterator<Item = (A, B, C, D, E, F)> + Iterator> Split6<A, B, C, D, E, F> for I {
    fn split6(self) -> (Vec<A>, Vec<B>, Vec<C>, Vec<D>, Vec<E>, Vec<F>) {
        let (mut v1, mut v2, mut v3, mut v4, mut v5, mut v6)
            = (vec![], vec![], vec![], vec![], vec![], vec![]);

        self.into_iter().for_each(|(a, b, c, d, e, f)| {
            v1.push(a); v2.push(b); v3.push(c); v4.push(d); v5.push(e); v6.push(f);
        });

        (v1, v2, v3, v4, v5, v6)
    }
}

impl TypeExt for Type {
    fn replace_lifetimes(&mut self, with: Lifetime) {
        let mut r = LifetimeReplacer { with };
        r.visit_type_mut(self);
    }

    fn strip_lifetimes(&mut self) {
        self.replace_lifetimes(syn::parse_quote!('_));
    }

    fn with_stripped_lifetimes(&self) -> Type {
        let mut new = self.clone();
        new.strip_lifetimes();
        new
    }

    fn with_replaced_lifetimes(&self, with: Lifetime) -> Type {
        let mut new = self.clone();
        new.replace_lifetimes(with);
        new
    }
}

pub struct LifetimeReplacer {
    pub with: Lifetime,
}

impl VisitMut for LifetimeReplacer {
    fn visit_lifetime_mut(&mut self, i: &mut Lifetime) {
        let mut ident = self.with.ident.clone();
        ident.set_span(i.ident.span());
        i.ident = ident;
    }
}

impl GenericExt for GenericArgument {
    fn kind(&self) -> GenericKind {
        match *self {
            GenericArgument::Lifetime(..) => GenericKind::Lifetime,
            GenericArgument::Type(..) => GenericKind::Type,
            GenericArgument::Constraint(..) => GenericKind::Constraint,
            GenericArgument::Const(..) => GenericKind::Const,
            GenericArgument::AssocType(_) => GenericKind::AssocType,
            GenericArgument::AssocConst(_) => GenericKind::AssocConst,
            _ => GenericKind::Unknown,
        }
    }
}

impl GenericExt for GenericParam {
    fn kind(&self) -> GenericKind {
        match *self {
            GenericParam::Lifetime(..) => GenericKind::Lifetime,
            GenericParam::Type(..) => GenericKind::Type,
            GenericParam::Const(..) => GenericKind::Const,
        }
    }
}

impl GenericParamExt for GenericParam {
    fn ident(&self) -> &Ident {
        match self {
            &GenericParam::Type(ref ty) => &ty.ident,
            &GenericParam::Lifetime(ref l) => &l.lifetime.ident,
            &GenericParam::Const(ref c) => &c.ident,
        }
    }
}

use syn::visit_mut::VisitMut;

pub struct IdentReplacer<'a> {
    pub to_replace: &'a Ident,
    pub with: &'a Ident,
    pub replaced: bool
}

impl<'a> IdentReplacer<'a> {
    pub fn new(to_replace: &'a Ident, with: &'a Ident) -> Self {
        IdentReplacer { to_replace, with, replaced: false }
    }
}

impl<'a> VisitMut for IdentReplacer<'a> {
    fn visit_ident_mut(&mut self, i: &mut Ident) {
        if i == self.to_replace {
            *i = self.with.clone();
            self.replaced = true;
        }

        visit_mut::visit_ident_mut(self, i);
    }
}
