use std::io::Write;
use std::ops::Deref;

use syn::ext::IdentExt;

use crate::bindgen::cdecl;
use crate::bindgen::config::{Config, Language};
use crate::bindgen::declarationtyperesolver::{DeclarationType, DeclarationTypeResolver};
use crate::bindgen::ir::{ConstExpr, Path, Type};
use crate::bindgen::utilities::IterHelpers;
use crate::bindgen::writer::{Source, SourceWriter};

#[derive(Debug, Clone)]
pub enum GenericParamType {
    Type,
    Const(Type),
}

#[derive(Debug, Clone)]
pub struct GenericParam {
    name: Path,
    ty: GenericParamType,
}

impl GenericParam {
    pub fn new_type_param(name: &str) -> Self {
        GenericParam {
            name: Path::new(name),
            ty: GenericParamType::Type,
        }
    }

    pub fn load(param: &syn::GenericParam) -> Result<Option<Self>, String> {
        match *param {
            syn::GenericParam::Type(syn::TypeParam { ref ident, .. }) => Ok(Some(GenericParam {
                name: Path::new(ident.unraw().to_string()),
                ty: GenericParamType::Type,
            })),

            syn::GenericParam::Lifetime(_) => Ok(None),

            syn::GenericParam::Const(syn::ConstParam {
                ref ident, ref ty, ..
            }) => match Type::load(ty)? {
                None => {
                    // A type that evaporates, like PhantomData.
                    Err(format!("unsupported const generic type: {:?}", ty))
                }
                Some(ty) => Ok(Some(GenericParam {
                    name: Path::new(ident.unraw().to_string()),
                    ty: GenericParamType::Const(ty),
                })),
            },
        }
    }

    pub fn name(&self) -> &Path {
        &self.name
    }
}

#[derive(Default, Debug, Clone)]
pub struct GenericParams(pub Vec<GenericParam>);

impl GenericParams {
    pub fn load(generics: &syn::Generics) -> Result<Self, String> {
        let mut params = vec![];
        for param in &generics.params {
            if let Some(p) = GenericParam::load(param)? {
                params.push(p);
            }
        }

        Ok(GenericParams(params))
    }

    /// Associate each parameter with an argument.
    pub fn call<'out>(
        &'out self,
        item_name: &str,
        arguments: &'out [GenericArgument],
    ) -> Vec<(&'out Path, &'out GenericArgument)> {
        assert!(self.len() > 0, "{} is not generic", item_name);
        assert!(
            self.len() == arguments.len(),
            "{} has {} params but is being instantiated with {} values",
            item_name,
            self.len(),
            arguments.len(),
        );
        self.iter()
            .map(|param| param.name())
            .zip(arguments.iter())
            .collect()
    }

    fn write_internal<F: Write>(
        &self,
        config: &Config,
        out: &mut SourceWriter<F>,
        with_default: bool,
    ) {
        if !self.0.is_empty() && config.language == Language::Cxx {
            out.write("template<");
            for (i, item) in self.0.iter().enumerate() {
                if i != 0 {
                    out.write(", ");
                }
                match item.ty {
                    GenericParamType::Type => {
                        write!(out, "typename {}", item.name);
                        if with_default {
                            write!(out, " = void");
                        }
                    }
                    GenericParamType::Const(ref ty) => {
                        cdecl::write_field(out, ty, item.name.name(), config);
                        if with_default {
                            write!(out, " = 0");
                        }
                    }
                }
            }
            out.write(">");
            out.new_line();
        }
    }

    pub fn write_with_default<F: Write>(&self, config: &Config, out: &mut SourceWriter<F>) {
        self.write_internal(config, out, true);
    }
}

impl Deref for GenericParams {
    type Target = [GenericParam];

    fn deref(&self) -> &[GenericParam] {
        &self.0
    }
}

impl Source for GenericParams {
    fn write<F: Write>(&self, config: &Config, out: &mut SourceWriter<F>) {
        self.write_internal(config, out, false);
    }
}

/// A (non-lifetime) argument passed to a generic, either a type or a constant expression.
///
/// Note: Both arguments in a type like `Array<T, N>` are represented as
/// `GenericArgument::Type`s, even if `N` is actually the name of a const. This
/// is a consequence of `syn::GenericArgument` doing the same thing.
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub enum GenericArgument {
    Type(Type),
    Const(ConstExpr),
}

