use super::{furniture::Furniture, grid::Grid};
use crate::game;
use comfy::{
	draw_rect, draw_rect_outline, draw_sprite, error, random_i32, vec2, EngineContext,
	HashSet, RandomRange as _, Vec2, GREEN, PURPLE, RED, WHITE
};
use indexmap::IndexSet;

pub const SCALE: f32 = 4.0;

#[derive(Debug, PartialEq)]
enum RoomType {
	Kitchen,
	Bath,
	Toilett,
	LivingRoom,
	SleepingRoom
}

#[derive(Debug)]
struct Tile {
	pos: Vec2,
	size: Vec2,
	f: Furniture,
	z: i32
}

#[derive(Debug)]
pub struct Room {
	_room_type: RoomType,
	pub size: (u8, u8), //(width, height)
	pub grid: Grid,
	furnitures: Vec<Tile>
}

impl RoomType {
	pub fn random() -> Self {
		match random_i32(0, 4) {
			0 => RoomType::Kitchen,
			1 => RoomType::Bath,
			2 => RoomType::Toilett,
			3 => RoomType::LivingRoom,
			4 => RoomType::SleepingRoom,
			_ => panic!("Somehow you where unlucky and got a random number out of range")
		}
	}
}

impl Room {
	pub fn new(ctx: &mut EngineContext<'_>) -> Self {
		let room_type = RoomType::random();
		let size = Self::random_size(&room_type);

		let furnitures = Self::random_room_furniture(&room_type, size, ctx);

		Room {
			_room_type: room_type,
			size,
			grid: Self::create_grid(size.0, size.1),
			furnitures
		}
	}

	fn random_size(room_type: &RoomType) -> (u8, u8) {
		//Kitchen + Living Room 5-8
		//Bath + sleepingroom 4-6
		//Toilet 2-3
		match room_type {
			RoomType::Kitchen | RoomType::LivingRoom => (random_i32(5, 8) as u8, 3),
			RoomType::Bath | RoomType::SleepingRoom => (random_i32(4, 6) as u8, 3),
			RoomType::Toilett => (random_i32(2, 3) as u8, 3)
		}
	}

