//! Types holding data of items.

mod field;
mod fields;

use proc_macro2::Span;
#[cfg(not(feature = "nightly"))]
use syn::Expr;
use syn::{FieldsNamed, Ident, Pat, PatPath, Path, Result, Variant};

pub use self::{
	field::{Field, Member},
	fields::Fields,
};
use crate::{util, Default, DeriveWhere, Either, Error, Incomparable, Skip, Trait, VariantAttr};

/// Holds all relevant data of a struct, union or variant.
#[cfg_attr(test, derive(Debug))]
pub struct Data<'a> {
	/// [`Skip`] attribute of this struct, union or variant.
	skip_inner: Skip,
	/// [`Incomparable`] attribute of this struct, union or variant.
	pub incomparable: Incomparable,
	/// [`struct@Ident`] of this struct, union or variant, used for implementing
	/// [`Debug`](std::fmt::Debug).
	pub ident: &'a Ident,
	/// [`Path`] of this struct, union or variant, used to construct new
	/// instances of that item, for example when implementing [`Clone`].
	pub path: Path,
	/// [Type](DataType) of this struct, union or variant.
	pub type_: DataType<'a>,
	#[cfg(not(feature = "nightly"))]
	/// Discriminant of this variant.
	pub discriminant: Option<&'a Expr>,
}

