/// Represents how a single tile location should be matched when evaluating a rule
#[derive(Ord, PartialOrd, Eq, PartialEq, Hash, Debug, Default, Copy, Clone)]
pub enum TileStatus {
	/// This tile will always match, regardless of the input tile
	#[default]
	Ignore,
	/// This tile will only match when there is no input tile (`None`)
	Nothing,
	/// This tile will always match as long as the tile exists (`Option::is_some`)
	Anything,
	/// This tile will match as long as the input tile exists and the input value is the same as this value
	Is(i32),
	/// This tile will match as long as the input tile exists and the input value is anything other than this value
	IsNot(i32),
}

impl PartialEq<Option<i32>> for TileStatus {
	fn eq(&self, other: &Option<i32>) -> bool {
		match self {
			Self::Ignore => true,
			Self::Nothing => other.is_none(),
			Self::Anything => other.is_some(),
			Self::Is(value) => &Some(*value) == other,
			Self::IsNot(value) => &Some(*value) != other,
		}
	}
}

impl TileStatus {
    #[deprecated(since = "0.2.0", note = "Use `TileStatus::into` directly instead")]
	pub fn to_ldtk_value(self) -> i64 {
        self.into_tile() as i64
	}

    #[deprecated(since = "0.2.0", note = "Use `TileStatus::from` directly instead")]
	pub fn from_ldtk_value(value: i64) -> Self {
        Self::from(value)
	}
}


/// Holds a grid of raw input data, as a more ideal format for interop and storage
#[repr(transparent)]
pub struct TileLayout(pub [Option<i32>; 9]);

impl TileLayout {
	/// Create a 1x1 grid of tile data
	pub fn single(value: i32) -> Self {
		TileLayout([None, None, None, None, Some(value), None, None, None, None])
	}

	/// Construct a filled 3x3 grid of tile data
	pub fn filled(values: [i32; 9]) -> Self {
		TileLayout(values.map(Some))
	}

	/// Construct a filled 3x3 grid of identical tile data
	pub fn spread(value: i32) -> Self {
		TileLayout([Some(value); 9])
	}

	/// Filter the layout data so that it only contains the tiles surrounding the target tile. This
    /// means that the array index of every entry before the center point will match the original data
    /// array, but ever entry after the center point will have its index shifted down by 1
    ///
    /// The main utility of this is to perform set operations on every tile _other_ than the target tile.
	///
	/// ## Examples
	///
	/// ```
	/// # use micro_autotile::TileLayout;
	/// let layout = TileLayout::single(123);
	/// let has_any_surrounding_tiles = layout.surrounding()
	///   .iter()
	///   .any(|tile| tile.is_some());
	///
	/// assert_eq!(has_any_surrounding_tiles, false);
	/// ```
	pub fn surrounding(&self) -> [Option<i32>; 8] {
		[
			self.0[0], self.0[1], self.0[2], self.0[3], self.0[5], self.0[6], self.0[7], self.0[8],
		]
	}
}

/// Holds the evaluation rules for a 3x3 grid of tiles. A 1x1 grid of tile matchers
/// can be created by providing an array of `TileStatus` structs that are all `TileStatus::Ignore`,
/// except for the value in the fifth position
///
/// e.g.
///
/// ```
/// # use micro_autotile::{TileMatcher, TileStatus};
/// let matcher = TileMatcher([
///   TileStatus::Ignore, TileStatus::Ignore, TileStatus::Ignore,
///   TileStatus::Ignore, TileStatus::Anything, TileStatus::Ignore,
///   TileStatus::Ignore, TileStatus::Ignore, TileStatus::Ignore,
/// ]);
/// ```
#[derive(Clone, Debug, Default)]
#[repr(transparent)]
pub struct TileMatcher(pub [TileStatus; 9]);

impl TileMatcher {
	/// Create a 1x1 matcher, where the target tile must be the supplied `value`
	pub const fn single(value: i32) -> Self {
		Self([
			TileStatus::Ignore,
			TileStatus::Ignore,
			TileStatus::Ignore,
			TileStatus::Ignore,
			TileStatus::Is(value),
			TileStatus::Ignore,
			TileStatus::Ignore,
			TileStatus::Ignore,
			TileStatus::Ignore,
		])
	}

	/// Create a 1x1 matcher, with any rule for the target tile
	pub const fn single_match(value: TileStatus) -> Self {
		Self([
			TileStatus::Ignore,
			TileStatus::Ignore,
			TileStatus::Ignore,
			TileStatus::Ignore,
			value,
			TileStatus::Ignore,
			TileStatus::Ignore,
			TileStatus::Ignore,
			TileStatus::Ignore,
		])
	}

	/// Check if the given input layout of tile data conforms to this matcher
	pub fn matches(&self, layout: &TileLayout) -> bool {
		self.0
			.iter()
			.zip(layout.0.iter())
			.all(|(status, reality)| *status == *reality)
	}

	/// Load data from an LDTK JSON file. Currently supports 1x1 and 3x3 matchers.
	/// Other sizes of matcher will result in `None`
	pub fn from_ldtk_array(value: Vec<i64>) -> Option<Self> {
		if value.len() == 1 {
			let tile = value[0];
			Some(Self::single_match(TileStatus::from(tile)))
		} else if value.len() == 9 {
			Some(TileMatcher(
				[
					value[0], value[1], value[2], value[3], value[4], value[5], value[6], value[7],
					value[8],
				]
				.map(TileStatus::from),
			))
		} else {
			None
		}
	}
}