	fn random_room_furniture(
		room_type: &RoomType,
		(width, _height): (u8, u8),
		ctx: &mut EngineContext<'_>
	) -> Vec<Tile> {
		let mut furnitures = Vec::new();

		let mut empty_spots: IndexSet<u8> = (0 .. width).collect();

		fn random_empty_spot<T>(empty_spots: &mut IndexSet<T>) -> Option<T> {
			if empty_spots.is_empty() {
				return None;
			}
			let random_idx = usize::gen_range(0, empty_spots.len());
			empty_spots.swap_remove_index(random_idx)
		}
		fn random_appliance<T>(empty_spots: &mut Vec<T>) -> Option<T> {
			if empty_spots.is_empty() {
				return None;
			}
			let random_idx = usize::gen_range(0, empty_spots.len());
			Some(empty_spots.swap_remove(random_idx))
		}

		const SIDEBOARD_HEIGHT: f32 = 0.1;
		const STOVE_HEIGHT: f32 = 0.025;
		const SINK_HEIGHT: f32 = 0.5;

		#[allow(clippy::single_match)] // we'll add more stuff later
		match room_type {
			RoomType::Kitchen => {
				// in a kitchen, we always add a fridge
				let fridge_pos = u8::gen_range(0, 2) * (width - 1);
				empty_spots.swap_remove(&fridge_pos);
				furnitures.push(Tile {
					pos: vec2(fridge_pos as f32, 0.0),
					size: vec2(1.0, 2.0),
					f: Furniture::new("kitchen", "fridge", ctx),
					z: 0
				});

				// and we always add an oven
				let Some(oven_pos) = random_empty_spot(&mut empty_spots) else {
					error!("How can I not fit an oven in a kitchen?!?");
					return furnitures;
				};
				furnitures.push(Tile {
					pos: vec2(oven_pos as f32, 0.0),
					size: vec2(1.0, 1.0),
					f: Furniture::new("kitchen", "oven", ctx),
					z: 0
				});

				// there's always sideboard above the oven with a stove
				furnitures.push(Tile {
					pos: vec2(oven_pos as f32, 1.0),
					size: vec2(1.0, SIDEBOARD_HEIGHT),
					f: Furniture::new("kitchen", "sideboard_1", ctx),
					z: 1
				});
				furnitures.push(Tile {
					pos: vec2(oven_pos as f32, 1.0 + SIDEBOARD_HEIGHT),
					size: vec2(1.0, STOVE_HEIGHT),
					f: Furniture::new("kitchen", "stove", ctx),
					z: 0
				});

				// and we always add a drawer that houses a sink
				let Some(sink_pos) = random_empty_spot(&mut empty_spots) else {
					error!("How can I not fit a sink in a kitchen?!?");
					return furnitures;
				};
				furnitures.push(Tile {
					pos: vec2(sink_pos as f32, 0.0),
					size: vec2(1.0, 1.0),
					f: Furniture::new("kitchen", "drawer_cupboard", ctx),
					z: 0
				});

				// there's always sideboard above that drawer with a sink **behind** it
				furnitures.push(Tile {
					pos: vec2(sink_pos as f32, 1.0),
					size: vec2(1.0, SINK_HEIGHT),
					f: Furniture::new("kitchen", "sink", ctx),
					z: 0
				});
				furnitures.push(Tile {
					pos: vec2(sink_pos as f32, 1.0),
					size: vec2(1.0, SIDEBOARD_HEIGHT),
					f: Furniture::new("kitchen", "sideboard_1", ctx),
					z: 1
				});

				// the current list of empty spots is the same list we can use to place
				// on-the-counter appliances later
				let mut empty_spots_clone = empty_spots.clone();

				// build a list of the remaining kitchen appliances. we only want them
				// included once, most kitchens don't contain two washing machines etc
				let mut remaining_appliances: IndexSet<&'static str> =
					["dishwasher", "dryer", "minifridge", "washing_machine"]
						.into_iter()
						.collect();

				// let's add at most half of the remaining positions as big appliances
				for _ in 0 .. empty_spots.len() / 2 {
					let Some(asset) = random_empty_spot(&mut remaining_appliances) else {
						break;
					};
					let Some(spot) = random_empty_spot(&mut empty_spots) else {
						error!("WTF I shouldn't've used more than half of the available spots");
						return furnitures;
					};
					furnitures.push(Tile {
						pos: vec2(spot as f32, 0.0),
						size: vec2(1.0, 1.0),
						f: Furniture::new("kitchen", asset, ctx),
						z: 0
					});
					furnitures.push(Tile {
						pos: vec2(spot as f32, 1.0),
						size: vec2(1.0, SIDEBOARD_HEIGHT),
						f: Furniture::new("kitchen", "sideboard_1", ctx),
						z: 1
					});
				}

				// and fill the remainder with drawers
				while !empty_spots.is_empty() {
					let asset = match u8::gen_range(0, 2) {
						0 => "drawer",
						1 => "drawer_cupboard",
						_ => unreachable!()
					};
					let Some(spot) = random_empty_spot(&mut empty_spots) else {
						error!("WTF I should still have spots available");
						return furnitures;
					};
					furnitures.push(Tile {
						pos: vec2(spot as f32, 0.0),
						size: vec2(1.0, 1.0),
						f: Furniture::new("kitchen", asset, ctx),
						z: 0
					});
					furnitures.push(Tile {
						pos: vec2(spot as f32, 1.0),
						size: vec2(1.0, SIDEBOARD_HEIGHT),
						f: Furniture::new("kitchen", "sideboard_1", ctx),
						z: 1
					});
				}

				// build a list of on-the-counter kitchen appliances. we only want them
				// included once, most kitchens don't contain two toasters etc
				let mut remaining_appliances: Vec<(&'static str, f32, f32)> = [
					("blender", 0.3, 0.45),
					("kettle", 0.3, 0.4),
					("toaster", 0.5, 0.25)
				]
				.into_iter()
				.collect();

				// and then we fill like half the counter with appliances
				for _ in 0 .. empty_spots_clone.len() / 2 {
					let Some((asset, asset_w, asset_h)) =
						random_appliance(&mut remaining_appliances)
					else {
						break;
					};
					let Some(spot) = random_empty_spot(&mut empty_spots_clone) else {
						error!("WTF I shouldn't've used more than half of the available spots");
						return furnitures;
					};
					furnitures.push(Tile {
						pos: vec2(spot as f32 + 0.5, 1.0 + SIDEBOARD_HEIGHT),
						size: vec2(asset_w, asset_h),
						f: Furniture::new("kitchen", asset, ctx),
						z: 0
					});
				}
			},

			_ => {}
		}

		furnitures
	}

