use std::borrow::Cow;
use std::collections::HashMap;
use std::fmt;
use std::fmt::Write;
use clang::{Availability, Entity, EntityKind, ExceptionSpecification};
use crate::element::UNNAMED;
use crate::entity::WalkAction;
use crate::type_ref::{Constness, CppNameStyle, TypeRefTypeHint};
use crate::{
	settings, Class, DefaultElement, Element, EntityElement, EntityExt, Field, FieldTypeHint, GeneratedType, GeneratorEnv,
	StringExt, TypeRef,
};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum OperatorKind {
	Unsupported,
	Index,
	Add,
	Sub,
	Mul,
	Div,
	Deref,
	Apply,
	Equals,
	NotEquals,
	GreaterThan,
	GreaterThanOrEqual,
	LessThan,
	LessThanOrEqual,
	Incr,
	Decr,
	And,
	Or,
	Xor,
	BitwiseNot,
}
impl OperatorKind {
	pub fn new(token: &str, arg_count: usize) -> Self {
		match token.trim() {
			"[]" => OperatorKind::Index,
			"+" => OperatorKind::Add,
			"-" => OperatorKind::Sub,
			"*" => {
				if arg_count == 0 {
					OperatorKind::Deref
				} else {
					OperatorKind::Mul
				}
			}
			"()" => OperatorKind::Apply,
			"/" => OperatorKind::Div,
			"==" => OperatorKind::Equals,
			"!=" => OperatorKind::NotEquals,
			">" => OperatorKind::GreaterThan,
			">=" => OperatorKind::GreaterThanOrEqual,
			"<" => OperatorKind::LessThan,
			"<=" => OperatorKind::LessThanOrEqual,
			"++" => OperatorKind::Incr,
			"--" => OperatorKind::Decr,
			"&" => OperatorKind::And,
			"|" => OperatorKind::Or,
			"^" => OperatorKind::Xor,
			"~" => OperatorKind::BitwiseNot,
			_ => OperatorKind::Unsupported,
		}
	}
	pub fn add_args_to_name(&self) -> bool {
		match self {
			OperatorKind::Index | OperatorKind::BitwiseNot | OperatorKind::Apply => false,
			OperatorKind::Unsupported
			| OperatorKind::Add
			| OperatorKind::Sub
			| OperatorKind::Mul
			| OperatorKind::Div
			| OperatorKind::Deref
			| OperatorKind::Equals
			| OperatorKind::NotEquals
			| OperatorKind::GreaterThan
			| OperatorKind::GreaterThanOrEqual
			| OperatorKind::LessThan
			| OperatorKind::LessThanOrEqual
			| OperatorKind::Incr
			| OperatorKind::Decr
			| OperatorKind::And
			| OperatorKind::Or
			| OperatorKind::Xor => true,
		}
	}
}
#[derive(Debug)]
pub enum Kind<'tu, 'ge> {
	Function,
	FunctionOperator(OperatorKind),
	Constructor(Class<'tu, 'ge>),
	InstanceMethod(Class<'tu, 'ge>),
	StaticMethod(Class<'tu, 'ge>),
	FieldAccessor(Class<'tu, 'ge>, Field<'tu, 'ge>),
	ConversionMethod(Class<'tu, 'ge>),
	InstanceOperator(Class<'tu, 'ge>, OperatorKind),
	GenericFunction,
	GenericInstanceMethod(Class<'tu, 'ge>),
}
impl<'tu, 'ge> Kind<'tu, 'ge> {
	pub fn as_instance_method(&self) -> Option<&Class<'tu, 'ge>> {
		match self {
			Kind::InstanceMethod(out)
			| Kind::FieldAccessor(out, _)
			| Kind::GenericInstanceMethod(out)
			| Kind::ConversionMethod(out)
			| Kind::InstanceOperator(out, ..) => Some(out),
			_ => None,
		}
	}
	pub fn as_constructor(&self) -> Option<&Class<'tu, 'ge>> {
		if let Kind::Constructor(out) = self {
			Some(out)
		} else {
			None
		}
	}
	pub fn as_static_method(&self) -> Option<&Class<'tu, 'ge>> {
		if let Kind::StaticMethod(out) = self {
			Some(out)
		} else {
			None
		}
	}
	pub fn as_conversion_method(&self) -> Option<&Class<'tu, 'ge>> {
		if let Kind::ConversionMethod(out) = self {
			Some(out)
		} else {
			None
		}
	}
	pub fn as_operator(&self) -> Option<(Option<&Class<'tu, 'ge>>, OperatorKind)> {
		match self {
			Kind::FunctionOperator(kind) => Some((None, *kind)),
			Kind::InstanceOperator(cls, kind) => Some((Some(cls), *kind)),
			_ => None,
		}
	}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum FunctionTypeHint {
	None,
	FieldSetter,
	Specialized(&'static HashMap<&'static str, &'static str>),
}
impl FunctionTypeHint {
	pub fn as_specialized(&self) -> Option<&'static HashMap<&'static str, &'static str>> {
		if let FunctionTypeHint::Specialized(spec) = self {
			Some(spec)
		} else {
			None
		}
	}
}
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
pub struct FuncId<'f> {
	name: Cow<'f, str>,
	args: Vec<Cow<'f, str>>,
}
impl<'f> FuncId<'f> {
	pub fn new<const ARGS: usize>(name: &'static str, args: [&'static str; ARGS]) -> FuncId<'static> {
		FuncId {
			name: name.into(),
			args: IntoIterator::into_iter(args).map(|a| a.into()).collect(),
		}
	}
	pub fn from_entity(entity: Entity) -> Self {
		let name = entity.cpp_name(CppNameStyle::Reference).into_owned().into();
		let args = if let EntityKind::FunctionTemplate = entity.get_kind() {
			let mut args = vec![];
			entity.walk_children_while(|child| {
				if child.get_kind() == EntityKind::ParmDecl {
					args.push(child.get_name().map(Cow::Owned).unwrap_or_else(|| UNNAMED.into()));
				}
				WalkAction::Continue
			});
			args
		} else {
			entity
				.get_arguments()
				.into_iter()
				.flatten()
				.map(|a| a.get_name().map(Cow::Owned).unwrap_or_else(|| UNNAMED.into()))
				.collect()
		};
		Self { name, args }
	}
	pub fn name(&self) -> &str {
		self.name.as_ref()
	}
	pub fn args(&self) -> &[Cow<str>] {
		&self.args
	}
}
impl fmt::Display for FuncId<'_> {
	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
		write!(f, "{}({})", self.name, self.args.join(", "))
	}
}
#[derive(Clone)]
pub struct Func<'tu, 'ge> {
	entity: Entity<'tu>,
	type_hint: FunctionTypeHint,
	name_hint: Option<String>,
	gen_env: &'ge GeneratorEnv<'tu>,
}
impl<'tu, 'ge> Func<'tu, 'ge> {
	pub fn new(entity: Entity<'tu>, gen_env: &'ge GeneratorEnv<'tu>) -> Self {
		Self {
			entity,
			type_hint: FunctionTypeHint::None,
			name_hint: None,
			gen_env,
		}
	}
	pub fn new_ext(
		entity: Entity<'tu>,
		type_hint: FunctionTypeHint,
		name_hint: Option<String>,
		gen_env: &'ge GeneratorEnv<'tu>,
	) -> Self {
		Self {
			entity,
			type_hint,
			name_hint,
			gen_env,
		}
	}
	pub fn set_name_hint(&mut self, name_hint: Option<String>) {
		self.name_hint = name_hint;
	}
	pub fn name_hint(&self) -> Option<&str> {
		self.name_hint.as_deref()
	}
	pub fn gen_env(&self) -> &'ge GeneratorEnv<'tu> {
		self.gen_env
	}
	pub fn type_hint(&self) -> FunctionTypeHint {
		self.type_hint
	}
	pub fn kind(&self) -> Kind<'tu, 'ge> {
		const OPERATOR: &str = "operator";
		match self.entity.get_kind() {
			EntityKind::FunctionDecl => {
				if let Some(operator) = self.entity.cpp_name(CppNameStyle::Declaration).strip_prefix(OPERATOR) {
					let arg_count = self.entity.get_arguments().map_or(0, |v| v.len());
					Kind::FunctionOperator(OperatorKind::new(operator.trim(), arg_count))
				} else {
					Kind::Function
				}
			}
			EntityKind::Constructor => Kind::Constructor(Class::new(
				self.entity.get_semantic_parent().expect("Can't get parent of constructor"),
				self.gen_env,
			)),
			EntityKind::Method => {
				let class = Class::new(
					self.entity.get_semantic_parent().expect("Can't get parent of method"),
					self.gen_env,
				);
				if self.entity.is_static_method() {
					Kind::StaticMethod(class)
				} else if let Some(operator) = self.entity.cpp_name(CppNameStyle::Declaration).strip_prefix(OPERATOR) {
					let arg_count = self.entity.get_arguments().map_or(0, |v| v.len());
					Kind::InstanceOperator(class, OperatorKind::new(operator.trim(), arg_count))
				} else {
					Kind::InstanceMethod(class)
				}
			}
			EntityKind::FieldDecl | EntityKind::VarDecl => {
				let fld = Field::new(self.entity, self.gen_env);
				let cls = fld.parent();
				Kind::FieldAccessor(cls, fld)
			}
			EntityKind::ConversionFunction => Kind::ConversionMethod(Class::new(
				self.entity.get_semantic_parent().expect("Can't get parent of method"),
				self.gen_env,
			)),
			EntityKind::FunctionTemplate => match self.entity.get_template_kind() {
				Some(EntityKind::Method) => Kind::GenericInstanceMethod(Class::new(
					self.entity.get_semantic_parent().expect("Can't get parent of generic method"),
					self.gen_env,
				)),
				_ => Kind::GenericFunction,
			},
			_ => unreachable!("Unknown function entity: {:#?}", self.entity),
		}
	}
	pub fn as_field_accessor(&self) -> Option<Field<'tu, 'ge>> {
		if let Kind::FieldAccessor(..) = self.kind() {
			Some(Field::new(self.entity, self.gen_env))
		} else {
			None
		}
	}
	pub fn as_field_setter(&self) -> Option<Field<'tu, 'ge>> {
		if self.as_field_accessor().is_some() && self.type_hint == FunctionTypeHint::FieldSetter {
			Some(Field::new_ext(self.entity, FieldTypeHint::FieldSetter, self.gen_env))
		} else {
			None
		}
	}
	pub fn constness(&self) -> Constness {
		if settings::FORCE_CONSTANT_METHOD.contains(self.cpp_name(CppNameStyle::Reference).as_ref()) {
			Constness::Const
		} else if let Some(fld) = self.as_field_accessor() {
			if self.type_hint == FunctionTypeHint::FieldSetter {
				Constness::Mut
			} else {
				let type_ref = fld.type_ref();
				Constness::from_is_mut(
					type_ref.as_array().is_some()
						|| type_ref.as_smart_ptr().is_some()
						|| type_ref.as_pointer().map_or(false, |r| r.constness().is_mut()),
				)
			}
		} else {
			Constness::from_is_const(self.entity.is_const_method())
		}
	}
	pub fn is_abstract(&self) -> bool {
		self.entity.is_pure_virtual_method()
	}
	pub fn is_generic(&self) -> bool {
		match self.kind() {
			Kind::GenericFunction | Kind::GenericInstanceMethod(..) => !self.type_hint.as_specialized().is_some(),
			Kind::Function
			| Kind::Constructor(..)
			| Kind::InstanceMethod(..)
			| Kind::StaticMethod(..)
			| Kind::FieldAccessor(..)
			| Kind::ConversionMethod(..)
			| Kind::FunctionOperator(..)
			| Kind::InstanceOperator(..) => false,
		}
	}
	pub fn is_infallible(&self) -> bool {
		self.as_field_accessor().is_some()
			|| matches!(
				self.entity.get_exception_specification(),
				Some(ExceptionSpecification::BasicNoexcept) | Some(ExceptionSpecification::Unevaluated)
			) || settings::FORCE_INFALLIBLE.contains(&self.func_id())
	}
	pub fn is_unsafe(&self) -> bool {
		settings::FUNC_UNSAFE.contains(&self.func_id())
			|| self
				.arguments()
				.into_iter()
				.any(|a| a.type_ref().is_rust_by_ptr() && !a.is_user_data())
	}
	pub fn is_default_constructor(&self) -> bool {
		self.entity.is_default_constructor() && !self.has_arguments()
	}
	pub fn is_clone(&self) -> bool {
		if self.cpp_name(CppNameStyle::Declaration) == "clone" {
			if let Some(c) = self.kind().as_instance_method() {
				!self.has_arguments() && self.return_type().as_class().map_or(false, |r| r == *c)
			} else {
				false
			}
		} else {
			false
		}
	}
	pub fn is_no_discard(&self) -> bool {
		self.gen_env.get_export_config(self.entity).map_or(false, |c| c.no_discard)
	}
	pub fn is_naked_return(&self) -> bool {
		self.is_infallible() && {
			let ret_type = self.return_type();
			ret_type.is_primitive()
				|| ret_type.as_pointer().is_some()
				|| ret_type.as_array().is_some()
				|| ret_type.is_extern_by_ptr()
				|| ret_type.as_string().is_some()
		}
	}
	pub fn return_type(&self) -> TypeRef<'tu, 'ge> {
		match self.kind() {
			Kind::Constructor(cls) => cls.type_ref(),
			Kind::Function
			| Kind::InstanceMethod(..)
			| Kind::StaticMethod(..)
			| Kind::ConversionMethod(..)
			| Kind::GenericInstanceMethod(..)
			| Kind::GenericFunction
			| Kind::FunctionOperator(..)
			| Kind::InstanceOperator(..) => {
				let mut out = TypeRef::new_ext(
					self.entity.get_result_type().expect("Can't get return type"),
					TypeRefTypeHint::PrimitiveRefAsPointer,
					None,
					self.gen_env,
				);
				if let Some(spec) = self.type_hint.as_specialized() {
					if out.is_generic() {
						let spec_type = spec
							.get(out.base().cpp_name(CppNameStyle::Reference).as_ref())
							.and_then(|s| self.gen_env.resolve_type(s));
						if let Some(spec_type) = spec_type {
							out.set_type_hint(TypeRefTypeHint::Specialized(spec_type));
						}
					}
				} else if let Some(&over) = settings::ARGUMENT_OVERRIDE.get(&self.func_id()).and_then(|x| x.get("return")) {
					out.set_type_hint(TypeRefTypeHint::ArgOverride(over))
				}
				if let Some(type_ref) = out.as_reference() {
					type_ref
				} else {
					out
				}
			}
			Kind::FieldAccessor(..) => {
				if self.type_hint == FunctionTypeHint::FieldSetter {
					self.gen_env.resolve_typeref("void")
				} else {
					let mut out = Field::new(self.entity, self.gen_env).type_ref();
					out.set_type_hint(TypeRefTypeHint::PrimitiveRefAsPointer);
					out
				}
			}
		}
	}
	pub fn has_arguments(&self) -> bool {
		!self.clang_arguments().is_empty()
	}
	pub fn clang_arguments(&self) -> Vec<Entity<'tu>> {
		match self.kind() {
			Kind::GenericFunction | Kind::GenericInstanceMethod(..) => {
				let mut out = vec![];
				self.entity.walk_children_while(|child| {
					if child.get_kind() == EntityKind::ParmDecl {
						out.push(child);
					}
					WalkAction::Continue
				});
				out
			}
			Kind::FieldAccessor(..) => {
				if self.type_hint == FunctionTypeHint::FieldSetter {
					vec![self.entity]
				} else {
					vec![]
				}
			}
			_ => self.entity.get_arguments().expect("Can't get arguments"),
		}
	}
	pub fn arguments(&self) -> Vec<Field<'tu, 'ge>> {
		let empty_spec_hashmap = HashMap::new();
		let spec = self.type_hint.as_specialized().unwrap_or(&empty_spec_hashmap);
		let is_field_setter = self.as_field_setter().is_some();
		let arg_overrides = settings::ARGUMENT_OVERRIDE.get(&self.func_id());
		self
			.clang_arguments()
			.into_iter()
			.map(|a| {
				if is_field_setter {
					return Field::new_ext(a, FieldTypeHint::FieldSetter, self.gen_env);
				}
				let arg_override = arg_overrides.and_then(|o| a.get_name().and_then(|arg_name| o.get(arg_name.as_str())));
				if let Some(arg_override) = arg_override {
					return Field::new_ext(a, FieldTypeHint::ArgOverride(*arg_override), self.gen_env);
				}
				let out = Field::new(a, self.gen_env);
				let type_ref = out.type_ref();
				if type_ref.is_generic() {
					let spec_type = spec
						.get(type_ref.base().cpp_name(CppNameStyle::Reference).as_ref())
						.and_then(|s| self.gen_env.resolve_type(s));
					if let Some(spec_type) = spec_type {
						return Field::new_ext(a, FieldTypeHint::Specialized(spec_type), self.gen_env);
					}
				}
				out
			})
			.collect()
	}
	pub fn generated_types(&self) -> Vec<GeneratedType<'tu, 'ge>> {
		self
			.arguments()
			.into_iter()
			.map(|a| a.type_ref())
			.filter(|t| !t.is_ignored())
			.flat_map(|t| t.generated_types())
			.chain(self.return_type().generated_types())
			.collect()
	}
	pub fn identifier(&self) -> Cow<str> {
		let mut out: String = if self.as_field_accessor().is_some() {
			let mut out: String = self.cpp_namespace().into_owned();
			if !out.is_empty() {
				out += "::";
			}
			let local_name = DefaultElement::cpp_name(self, CppNameStyle::Declaration);
			let (first_letter, rest) = local_name.split_at(1);
			if self.as_field_setter().is_some() {
				write!(out, "setProp{}{}", first_letter.to_uppercase(), rest).expect("write! to String shouldn't fail");
			} else {
				write!(out, "getProp{}{}", first_letter.to_uppercase(), rest).expect("write! to String shouldn't fail");
			}
			out
		} else {
			self.cpp_name(CppNameStyle::Reference).into_owned()
		};
		out.cleanup_name();
		if let Some(spec) = self.type_hint.as_specialized() {
			for typ in spec.values() {
				out.push('_');
				let mut typ = typ.to_string();
				typ.cleanup_name();
				out.push_str(&typ);
			}
		}
		if self.constness().is_const() {
			out += "_const";
		}
		for arg in self.arguments() {
			out.push('_');
			let type_ref = arg.type_ref();
			out += &type_ref.cpp_safe_id();
		}
		out.into()
	}
	pub fn func_id(&self) -> FuncId {
		let mut out = FuncId::from_entity(self.entity);
		if self.as_field_setter().is_some() {
			out.args.push("val".into());
		}
		out
	}
}
impl<'tu> EntityElement<'tu> for Func<'tu, '_> {
	fn entity(&self) -> Entity<'tu> {
		self.entity
	}
}
impl Element for Func<'_, '_> {
	fn is_excluded(&self) -> bool {
		let identifier = self.identifier();
		if settings::FUNC_MANUAL.contains_key(identifier.as_ref()) || settings::FUNC_SPECIALIZE.contains_key(identifier.as_ref()) {
			return false;
		}
		DefaultElement::is_excluded(self)
			|| self.is_generic()
			|| self.kind().as_operator().map_or(false, |kind| {
				if matches!(kind, (_, OperatorKind::Incr | OperatorKind::Decr)) {
					self.clang_arguments().len() == 1
				} else {
					false
				}
			}) || if let Some(cls) = self.kind().as_constructor() {
			cls.is_abstract()
		} else {
			false
		}
	}
	fn is_ignored(&self) -> bool {
		let identifier = self.identifier();
		if settings::FUNC_MANUAL.contains_key(identifier.as_ref()) || settings::FUNC_SPECIALIZE.contains_key(identifier.as_ref()) {
			return false;
		}
		DefaultElement::is_ignored(self)
			|| self.entity.get_availability() == Availability::Unavailable
			|| self
				.kind()
				.as_operator()
				.map_or(false, |(_, op)| op == OperatorKind::Unsupported)
			|| self.arguments().into_iter().any(|a| a.type_ref().is_ignored())
			|| {
				let ret = self.return_type();
				ret.is_ignored() || ret.as_class().map_or(false, |cls| cls.is_abstract())
			} || settings::FUNC_RENAME
			.get(identifier.as_ref())
			.filter(|&&n| n == "-")
			.is_some()
	}
	fn is_system(&self) -> bool {
		DefaultElement::is_system(self)
	}
	fn is_public(&self) -> bool {
		DefaultElement::is_public(self)
	}
	fn usr(&self) -> Cow<str> {
		DefaultElement::usr(self)
	}
	fn cpp_namespace(&self) -> Cow<str> {
		DefaultElement::cpp_namespace(self).into()
	}
	fn cpp_name(&self, style: CppNameStyle) -> Cow<str> {
		let decl_name = if self.kind().as_conversion_method().is_some() {
			format!("operator {}", self.return_type().cpp_name(CppNameStyle::Reference))
		} else if self.as_field_setter().is_some() {
			let name = DefaultElement::cpp_name(self, CppNameStyle::Declaration);
			let (first_letter, rest) = name.split_at(1);
			format!("set{}{}", first_letter.to_uppercase(), rest)
		} else {
			return DefaultElement::cpp_name(self, style);
		};
		match style {
			CppNameStyle::Declaration => decl_name.into(),
			CppNameStyle::Reference => DefaultElement::cpp_decl_name_with_namespace(self, &decl_name),
		}
	}
}
impl fmt::Display for Func<'_, '_> {
	fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
		write!(f, "{}", self.entity.get_display_name().expect("Can't get display name"))
	}
}
impl fmt::Debug for Func<'_, '_> {
	fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
		let mut debug_struct = f.debug_struct("Func");
		self
			.update_debug_struct(&mut debug_struct)
			.field("export_config", &self.gen_env.get_export_config(self.entity))
			.field("is_const", &self.constness())
			.field("is_infallible", &self.is_infallible())
			.field("type_hint", &self.type_hint)
			.field("kind", &self.kind())
			.field("return_type", &self.return_type())
			.field("arguments", &self.arguments())
			.finish()
	}
}