use std::sync::Arc;

use super::super::api::Rect;
use crate::{
	ui::{api::Color, event::Event},
	vertexbuffer::types::UiInstance,
	RenderInput, RenderState,
};
use galactica_content::{Sprite, SpriteAutomaton};
use galactica_util::to_radians;
use rhai::ImmutableString;

#[derive(Debug, Clone)]
pub struct UiSprite {
	pub anim: SpriteAutomaton,
	pub name: ImmutableString,

	/// Sprite angle, in degrees
	angle: f32,

	/// If true, this sprite will be scaled to fit in its box without affecting aspect ratio.
	/// If false, this sprite will be stretched to fit in its box
	preserve_aspect: bool,

	rect: Rect,
	mask: Option<Arc<Sprite>>,
	color: Color,

	/// If true, ignore mouse events until click is released
	waiting_for_release: bool,
	has_mouse: bool,
	has_click: bool,
}

impl UiSprite {
	pub fn new(name: ImmutableString, sprite: Arc<Sprite>, rect: Rect) -> Self {
		Self {
			name,
			anim: SpriteAutomaton::new(sprite),
			rect,
			angle: 0.0,
			color: Color::new(1.0, 1.0, 1.0, 1.0),
			mask: None,
			has_mouse: false,
			has_click: false,
			waiting_for_release: false,
			preserve_aspect: false,
		}
	}

	pub fn set_mask(&mut self, mask: Option<Arc<Sprite>>) {
		self.mask = mask;
	}

	pub fn set_angle(&mut self, angle: f32) {
		self.angle = angle;
	}

	pub fn set_rect(&mut self, rect: Rect) {
		self.rect = rect;
	}

	pub fn set_color(&mut self, color: Color) {
		self.color = color;
	}

	pub fn set_preserve_aspect(&mut self, preserve_aspect: bool) {
		self.preserve_aspect = preserve_aspect;
	}

	pub fn push_to_buffer(&self, input: &RenderInput, state: &mut RenderState) {
		let mut rect = self
			.rect
			.to_centered(&state.window, input.ct.config.ui_scale);

		if self.preserve_aspect {
			let rect_aspect = rect.dim.x / rect.dim.y;
			let sprite_aspect = self.anim.get_sprite().aspect;

			// "wide rect" case => match height, reduce width
			if rect_aspect > sprite_aspect {
				let shrink = rect.dim.x - rect.dim.y * sprite_aspect;
				rect.dim.x -= shrink;

			// "tall rect" case => match width, reduce height
			} else if rect_aspect < sprite_aspect {
				let shrink = rect.dim.y - rect.dim.x / sprite_aspect;
				rect.dim.y -= shrink;
			}
		}

		let anim_state = self.anim.get_texture_idx();
		state.push_ui_buffer(UiInstance {
			position: rect.pos.into(),
			angle: to_radians(90.0 + self.angle),
			dim: rect.dim.into(),
			color: self.color.as_array(),
			texture_index: anim_state.texture_index(),
			texture_fade: anim_state.fade,
			mask_index: self
				.mask
				.as_ref()
				.map(|x| {
					let texture_b = x.get_first_frame(); // TODO: animate?
					[1, texture_b]
				})
				.unwrap_or([0, 0]),
		});
	}

	pub fn check_events(&mut self, input: &RenderInput, state: &mut RenderState) -> Event {
		let r = self
			.rect
			.to_centered(&state.window, input.ct.config.ui_scale);

		if self.waiting_for_release && self.has_mouse && !input.player.input.pressed_leftclick() {
			self.waiting_for_release = false;
		}

		if !self.waiting_for_release
			&& self.has_mouse
			&& !self.has_click
			&& input.player.input.pressed_leftclick()
		{
			self.has_click = true;
			return Event::MouseClick;
		}

		if self.has_mouse && self.has_click && !input.player.input.pressed_leftclick() {
			self.has_click = false;
			return Event::MouseRelease;
		}

		// Release mouse when cursor leaves box
		if self.has_click && !self.has_mouse {
			self.has_click = false;
			return Event::MouseRelease;
		}

		if r.contains_mouse(input, state) && !self.has_mouse {
			if input.player.input.pressed_leftclick() {
				// If we're holding click when the cursor enters,
				// don't trigger the `Click` event.
				self.waiting_for_release = true;
			}
			self.has_mouse = true;
			return Event::MouseHover;
		}

		if !r.contains_mouse(input, state) && self.has_mouse {
			self.waiting_for_release = false;
			self.has_mouse = false;
			return Event::MouseUnhover;
		}

		return Event::None;
	}

	pub fn step(&mut self, input: &RenderInput, _state: &mut RenderState) {
		self.anim.step(input.time_since_last_run);
	}
}