	fn create_grid(width: u8, height: u8) -> Grid {
		error!("START GRID CREATION!");
		let left_border = width as f32 / 2.0;
		let lower_border = height as f32 / 2.0;

		//Lower Cable
		let lower_cable_y = height as f32 / 6.0;
		let mut current_x = -0.5;

		let mut nodes = Vec::new();
		let max_offset = ((width / 2) as i32).max(1);

		while current_x < width as f32 {
			nodes.push(vec2(
				(current_x - left_border) * SCALE,
				(lower_cable_y - lower_border) * SCALE
			));
			current_x += random_i32(1, max_offset) as f32;
		}
		current_x = width as f32 + 0.5;
		nodes.push(vec2(
			(current_x - left_border) * SCALE,
			(lower_cable_y - lower_border) * SCALE
		));

		let mut connections = Vec::new();
		for i in 1 .. nodes.len() {
			connections.push((i - 1, i));
		}

		//Lamps
		let upper_cable_y = height as f32 - 0.25;
		let max_lamps = (width as f32 / 2.5).round() as i32;
		let lamp_amount = random_i32(1, max_lamps + 1);
		let node_indices: HashSet<usize> = (0 .. lamp_amount)
			.map(|_| random_i32(1, nodes.len() as i32 - 1) as usize)
			.collect();

		let last_lower_node_index = nodes.len();
		for (i, index) in node_indices.iter().enumerate() {
			nodes.push(vec2(
				nodes.get(*index).unwrap().x,
				(upper_cable_y - lower_border) * SCALE
			));
			connections.push((*index, last_lower_node_index + i));
		}

		Grid::new(nodes, connections)
	}

	pub fn draw(&self) {
		let (width, height) = self.size;

		draw_rect(
			vec2(0.0, 0.0),
			vec2(width as f32 * SCALE, height as f32 * SCALE),
			PURPLE,
			game::ZLayer::MapMax as i32 - 2
		);
		draw_rect_outline(
			vec2(0.0, 0.0),
			vec2(width as f32 * SCALE, height as f32 * SCALE),
			0.3,
			RED,
			game::ZLayer::MapMax as i32 - 1
		);

		for tile in &self.furnitures {
			let mut pos = tile.pos - vec2(width as f32 / 2.0, height as f32 / 2.0);
			pos += tile.size * 0.5;

			if let Some(texture) = tile.f.get_human_texture_handle() {
				draw_sprite(
					texture,
					pos * SCALE,
					WHITE,
					game::ZLayer::MapMax as i32 + tile.z,
					tile.size * SCALE
				);
			} else {
				draw_rect_outline(
					pos * SCALE,
					tile.size * SCALE,
					0.3,
					GREEN,
					game::ZLayer::MapMax as i32 + tile.z
				);
			}
		}

		self.grid.draw();
	}
}
