use super::{direct_bound_partial_cmp, BoundOrd, BoundOrdering, BoundPartialOrd, Measure};
use range_traits::PartialEnum;
use std::{
	cmp::Ordering,
	hash::{Hash, Hasher},
	ops::Bound,
};

/// Types that can be interpreted as range bounds.
pub trait AsBound {
	type Item;

	fn bound(&self) -> Bound<&Self::Item>;
}

impl AsBound for u8 {
	type Item = u8;

	fn bound(&self) -> Bound<&u8> {
		Bound::Included(self)
	}
}

impl<'a, T> AsBound for Bound<&'a T> {
	type Item = T;

	fn bound(&self) -> Bound<&T> {
		*self
	}
}

#[inline(always)]
pub(crate) fn invert_bound<T>(bound: Bound<T>) -> Option<Bound<T>> {
	match bound {
		Bound::Unbounded => None,
		Bound::Included(t) => Some(Bound::Excluded(t)),
		Bound::Excluded(t) => Some(Bound::Included(t)),
	}
}

#[derive(Clone, Copy, Debug)]
pub enum Directed<T> {
	Start(T),
	End(T),
}

impl<T> Directed<T> {
	/// Returns the directed value, without direction.
	pub fn unwrap(self) -> T {
		match self {
			Self::Start(t) => t,
			Self::End(t) => t,
		}
	}

	/// Returns a reference to the directed value, without direction.
	pub fn value(&self) -> &T {
		match self {
			Self::Start(t) => t,
			Self::End(t) => t,
		}
	}
}

impl<T> Directed<Bound<T>> {
	pub fn as_ref(&self) -> Directed<Bound<&T>> {
		match self {
			Directed::Start(b) => Directed::Start(match b {
				Bound::Included(b) => Bound::Included(b),
				Bound::Excluded(b) => Bound::Excluded(b),
				Bound::Unbounded => Bound::Unbounded,
			}),
			Directed::End(b) => Directed::End(match b {
				Bound::Included(b) => Bound::Included(b),
				Bound::Excluded(b) => Bound::Excluded(b),
				Bound::Unbounded => Bound::Unbounded,
			}),
		}
	}
}

impl<'a, T: Hash + PartialEnum> Hash for Directed<Bound<&'a T>> {
	fn hash<H: Hasher>(&self, h: &mut H) {
		match self {
			Directed::Start(b) => match b {
				Bound::Included(b) => b.hash(h),
				Bound::Excluded(b) => match b.succ() {
					Some(c) => c.hash(h),
					None => b.hash(h),
				},
				Bound::Unbounded => (),
			},
			Directed::End(b) => match b {
				Bound::Included(b) => b.hash(h),
				Bound::Excluded(b) => match b.pred() {
					Some(c) => c.hash(h),
					None => b.hash(h),
				},
				Bound::Unbounded => (),
			},
		}
	}
}

impl<T, U> PartialEq<Directed<Bound<&U>>> for Directed<Bound<&T>>
where
	T: Measure<U> + PartialOrd<U> + PartialEnum,
	U: PartialEnum,
{
	fn eq(&self, other: &Directed<Bound<&U>>) -> bool {
		self.partial_cmp(other) == Some(Ordering::Equal)
	}
}

impl<T> Eq for Directed<Bound<&T>> where T: Measure + Ord + PartialEnum {}

impl<T, U> PartialOrd<Directed<Bound<&U>>> for Directed<Bound<&T>>
where
	T: Measure<U> + PartialOrd<U> + PartialEnum,
	U: PartialEnum,
{
	fn partial_cmp(&self, other: &Directed<Bound<&U>>) -> Option<Ordering> {
		match self.bound_partial_cmp(other) {
			Some(BoundOrdering::Included(true)) => Some(Ordering::Equal),
			Some(BoundOrdering::Included(false)) => match other {
				Directed::Start(_) => Some(Ordering::Greater),
				Directed::End(_) => Some(Ordering::Less),
			},
			Some(BoundOrdering::Excluded(_)) => match other {
				Directed::Start(_) => Some(Ordering::Less),
				Directed::End(_) => Some(Ordering::Greater),
			},
			None => None,
		}
	}
}

impl<T> Ord for Directed<Bound<&T>>
where
	T: Measure + Ord + PartialEnum,
{
	fn cmp(&self, other: &Directed<Bound<&T>>) -> Ordering {
		match self.bound_cmp(other) {
			BoundOrdering::Included(true) => Ordering::Equal,
			BoundOrdering::Included(false) => match other {
				Directed::Start(_) => Ordering::Greater,
				Directed::End(_) => Ordering::Less,
			},
			BoundOrdering::Excluded(_) => match other {
				Directed::Start(_) => Ordering::Less,
				Directed::End(_) => Ordering::Greater,
			},
		}
	}
}

pub(crate) fn min_bound<'a, T: Measure + PartialOrd + PartialEnum>(
	a: Bound<&'a T>,
	b: Bound<&'a T>,
	start: bool,
) -> Bound<&'a T> {
	match direct_bound_partial_cmp(a, b, start) {
		Some(BoundOrdering::Included(_)) => {
			if start {
				b
			} else {
				a
			}
		}
		_ => {
			if start {
				a
			} else {
				b
			}
		}
	}
}

pub(crate) fn max_bound<'a, T: Measure + PartialOrd + PartialEnum>(
	a: Bound<&'a T>,
	b: Bound<&'a T>,
	start: bool,
) -> Bound<&'a T> {
	match direct_bound_partial_cmp(a, b, start) {
		Some(BoundOrdering::Included(_)) => {
			if start {
				a
			} else {
				b
			}
		}
		_ => {
			if start {
				b
			} else {
				a
			}
		}
	}
}