/// Type of this data.
#[cfg_attr(test, derive(Debug))]
pub enum DataType<'a> {
	/// Struct.
	Struct(Fields<'a>),
	/// Tuple.
	Tuple(Fields<'a>),
	/// Union.
	Union(Fields<'a>),
	/// Variant.
	Variant {
		/// [`struct@Default`] attribute of this variant.
		default: Default,
		/// [Type](VariantType) of this variant.
		type_: VariantType<'a>,
	},
	/// Unit.
	Unit(Pat),
}

/// Type of [`Data`].
#[cfg_attr(test, derive(Debug))]
pub enum VariantType<'a> {
	/// Struct variant.
	Struct(Fields<'a>),
	/// Tuple variant.
	Tuple(Fields<'a>),
	/// Unit variant.
	Unit(Pat),
}

/// Type to enable simplified matching.
pub enum SimpleType<'a> {
	/// Struct, struct variant.
	Struct(&'a Fields<'a>),
	/// Tuple struct or tuple variant.
	Tuple(&'a Fields<'a>),
	/// Union.
	Union,
	/// Unit variant.
	Unit(&'a Pat),
}

impl<'a> Data<'a> {
	/// Create [`Data`]s from [`syn::Fields`] of a struct.
	pub fn from_struct(
		span: Span,
		derive_wheres: &[DeriveWhere],
		skip_inner: Skip,
		incomparable: Incomparable,
		ident: &'a Ident,
		fields: &'a syn::Fields,
	) -> Result<Self> {
		let path = util::path_from_idents(&[ident]);

		match fields {
			syn::Fields::Named(fields) => {
				if fields.named.is_empty() && incomparable.0.is_none() {
					Err(Error::item_empty(span))
				} else {
					let fields =
						Fields::from_named(derive_wheres, &skip_inner, path.clone(), fields)?;

					Ok(Self {
						skip_inner,
						incomparable,
						ident,
						path,
						type_: DataType::Struct(fields),
						#[cfg(not(feature = "nightly"))]
						discriminant: None,
					})
				}
			}
			syn::Fields::Unnamed(fields) => {
				if fields.unnamed.is_empty() && incomparable.0.is_none() {
					Err(Error::item_empty(span))
				} else {
					let fields =
						Fields::from_unnamed(derive_wheres, &skip_inner, path.clone(), fields)?;

					Ok(Self {
						skip_inner,
						incomparable,
						ident,
						path,
						type_: DataType::Tuple(fields),
						#[cfg(not(feature = "nightly"))]
						discriminant: None,
					})
				}
			}
			syn::Fields::Unit if incomparable.0.is_some() => Ok(Self {
				skip_inner,
				incomparable,
				ident,
				path: path.clone(),
				type_: DataType::Unit(Pat::Path(PatPath {
					attrs: Vec::new(),
					qself: None,
					path,
				})),
				#[cfg(not(feature = "nightly"))]
				discriminant: None,
			}),
			syn::Fields::Unit => Err(Error::item_empty(span)),
		}
	}

	/// Create [`Data`]s from [`FieldsNamed`] of an union.
	pub fn from_union(
		span: Span,
		derive_wheres: &[DeriveWhere],
		skip_inner: Skip,
		incomparable: Incomparable,
		ident: &'a Ident,
		fields: &'a FieldsNamed,
	) -> Result<Self> {
		if fields.named.is_empty() && incomparable.0.is_none() {
			Err(Error::item_empty(span))
		} else {
			let path = util::path_from_idents(&[ident]);
			let fields = Fields::from_named(derive_wheres, &skip_inner, path.clone(), fields)?;

			Ok(Self {
				skip_inner,
				incomparable,
				ident,
				path,
				type_: DataType::Union(fields),
				#[cfg(not(feature = "nightly"))]
				discriminant: None,
			})
		}
	}

	/// Create [`Data`]s from [`syn::Fields`] of a variant.
	pub fn from_variant(
		item_ident: &'a Ident,
		derive_wheres: &[DeriveWhere],
		variant: &'a Variant,
	) -> Result<Self> {
		// Parse `Attribute`s on variant.
		let VariantAttr {
			default,
			skip_inner,
			incomparable,
		} = VariantAttr::from_attrs(&variant.attrs, derive_wheres, variant)?;

		let path = util::path_from_idents(&[item_ident, &variant.ident]);

		match &variant.fields {
			syn::Fields::Named(fields) => {
				let fields = Fields::from_named(derive_wheres, &skip_inner, path.clone(), fields)?;

				Ok(Self {
					skip_inner,
					incomparable,
					ident: &variant.ident,
					path,
					type_: DataType::Variant {
						default,
						type_: VariantType::Struct(fields),
					},
					#[cfg(not(feature = "nightly"))]
					discriminant: variant.discriminant.as_ref().map(|(_, expr)| expr),
				})
			}
			syn::Fields::Unnamed(fields) => {
				let fields =
					Fields::from_unnamed(derive_wheres, &skip_inner, path.clone(), fields)?;

				Ok(Self {
					skip_inner,
					incomparable,
					ident: &variant.ident,
					path,
					type_: DataType::Variant {
						default,
						type_: VariantType::Tuple(fields),
					},
					#[cfg(not(feature = "nightly"))]
					discriminant: variant.discriminant.as_ref().map(|(_, expr)| expr),
				})
			}
			syn::Fields::Unit => {
				let pattern = Pat::Path(PatPath {
					attrs: Vec::new(),
					qself: None,
					path: path.clone(),
				});

				Ok(Self {
					skip_inner,
					incomparable,
					ident: &variant.ident,
					path,
					type_: DataType::Variant {
						default,
						type_: VariantType::Unit(pattern),
					},
					#[cfg(not(feature = "nightly"))]
					discriminant: variant.discriminant.as_ref().map(|(_, expr)| expr),
				})
			}
		}
	}

	/// Returns the [`Fields`] of this [`Data`]. If [`Data`] is a unit variant
	/// or struct returns [`Pat`] instead.
	pub fn fields(&self) -> Either<&Fields<'_>, &Pat> {
		match &self.type_ {
			DataType::Struct(fields)
			| DataType::Tuple(fields)
			| DataType::Union(fields)
			| DataType::Variant {
				type_: VariantType::Struct(fields),
				..
			}
			| DataType::Variant {
				type_: VariantType::Tuple(fields),
				..
			} => Either::Left(fields),
			DataType::Unit(pattern)
			| DataType::Variant {
				type_: VariantType::Unit(pattern),
				..
			} => Either::Right(pattern),
		}
	}

	/// Returns the destructuring `self` pattern of this [`Data`].
	pub fn self_pattern(&self) -> &Pat {
		match self.fields() {
			Either::Left(fields) => &fields.self_pattern,
			Either::Right(pattern) => pattern,
		}
	}

	/// Returns `true` if this variant is marked as the [`struct@Default`]. If
	/// not a variant, always returns `true`.
	pub fn is_default(&self) -> bool {
		match self.type_ {
			DataType::Variant { default, .. } => default.0.is_some(),
			_ => true,
		}
	}

	/// Returns `true` if this item or variant is marked as [`Incomparable`].
	pub fn is_incomparable(&self) -> bool {
		self.incomparable.0.is_some()
	}

	/// Returns [`Some`] if this variant has a [`struct@Default`]. If
	/// not a variant, always returns [`None`].
	pub fn default_span(&self) -> Option<Span> {
		match &self.type_ {
			DataType::Variant { default, .. } => default.0,
			_ => None,
		}
	}

	/// Returns `true` if this [`Data`] has no [`Fields`].
	pub fn is_empty(&self, trait_: Trait) -> bool {
		self.iter_fields(trait_).count() == 0
	}

	/// Returns `true` if a field is skipped with that [`Trait`].
	pub fn any_skip_trait(&self, trait_: Trait) -> bool {
		self.skip_inner.trait_skipped(trait_)
			|| match self.fields() {
				Either::Left(fields) => fields.any_skip_trait(trait_),
				Either::Right(_) => false,
			}
	}

	/// Returns `true` if all fields are skipped with that [`Trait`].
	pub fn skip(&self, trait_: Trait) -> bool {
		self.skip_inner.trait_skipped(trait_)
			|| match self.fields() {
				Either::Left(fields) => fields.skip(trait_),
				Either::Right(_) => false,
			}
	}

	/// Return a [`SimpleType`].
	pub fn simple_type(&self) -> SimpleType<'_> {
		match &self.type_ {
			DataType::Struct(fields)
			| DataType::Variant {
				type_: VariantType::Struct(fields),
				..
			} => SimpleType::Struct(fields),
			DataType::Tuple(fields)
			| DataType::Variant {
				type_: VariantType::Tuple(fields),
				..
			} => SimpleType::Tuple(fields),
			DataType::Unit(pattern)
			| DataType::Variant {
				type_: VariantType::Unit(pattern),
				..
			} => SimpleType::Unit(pattern),
			DataType::Union(_) => SimpleType::Union,
		}
	}

	/// Returns an [`Iterator`] over [`Field`]s.
	pub fn iter_fields(&self, trait_: Trait) -> impl DoubleEndedIterator<Item = &Field<'_>> {
		if self.skip(trait_) {
			[].iter()
		} else {
			match self.fields() {
				Either::Left(fields) => fields.fields.iter(),
				Either::Right(_) => [].iter(),
			}
		}
		.filter(move |field| !field.skip(trait_))
	}

	/// Returns an [`Iterator`] over [`Member`]s.
	pub fn iter_field_ident(&self, trait_: Trait) -> impl Iterator<Item = &Member<'_>> {
		self.iter_fields(trait_).map(|field| &field.member)
	}

	/// Returns an [`Iterator`] over [`struct@Ident`]s used as temporary
	/// variables for destructuring `self`.
	pub fn iter_self_ident(&self, trait_: Trait) -> impl DoubleEndedIterator<Item = &Ident> {
		self.iter_fields(trait_).map(|field| &field.self_ident)
	}

	/// Returns an [`Iterator`] over [`struct@Ident`]s used as temporary
	/// variables for destructuring `other`.
	pub fn iter_other_ident(&self, trait_: Trait) -> impl DoubleEndedIterator<Item = &Ident> {
		self.iter_fields(trait_).map(|field| &field.other_ident)
	}
}
