use {
	crate::{
		lua::{LuaError, LuaString},
		prelude::*,
	},
	glib::{
		variant::{DictEntry, VariantTypeMismatchError},
		VariantType,
	},
	std::borrow::Cow,
};
newtype_wrapper! {
	#[derive(Debug, Eq, Clone, Hash)]
	pub struct LuaVariant(Variant ;? LuaError);
}
#[derive(Copy, Clone, Debug, PartialOrd, Ord, PartialEq, Eq, Hash)]
pub enum LuaType {
	Nil,
	Boolean,
	Integer,
	Float,
	String,
	Table,
}
impl LuaType {
	pub fn with_variant_type(ty: &VariantTy) -> Option<Self> {
		Some(match ty {
			ty if ty == VariantTy::UNIT => LuaType::Nil,
			ty if ty == VariantTy::BOOLEAN => LuaType::Boolean,
			ty if ty == VariantTy::INT64 || ty == VariantTy::INT32 || ty == VariantTy::INT16
				|| ty == VariantTy::UINT32 || ty == VariantTy::UINT16
				=> LuaType::Integer,
			ty if ty == VariantTy::DOUBLE => LuaType::Float,
			ty if ty == VariantTy::STRING => LuaType::String,
			ty if ty.is_subtype_of(VariantTy::DICTIONARY) => match ty.element() {
				ty if Self::with_variant_type(ty.key()).is_some() && Self::with_variant_type(ty.value()).is_some() => LuaType::Table,
				_ => return None,
			},
			ty if ty.is_array() && Self::with_variant_type(ty.element()).is_some() => LuaType::Table,
			_ => return None,
		})
	}
	pub fn with_lua_variant(var: &LuaVariant) -> Self {
		const MAX_SAFE_INTEGER: u64 = i64::MAX as u64;
		match Self::with_variant_type(var.as_variant().type_()) {
			Some(ty) => ty,
			None => match var.as_variant().classify() {
				VariantClass::Uint64 => match var.as_variant().get::<u64>() {
					Some(0..=MAX_SAFE_INTEGER) => LuaType::Integer,
					_ => LuaType::Float,
				},
				VariantClass::Variant => Self::with_lua_variant(&var.flattened()),
				_ => LuaType::Table,
			},
		}
	}
	pub fn with_variant(var: &Variant) -> Result<Self, LuaError> {
		const MAX_SAFE_INTEGER: u64 = i64::MAX as u64;
		let ty = var.type_();
		if let Some(ty) = Self::with_variant_type(ty) {
			return Ok(ty)
		}
		Ok(match var.classify() {
			VariantClass::Uint64 => match var.get::<u64>() {
				Some(0..=MAX_SAFE_INTEGER) => LuaType::Integer,
				_ => LuaType::Float,
			},
			VariantClass::Variant => Self::with_variant(&var.as_variant().expect("VariantTy"))?,
			VariantClass::Array if ty.element().is_dict_entry() => match var
				.iter()
				.map(|v| Self::with_variant(&v.child_value(0)).and_then(|_| Self::with_variant(&v.child_value(1)).map(drop)))
				.collect()
			{
				Ok(()) => LuaType::Table,
				Err(e) => return Err(e),
			},
			VariantClass::Array if var.iter().all(|v| Self::with_variant(&v).is_ok()) => LuaType::Table,
			_ => return Err(LuaError::UnsupportedType(Cow::Owned(ty.to_owned()))),
		})
	}
	pub fn is_numeric(&self) -> bool {
		matches!(self, LuaType::Integer | LuaType::Float)
	}
	pub fn is_table(&self) -> bool {
		matches!(self, LuaType::Table)
	}
	pub fn is_nil(&self) -> bool {
		matches!(self, LuaType::Nil)
	}
}
pub enum LuaValue<'a> {
	Nil,
	Boolean(bool),
	Integer(i64),
	Float(f64),
	String(LuaString<'a>),
	Table(LuaTable<'a>),
}
impl<'a> LuaValue<'a> {
	pub fn lua_type(&self) -> LuaType {
		match self {
			LuaValue::Nil => LuaType::Nil,
			LuaValue::Boolean(..) => LuaType::Boolean,
			LuaValue::Integer(..) => LuaType::Integer,
			LuaValue::Float(..) => LuaType::Float,
			LuaValue::String(..) => LuaType::String,
			LuaValue::Table(..) => LuaType::Table,
		}
	}
	pub fn to_lua_string(&self) -> Result<LuaString, LuaError> {
		Ok(match self {
			LuaValue::Nil => b"nil"[..].into(),
			LuaValue::Boolean(v) => v.to_string().into(),
			LuaValue::Integer(v) => v.to_string().into(),
			LuaValue::Float(v) => v.to_string().into(),
			LuaValue::String(v) => v.borrowed(),
			LuaValue::Table(_) =>
				return Err(LuaError::TypeMismatch(VariantTypeMismatchError::new(
					VariantTy::VARDICT.to_owned(),
					VariantTy::STRING.to_owned(),
				))),
		})
	}
	pub fn to_integer(&self) -> Result<i64, LuaError> {
		match *self {
			LuaValue::Integer(v) => Ok(v),
			LuaValue::Float(v) => Ok(v as i64),
			LuaValue::String(ref s) => s.parse(),
			ref v => Err(LuaError::TypeMismatch(VariantTypeMismatchError::new(
				match v {
					LuaValue::Nil => VariantTy::UNIT,
					LuaValue::Boolean(..) => VariantTy::BOOLEAN,
					LuaValue::Table(t) if t.is_array().unwrap_or(false) => VariantTy::ARRAY,
					_ => VariantTy::VARDICT,
				}
				.to_owned(),
				VariantTy::INT64.to_owned(),
			))),
		}
	}
}
impl<'v> LuaVariant<'v> {
	pub fn nil() -> Self {
		unsafe { Self::unsafe_from(().to_variant()) }
	}
	fn check(v: &Variant) -> Result<(), LuaError> {
		LuaType::with_variant(v).map(drop)
	}
	pub fn convert_from(v: &Variant) -> Result<Self, LuaError> {
		if Self::check(&v).is_ok() {
			return Ok(unsafe { Self::unsafe_from(v.clone()) })
		}
		match v.classify() {
			VariantClass::Variant => LuaVariant::convert_from(&v.as_variant().expect("VariantClass")),
			VariantClass::Byte => Ok(Self::from(v.get::<u8>().expect("VariantClass") as u16)),
			VariantClass::Maybe => match v.as_maybe() {
				Some(v) => LuaVariant::convert_from(&v),
				None => Ok(().into()),
			},
			VariantClass::Array if v.type_().element().is_dict_entry() => v
				.iter()
				.map(|v| {
					LuaVariant::convert_from(&v.child_value(0))
						.and_then(|k| LuaVariant::convert_from(&v.child_value(1)).map(|v| (k, v)))
				})
				.collect(),
			VariantClass::Array | VariantClass::Tuple => v.iter().map(|v| LuaVariant::convert_from(&v)).collect(),
			_ => Err(LuaError::UnsupportedType(Cow::Owned(v.type_().to_owned()))),
		}
	}
	pub fn with_bytes<B: AsRef<[u8]>>(bytes: B) -> Self {
		bytes.as_ref().iter().map(|&v| LuaVariant::from(v as u16)).collect()
	}
	pub fn lua_type(&self) -> LuaType {
		LuaType::with_lua_variant(self)
	}
	pub fn lua_value(&self) -> LuaValue {
		match self.lua_type() {
			LuaType::Nil => Some(LuaValue::Nil),
			LuaType::Boolean => self.get_bool().map(LuaValue::Boolean),
			LuaType::Integer => self.get_integer().and_then(|i| i.ok()).map(LuaValue::Integer),
			LuaType::Float => self.get_float().and_then(|f| f.ok()).map(LuaValue::Float),
			LuaType::String => self.get_string().map(LuaValue::String),
			LuaType::Table => LuaTable::borrow(self.as_variant()).ok().map(LuaValue::Table),
		}
		.expect("LuaType")
	}
	pub fn flattened(&self) -> Self {
		match self.as_variant().classify() {
			VariantClass::Variant =>
				unsafe { LuaVariant::unsafe_from(self.as_variant().as_variant().expect("VariantClass")) }.flattened(),
			_ => self.clone(),
		}
	}
	pub fn get_nil<'a>(&'a self) -> Option<()> {
		match self.as_variant().classify() {
			VariantClass::Tuple => self.as_variant().get(),
			VariantClass::Variant => self.flattened().get_nil(),
			_ => None,
		}
	}
	pub fn get_bool<'a>(&'a self) -> Option<bool> {
		match self.as_variant().classify() {
			VariantClass::Boolean => self.as_variant().get(),
			VariantClass::Variant => self.flattened().get_bool(),
			_ => None,
		}
	}
	pub fn get_table<'a>(&'a self) -> Option<LuaTable<'a>> {
		match self.lua_type() {
			LuaType::Table => Some(unsafe { LuaTable::unsafe_from(self.flattened().into_inner()) }),
			_ => None,
		}
	}
	pub fn get_string(&self) -> Option<LuaString> {
		LuaString::try_from(self.as_variant()).ok()
	}
	pub fn get_float(&self) -> Option<Result<f64, LuaError>> {
		const MAX_SAFE_INTEGER: i64 = 2 ^ 53 - 1;
		const MAX_SAFE_INTEGER_UNSIGNED: u64 = 2 ^ 53 - 1;
		const MIN_SAFE_INTEGER: i64 = -(2 ^ 53 - 1);
		Some(match self.as_variant().classify() {
			VariantClass::Double => Ok(self.as_variant().get::<f64>().expect("VariantClass")),
			VariantClass::Boolean => u8::from(self.as_variant().get::<bool>().unwrap())
				.try_into()
				.map_err(Into::into),
			VariantClass::Byte => self.as_variant().get::<u8>().unwrap().try_into().map_err(Into::into),
			VariantClass::Uint16 => self.as_variant().get::<u16>().unwrap().try_into().map_err(Into::into),
			VariantClass::Uint32 => self.as_variant().get::<u32>().unwrap().try_into().map_err(Into::into),
			VariantClass::Uint64 => match self.as_variant().get::<u64>().unwrap() {
				v @ 0..=MAX_SAFE_INTEGER_UNSIGNED => Ok(v as f64),
				v => Err(i32::try_from(v).unwrap_err().into()),
			},
			VariantClass::Int16 => self.as_variant().get::<i16>().unwrap().try_into().map_err(Into::into),
			VariantClass::Int32 => self.as_variant().get::<i32>().unwrap().try_into().map_err(Into::into),
			VariantClass::Int64 => match self.as_variant().get::<i64>().unwrap() {
				v @ MIN_SAFE_INTEGER..=MAX_SAFE_INTEGER => Ok(v as f64),
				v => Err(u32::try_from(v).unwrap_err().into()),
			},
			VariantClass::Variant => return self.flattened().get_float(),
			_ => return None,
		})
	}
	#[rustfmt::skip]
	pub fn get_integer<T>(&self) -> Option<Result<T, LuaError>>
	where
		T: TryFrom<bool>, <T as TryFrom<bool>>::Error: Into<LuaError>,
		T: TryFrom<u8>, <T as TryFrom<u8>>::Error: Into<LuaError>,
		T: TryFrom<u16>, <T as TryFrom<u16>>::Error: Into<LuaError>,
		T: TryFrom<u32>, <T as TryFrom<u32>>::Error: Into<LuaError>,
		T: TryFrom<u64>, <T as TryFrom<u64>>::Error: Into<LuaError>,
		T: TryFrom<i16>, <T as TryFrom<i16>>::Error: Into<LuaError>,
		T: TryFrom<i32>, <T as TryFrom<i32>>::Error: Into<LuaError>,
		T: TryFrom<i64>, <T as TryFrom<i64>>::Error: Into<LuaError>,
	{
		Some(match self.as_variant().classify() {
			VariantClass::Boolean => self.as_variant().get::<bool>().unwrap().try_into().map_err(Into::into),
			VariantClass::Byte => self.as_variant().get::<u8>().unwrap().try_into().map_err(Into::into),
			VariantClass::Uint16 => self.as_variant().get::<u16>().unwrap().try_into().map_err(Into::into),
			VariantClass::Uint32 => self.as_variant().get::<u32>().unwrap().try_into().map_err(Into::into),
			VariantClass::Uint64 => self.as_variant().get::<u64>().unwrap().try_into().map_err(Into::into),
			VariantClass::Int16 => self.as_variant().get::<i16>().unwrap().try_into().map_err(Into::into),
			VariantClass::Int32 => self.as_variant().get::<i32>().unwrap().try_into().map_err(Into::into),
			VariantClass::Int64 => self.as_variant().get::<i64>().unwrap().try_into().map_err(Into::into),
			VariantClass::Variant => return self.flattened().get_integer::<T>(),
			_ => return None,
		})
	}
}
impl<'a> StaticVariantType for LuaVariant<'a> {
	fn static_variant_type() -> Cow<'static, VariantTy> {
		VariantTy::VARIANT.into()
	}
}
impl<'a> ToVariant for LuaVariant<'a> {
	fn to_variant(&self) -> Variant {
		Variant::from_variant(self.as_variant())
	}
}
newtype_wrapper! {
	#[derive(Debug, Eq, Clone, Hash)]
	pub struct LuaTable(Variant ;? LuaError);
}
impl<'v> LuaTable<'v> {
	fn check(v: &Variant) -> Result<(), LuaError> {
		LuaType::with_variant(v).and_then(|ty| match ty {
			LuaType::Table => Ok(()),
			_ => Err(LuaError::TypeMismatch(VariantTypeMismatchError::new(
				v.type_().to_owned(),
				VariantTy::ARRAY.to_owned(),
			))),
		})
	}
	pub fn into_lua_variant(self) -> LuaVariant<'v> {
		unsafe { LuaVariant::unsafe_from(self.into_inner()) }
	}
	pub fn lua_variant(&self) -> LuaVariant {
		unsafe { LuaVariant::unsafe_from(self.as_variant()) }
	}
	pub fn entry_len(&self) -> usize {
		self.as_variant().n_children()
	}
	pub fn is_empty(&self) -> bool {
		self.entry_len() == 0
	}
	pub fn table_getn(&self) -> Option<u64> {
		self
			.by_key(&"n".into())
			.and_then(|v| v.get_integer())
			.and_then(|v| v.ok())
	}
	pub fn variant_is_dict(&self) -> bool {
		self.as_variant().type_().is_subtype_of(VariantTy::DICTIONARY)
	}
	pub fn iter_dict_entries<'a>(
		&'a self,
	) -> impl Iterator<Item = DictEntry<LuaVariant<'static>, LuaVariant<'static>>> + 'a {
		self.as_variant().iter().enumerate().map(move |(i, v)| unsafe {
			match self.variant_is_dict() {
				true => DictEntry::new(
					LuaVariant::unsafe_from(v.child_value(0)),
					LuaVariant::unsafe_from(v.child_value(1)),
				),
				false => DictEntry::new((i as u64 + 1).into(), LuaVariant::unsafe_from(v)),
			}
		})
	}
	pub fn iter_array_indices<'a>(&'a self) -> impl Iterator<Item = Option<u64>> + 'a {
		self
			.as_variant()
			.iter()
			.enumerate()
			.map(move |(i, v)| match self.variant_is_dict() {
				true => unsafe { LuaVariant::unsafe_from(v.child_value(0)) }
					.lua_value()
					.to_integer()
					.ok()
					.and_then(|i| i.try_into().ok())
					.and_then(|idx: u64| idx.checked_sub(1)),
				false => Some(i as u64),
			})
	}
	pub fn array_indices(&self) -> Vec<(usize, u64)> {
		let mut indices: Vec<_> = self
			.iter_array_indices()
			.enumerate()
			.filter_map(|(i, idx)| idx.map(|idx| (i, idx)))
			.collect();
		indices.sort_by_key(|&(_, idx)| idx);
		indices
	}
	pub fn iter_array_entries<'a>(&'a self) -> impl Iterator<Item = (u64, LuaVariant<'static>)> + 'a {
		self
			.array_indices()
			.into_iter()
			.map(move |(i, idx)| (idx, self.value_at(i).expect("array_indices")))
	}
	pub fn iter_array<'a>(&'a self) -> impl Iterator<Item = Option<LuaVariant<'static>>> + 'a {
		let indices = self.array_indices();
		let last = self.table_getn().or_else(|| indices.last().map(|&(_, idx)| idx));
		let mut indices = indices.into_iter().peekable();
		(0..=last.unwrap_or(0)).map(move |idx| match indices.peek() {
			Some(&(_, next)) if idx == next => indices.next().map(|(i, _)| self.value_at(i).expect("array_indices")),
			Some(_) => None,
			None => indices.next().map(|_| unreachable!("array_indices")),
		})
	}
	pub fn array_len(&self) -> u64 {
		self
			.table_getn()
			.or_else(|| self.array_indices().last().map(|&(_, idx)| idx))
			.unwrap_or(0)
	}
	pub fn key_at(&self, index: usize) -> Option<LuaVariant<'static>> {
		match self.variant_is_dict() {
			true => self
				.as_variant()
				.try_child_value(index)
				.map(|v| unsafe { LuaVariant::unsafe_from(v.child_value(0)) }),
			false => None,
		}
	}
	pub fn value_at(&self, index: usize) -> Option<LuaVariant<'static>> {
		self.as_variant().try_child_value(index).map(|v| unsafe {
			LuaVariant::unsafe_from(match self.variant_is_dict() {
				true => v.child_value(1),
				false => v,
			})
		})
	}
	pub fn by_key(&self, key: &LuaVariant) -> Option<LuaVariant<'static>> {
		self
			.iter_dict_entries()
			.find(|e| e.key() == key)
			.map(|e| e.value().owned())
	}
	pub fn is_array(&self) -> Option<bool> {
		if self.table_getn().is_some() {
			return Some(true)
		}
		match self.iter_array_indices().all(|i| i.is_some()) {
			true if self.is_empty() => None,
			v => Some(v),
		}
	}
	pub fn is_vardict(&self) -> bool {
		self.as_variant().type_() == VariantTy::VARDICT
	}
	pub fn into_vardict(self) -> Result<Self, LuaError> {
		if self.is_vardict() {
			return Ok(self)
		}
		self
			.iter_dict_entries()
			.map(|e| {
				e.key()
					.lua_value()
					.to_lua_string()
					.and_then(|s| s.into_string().map_err(Into::into))
					.map(|s| (s, e.value().owned()))
			})
			.collect()
	}
}
impl StaticVariantType for LuaTable<'_> {
	fn static_variant_type() -> Cow<'static, VariantTy> {
		VariantTy::VARDICT.into()
	}
}
impl<'a, 'v, V: StaticVariantType + Into<LuaVariant<'v>>> FromIterator<V> for LuaTable<'a> {
	fn from_iter<T: IntoIterator<Item = V>>(iter: T) -> Self {
		unsafe {
			UnsafeFrom::unsafe_from(Variant::array_from_iter_with_type(
				&V::static_variant_type(),
				iter.into_iter().map(|v| v.into().to_variant()),
			))
		}
	}
}
impl<'a, 'v, K: StaticVariantType + Into<LuaVariant<'v>>, V: StaticVariantType + Into<LuaVariant<'v>>>
	FromIterator<(K, V)> for LuaTable<'a>
{
	fn from_iter<I: IntoIterator<Item = (K, V)>>(iter: I) -> Self {
		let entry_type = VariantType::new_dict_entry(&K::static_variant_type(), &V::static_variant_type());
		let entries = iter
			.into_iter()
			.map(|(k, v)| Variant::from_dict_entry(&k.into().into_variant(), &v.into().into_variant()));
		unsafe { UnsafeFrom::unsafe_from(Variant::array_from_iter_with_type(&entry_type, entries)) }
	}
}
impl<'a, 'v> FromIterator<LuaVariant<'v>> for LuaVariant<'a> {
	fn from_iter<T: IntoIterator<Item = LuaVariant<'v>>>(iter: T) -> Self {
		LuaTable::from_iter(iter).into_lua_variant()
	}
}
impl<'a, 'v, K: StaticVariantType + Into<LuaVariant<'v>>, V: StaticVariantType + Into<LuaVariant<'v>>>
	FromIterator<(K, V)> for LuaVariant<'a>
{
	fn from_iter<I: IntoIterator<Item = (K, V)>>(iter: I) -> Self {
		LuaTable::from_iter(iter).into_lua_variant()
	}
}
impl<'a> ToVariant for LuaTable<'a> {
	fn to_variant(&self) -> Variant {
		self
			.clone()
			.into_vardict()
			.expect("VarDict requires UTF8 keys")
			.into_variant()
	}
}
impl<'a> From<LuaTable<'a>> for LuaVariant<'a> {
	fn from(v: LuaTable<'a>) -> Self {
		unsafe { LuaVariant::unsafe_from(v.into_inner()) }
	}
}
impl<'a> UnsafeFrom<LuaVariant<'a>> for LuaTable<'a> {
	unsafe fn unsafe_from(v: LuaVariant<'a>) -> Self {
		Self::unsafe_from(v.into_inner())
	}
}
impl<'a> TryFrom<LuaVariant<'a>> for LuaTable<'a> {
	type Error = LuaError;
	fn try_from(v: LuaVariant<'a>) -> Result<Self, Self::Error> {
		Self::try_from(v.into_inner())
	}
}
macro_rules! lua_primitives {
	($($ty:ty,)*) => {
		$(
			impl<'a> From<$ty> for LuaVariant<'a> {
				fn from(v: $ty) -> Self {
					unsafe {
						Self::unsafe_from(v.to_variant())
					}
				}
			}
		)*
	};
}
lua_primitives! {
	i16, u16, i32, u32, i64, u64,
	bool, f64, (),
}
impl<'v, 's, S: Into<LuaString<'s>>> From<S> for LuaVariant<'v> {
	fn from(s: S) -> Self {
		unsafe { Self::unsafe_from(s.into().to_variant()) }
	}
}