use embedded_graphics::{
	mono_font::{MonoTextStyle, ascii::FONT_10X20},
	pixelcolor::Rgb565,
	prelude::*,
	primitives::{PrimitiveStyle, Rectangle},
	text::Text,
};
use serde::{Deserialize, Serialize};

#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(rename_all = "lowercase")]
pub enum Screen {
	Clear([u8; 3]),
	Light(bool),
	Layout(Vec<Item>),
}

impl Drawable for Screen {
	type Color = Rgb565;
	type Output = ();

	fn draw<D>(&self, target: &mut D) -> Result<Self::Output, D::Error>
	where
		D: DrawTarget<Color = Self::Color>,
	{
		use Screen::*;
		match self {
			Clear([r, g, b]) => target.clear(Rgb565::new(*r, *g, *b)),
			Layout(items) => {
				for item in items {
					item.draw(target)?;
				}
				Ok(())
			}
			Light(_) => unreachable!("Light is handled outside of the draw method"),
		}
	}
}

#[derive(Clone, Debug, Default, Deserialize, Serialize)]
pub struct Item {
	pub x: i32,
	pub y: i32,
	pub width: Option<u32>,
	pub height: Option<u32>,
	pub fill: Option<[u8; 3]>,
	pub stroke: Option<[u8; 3]>,
	pub text: Option<String>,
}

impl Drawable for Item {
	type Color = Rgb565;
	type Output = ();

	fn draw<D>(&self, target: &mut D) -> Result<Self::Output, D::Error>
	where
		D: DrawTarget<Color = Self::Color>,
	{
		if let (Some(width), Some(height), Some(colour)) = (self.width, self.height, self.fill) {
			Rectangle::new(Point::new(self.x, self.y), Size::new(width, height))
				.into_styled(PrimitiveStyle::with_fill(Rgb565::new(
					colour[0], colour[1], colour[2],
				)))
				.draw(target)?;
		}

		if let (Some(text), Some(stroke)) = (self.text.as_deref(), self.stroke) {
			Text::new(
				text,
				Point::new(self.x, self.y),
				MonoTextStyle::new(&FONT_10X20, Rgb565::new(stroke[0], stroke[1], stroke[2])),
			)
			.draw(target)?;
		}

		Ok(())
	}
}