impl GenericArgument {
    pub fn specialize(&self, mappings: &[(&Path, &GenericArgument)]) -> GenericArgument {
        match *self {
            GenericArgument::Type(ref ty) => {
                if let Type::Path(ref path) = *ty {
                    if path.is_single_identifier() {
                        // See note on `GenericArgument` above: `ty` may
                        // actually be the name of a const. Check for that now.
                        for &(name, value) in mappings {
                            if *name == path.path {
                                return value.clone();
                            }
                        }
                    }
                }
                GenericArgument::Type(ty.specialize(mappings))
            }
            GenericArgument::Const(ref expr) => GenericArgument::Const(expr.clone()),
        }
    }

    pub fn rename_for_config(&mut self, config: &Config, generic_params: &GenericParams) {
        match *self {
            GenericArgument::Type(ref mut ty) => ty.rename_for_config(config, generic_params),
            GenericArgument::Const(ref mut expr) => expr.rename_for_config(config),
        }
    }
}

impl Source for GenericArgument {
    fn write<F: Write>(&self, config: &Config, out: &mut SourceWriter<F>) {
        match *self {
            GenericArgument::Type(ref ty) => ty.write(config, out),
            GenericArgument::Const(ref expr) => expr.write(config, out),
        }
    }
}

#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct GenericPath {
    path: Path,
    export_name: String,
    generics: Vec<GenericArgument>,
    ctype: Option<DeclarationType>,
}

impl GenericPath {
    pub fn new(path: Path, generics: Vec<GenericArgument>) -> Self {
        let export_name = path.name().to_owned();
        Self {
            path,
            export_name,
            generics,
            ctype: None,
        }
    }

    pub fn self_path() -> Self {
        Self::new(Path::new("Self"), vec![])
    }

    pub fn replace_self_with(&mut self, self_ty: &Path) {
        if self.path.replace_self_with(self_ty) {
            self.export_name = self_ty.name().to_owned();
        }
        // Caller deals with generics.
    }

    pub fn path(&self) -> &Path {
        &self.path
    }

    pub fn generics(&self) -> &[GenericArgument] {
        &self.generics
    }

    pub fn generics_mut(&mut self) -> &mut [GenericArgument] {
        &mut self.generics
    }

    pub fn ctype(&self) -> Option<&DeclarationType> {
        self.ctype.as_ref()
    }

    pub fn name(&self) -> &str {
        self.path.name()
    }

    pub fn export_name(&self) -> &str {
        &self.export_name
    }

    pub fn is_single_identifier(&self) -> bool {
        self.generics.is_empty()
    }

    pub fn rename_for_config(&mut self, config: &Config, generic_params: &GenericParams) {
        for generic in &mut self.generics {
            generic.rename_for_config(config, generic_params);
        }
        if !generic_params.iter().any(|param| param.name == self.path) {
            config.export.rename(&mut self.export_name);
        }
    }

    pub fn resolve_declaration_types(&mut self, resolver: &DeclarationTypeResolver) {
        self.ctype = resolver.type_for(&self.path);
    }

    pub fn load(path: &syn::Path) -> Result<Self, String> {
        assert!(
            !path.segments.is_empty(),
            "{:?} doesn't have any segments",
            path
        );
        let last_segment = path.segments.last().unwrap();
        let name = last_segment.ident.unraw().to_string();

        let path = Path::new(name);
        let phantom_data_path = Path::new("PhantomData");
        if path == phantom_data_path {
            return Ok(Self::new(path, Vec::new()));
        }

        let generics = match last_segment.arguments {
            syn::PathArguments::AngleBracketed(syn::AngleBracketedGenericArguments {
                ref args,
                ..
            }) => args.iter().try_skip_map(|x| match *x {
                syn::GenericArgument::Type(ref x) => Ok(Type::load(x)?.map(GenericArgument::Type)),
                syn::GenericArgument::Lifetime(_) => Ok(None),
                syn::GenericArgument::Const(ref x) => {
                    Ok(Some(GenericArgument::Const(ConstExpr::load(x)?)))
                }
                _ => Err(format!("can't handle generic argument {:?}", x)),
            })?,
            syn::PathArguments::Parenthesized(_) => {
                return Err("Path contains parentheses.".to_owned());
            }
            _ => Vec::new(),
        };

        Ok(Self::new(path, generics))
    }
}
