use core_types::CacheHash;
use core_types::blending::BlendMode;
use core_types::color::Color;
use core_types::list::{Item, List};
use core_types::math::bbox::AxisAlignedBbox;
use dyn_any::DynAny;
use glam::DVec2;
/// The style of a brush.
#[derive(Clone, Debug, CacheHash, DynAny)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct BrushStyle {
	pub color: Color,
	pub diameter: f64,
	pub hardness: f64,
	pub flow: f64,
	pub spacing: f64, // Spacing as a fraction of the diameter.
	pub blend_mode: BlendMode,
}

impl Default for BrushStyle {
	fn default() -> Self {
		Self {
			color: Color::BLACK,
			diameter: 40.,
			hardness: 50.,
			flow: 100.,
			spacing: 50., // Percentage of diameter.
			blend_mode: BlendMode::Normal,
		}
	}
}

impl Eq for BrushStyle {}

impl PartialEq for BrushStyle {
	fn eq(&self, other: &Self) -> bool {
		self.color == other.color
			&& self.diameter.to_bits() == other.diameter.to_bits()
			&& self.hardness.to_bits() == other.hardness.to_bits()
			&& self.flow.to_bits() == other.flow.to_bits()
			&& self.spacing.to_bits() == other.spacing.to_bits()
			&& self.blend_mode == other.blend_mode
	}
}

/// A single sample of brush parameters across the brush stroke.
#[derive(Clone, Debug, PartialEq, core_types::CacheHash, DynAny)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct BrushInputSample {
	pub position: DVec2,
}

/// The parameters for a single stroke brush.
#[derive(Clone, Debug, PartialEq, core_types::CacheHash, Default, DynAny)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct BrushStroke {
	pub style: BrushStyle,
	pub trace: Vec<BrushInputSample>,
}

/// One Brush layer's full sequence of strokes, treated as a single rank-0 value rather than a frame of independent strokes.
#[derive(Default, Debug, Clone, PartialEq, CacheHash, DynAny)]
pub struct BrushTrace(pub List<BrushStroke>);

impl From<List<BrushStroke>> for BrushTrace {
	fn from(strokes: List<BrushStroke>) -> Self {
		Self(strokes)
	}
}

impl From<Vec<BrushStroke>> for BrushTrace {
	fn from(strokes: Vec<BrushStroke>) -> Self {
		Self(strokes.into_iter().map(Item::new_from_element).collect())
	}
}

impl BrushStroke {
	pub fn bounding_box(&self) -> AxisAlignedBbox {
		let radius = self.style.diameter / 2.;
		self.compute_blit_points()
			.iter()
			.map(|pos| AxisAlignedBbox {
				start: *pos + DVec2::new(-radius, -radius),
				end: *pos + DVec2::new(radius, radius),
			})
			.reduce(|a, b| a.union(&b))
			.unwrap_or(AxisAlignedBbox::ZERO)
	}

	pub fn compute_blit_points(&self) -> Vec<DVec2> {
		// We always travel in a straight line towards the next user input,
		// placing a blit point every time we travelled our spacing distance.
		let spacing_dist = self.style.spacing / 100. * self.style.diameter;

		let Some(first_sample) = self.trace.first() else {
			return Vec::new();
		};

		let mut cur_pos = first_sample.position;
		let mut result = vec![cur_pos];
		let mut dist_until_next_blit = spacing_dist;
		for sample in &self.trace[1..] {
			// Travel to the next sample.
			let delta = sample.position - cur_pos;
			let mut dist_left = delta.length();
			let unit_step = delta / dist_left;

			while dist_left >= dist_until_next_blit {
				// Take a step to the next blit point.
				cur_pos += dist_until_next_blit * unit_step;
				dist_left -= dist_until_next_blit;

				// Blit.
				result.push(cur_pos);
				dist_until_next_blit = spacing_dist;
			}

			// Take the partial step to land at the sample.
			dist_until_next_blit -= dist_left;
			cur_pos = sample.position;
		}

		result
	}
}
