use crate::{Color32, Direction, FRAC_PI_2, GradientAngle, PI, Position};

/// ## FromArray
/// Note: This is just a helper trait, in order to convert an array to Color32 directly.
/// It removes the annoying conversion stuff
///
/// I MIGHT open a PR about this 😋
pub trait FromArray<T>: Sized {
	/// ## [`from_array()`]
	/// Convert an array of u8 rgba to Color32.
	fn from_array(rgba: [u8; 4]) -> Self;
}

/// ### Helper
///
/// Implement `From<[u8; 4]>` for Color32
impl FromArray<[u8; 4]> for Color32 {
	#[inline]
	fn from_array(rgba: [u8; 4]) -> Self {
		Color32::from_rgba_premultiplied(rgba[0], rgba[1], rgba[2], rgba[3])
	}
}

/// ### Helper
///
/// Calculates the right angle (funny) for the gradient to start from
#[doc(hidden)]
#[inline]
pub(crate) fn calculate_angle(gradient_angle: GradientAngle) -> f32 {
	match gradient_angle {
		GradientAngle::Direction(direction) => match direction {
			Direction::TopDown => 0.0,                // 0		deg
			Direction::BottomUp => PI,                // 180	deg
			Direction::LeftToRight => FRAC_PI_2,      // 90		deg
			Direction::RightToLeft => PI + FRAC_PI_2, // 270	deg
		},
		GradientAngle::Degree(d) => d * (PI / 180.0), // Convert to radians
		GradientAngle::Radian(r) => {
			// Make sure radians are within 360 deg
			let rhs = PI * 2.0;
			let r = r % rhs;
			if r < 0.0 { r + rhs.abs() } else { r }
		}
	}
}

/// ### Helper
///
/// Calculates the position of starting for `conic_gradient()` and `radial_gradient()` exclusively.
#[doc(hidden)]
#[inline]
pub(crate) fn calculate_position(position: Option<Position>) -> (f32, f32) {
	match position.unwrap_or(Position::Center) {
		Position::TopLeft => (0.0_f32, 0.0_f32),
		Position::Top => (0.5_f32, 0.0_f32),
		Position::TopRight => (1.0_f32, 0.0_f32),
		Position::Left => (0.0_f32, 0.5_f32),
		Position::Center => (0.5_f32, 0.5_f32),
		Position::Right => (1.0_f32, 0.5_f32),
		Position::BottomLeft => (0.0_f32, 1.0_f32),
		Position::Bottom => (0.5_f32, 1.0_f32),
		Position::BottomRight => (1.0_f32, 1.0_f32),
		Position::Percentage(x, y) => (x as f32 / 100.0, y as f32 / 100.0),
	}
}

/// ### Helper
///
/// Linear Interpolate two colors
#[doc(hidden)]
#[inline]
fn lerp_color(c1: Color32, c2: Color32, t: f32) -> Color32 {
	#[inline(always)]
	fn lerp_c<F>(c1: Color32, c2: Color32, t: f32, c: F) -> u8
	where
		F: Fn(Color32) -> u8,
	{
		(c(c1) as f32 * (1.0 - t) + c(c2) as f32 * t) as u8
	}

	Color32::from_rgba_premultiplied(
		lerp_c(c1, c2, t, |c| c.r()),
		lerp_c(c1, c2, t, |c| c.g()),
		lerp_c(c1, c2, t, |c| c.b()),
		lerp_c(c1, c2, t, |c| c.a()),
	)
}

/// ### Helper
///
/// Resolves Option<u8> percentages into 0 to 1 bounds
#[doc(hidden)]
pub(crate) fn resolve_color_stops(colors: &[(Color32, Option<u8>)]) -> Vec<(Color32, f32)> {
	// No. of colors and init empty vec
	let len = colors.len();
	let mut resolved = vec![(Color32::TRANSPARENT, -1.0); len];

	// Set defined stops
	for (i, &(c, pct)) in colors.iter().enumerate() {
		resolved[i].0 = c;
		if let Some(p) = pct {
			resolved[i].1 = (p as f32 / 100.0).clamp(0.0, 1.0);
		} else if i == 0 {
			resolved[i].1 = 0.0;
		} else if i == len - 1 {
			resolved[i].1 = 1.0;
		} else {
			resolved[i].1 = 1.0 / colors.len() as f32;
		}
	}

	// Distribute undefined stops
	let mut i = 0;
	while i < len {
		if resolved[i].1 < 0.0 {
			let mut j = i;
			while j < len && resolved[j].1 < 0.0 {
				j += 1;
			}
			let start = resolved[i - 1].1;
			let end = resolved[j].1;
			let step = (end - start) / ((j - i + 1) as f32);
			resolved.iter_mut().enumerate().take(j).skip(i).for_each(|(idx, k)| {
				k.1 = start + step * ((idx - i + 1) as f32);
			});
			i = j;
		} else {
			i += 1;
		}
	}
	resolved
}

/// ### Helper
///
/// Sample pre resolved color stops
#[doc(hidden)]
#[inline]
pub(crate) fn sample_resolved_gradient(t: f32, stops: &[(Color32, f32)]) -> Color32 {
	if stops.len() == 1 || t <= stops[0].1 {
		return stops[0].0;
	}

	if t >= stops.last().unwrap().1 {
		return stops.last().unwrap().0;
	}

	for w_idx in 0..stops.len() - 1 {
		let (c1, t1) = stops[w_idx];
		let (c2, t2) = stops[w_idx + 1];
		if t >= t1 && t <= t2 {
			let range = t2 - t1;
			let factor = if range > 0.0 { (t - t1) / range } else { 0.0 };
			return lerp_color(c1, c2, factor);
		}
	}
	stops.last().unwrap().0
}
