// TODO remove this
#![allow(dead_code)]

use crate::assets::ASSETS;
use comfy::{info, IVec2, TextureHandle, UVec2};
use std::collections::HashMap;

pub enum MovementCost {
	/// No movement possible - cost infinitely high.
	Infinite,
	/// There is a path for this movement - movement is cheap.
	Path,
	/// There is no path and no obstacle.
	Default,
	/// There is an obstacle (i.e. fence) - movement is expensive.
	Obstacle
}

#[derive(Clone, Copy, Debug)]
pub enum Tile {
	Grass,

	Path {
		left: bool,
		right: bool,
		top: bool,
		bottom: bool
	},

	Fence {
		left: bool,
		right: bool,
		top: bool,
		bottom: bool
	},

	House {
		texture: TextureHandle,
		door: bool
	}
}

impl Tile {
	pub fn textures(&self) -> Vec<TextureHandle> {
		match self {
			Self::Grass => vec![ASSETS.overworld.grass],

			Self::Path {
				left,
				right,
				top,
				bottom
			} => {
				let path_texture = match (left, right, top, bottom) {
					(true, true, false, false) => ASSETS.overworld.path_horiz,
					(false, false, true, true) => ASSETS.overworld.path_vert,

					(true, false, true, false) => ASSETS.overworld.path_top_left,
					(true, false, false, true) => ASSETS.overworld.path_bottom_left,
					(false, true, true, false) => ASSETS.overworld.path_top_right,
					(false, true, false, true) => ASSETS.overworld.path_bottom_right,

					(true, true, true, false) => ASSETS.overworld.path_top_left_right,
					(true, true, false, true) => ASSETS.overworld.path_bottom_left_right,
					(true, false, true, true) => ASSETS.overworld.path_top_left_bottom,
					(false, true, true, true) => ASSETS.overworld.path_top_right_bottom,

					(true, true, true, true) => ASSETS.overworld.path_crossing,

					(true, false, false, false)
					| (false, true, false, false)
					| (false, false, true, false)
					| (false, false, false, true) => panic!("We don't have no dead ends"),
					(false, false, false, false) => panic!("I think you meant grass?!?")
				};

				vec![ASSETS.overworld.grass, path_texture]
			},

			Self::Fence {
				left,
				right,
				top,
				bottom
			} => {
				let path_texture = match (left, right, top, bottom) {
					(true, true, false, false) => ASSETS.overworld.fence_horiz,
					(false, false, true, true) => ASSETS.overworld.fence_vert,

					(true, false, true, false) => ASSETS.overworld.fence_top_left,
					(true, false, false, true) => ASSETS.overworld.fence_bottom_left,
					(false, true, true, false) => ASSETS.overworld.fence_top_right,
					(false, true, false, true) => ASSETS.overworld.fence_bottom_right,

					(true, true, true, false)
					| (true, true, false, true)
					| (true, false, true, true)
					| (false, true, true, true) => unimplemented!(),

					(true, true, true, true) => unimplemented!(),

					(true, false, false, false)
					| (false, true, false, false)
					| (false, false, true, false)
					| (false, false, false, true) => panic!("We don't have no dead ends"),
					(false, false, false, false) => panic!("I think you meant grass?!?")
				};

				vec![ASSETS.overworld.grass, path_texture]
			},

			Self::House { texture, .. } => {
				vec![ASSETS.overworld.grass, *texture]
			}
		}
	}

	pub fn can_stand_inside(&self) -> bool {
		#[allow(clippy::match_like_matches_macro)] // I believe this is better readable
		match self {
			Self::House { door: false, .. } => false,
			_ => true
		}
	}
	pub fn movement_cost_left(&self) -> MovementCost {
		match self {
			Self::Grass => MovementCost::Default,
			Self::Path { left: true, .. } => MovementCost::Path,
			Self::Path { left: false, .. } => MovementCost::Default,
			Self::Fence { left: false, .. } => MovementCost::Obstacle,
			_ => MovementCost::Infinite
		}
	}
	pub fn movement_cost_right(&self) -> MovementCost {
		match self {
			Self::Grass => MovementCost::Default,
			Self::Path { right: true, .. } => MovementCost::Path,
			Self::Path { right: false, .. } => MovementCost::Default,
			Self::Fence { right: false, .. } => MovementCost::Obstacle,
			_ => MovementCost::Infinite
		}
	}
	pub fn movement_cost_up(&self) -> MovementCost {
		match self {
			Self::Grass => MovementCost::Default,
			Self::Path { top: true, .. } => MovementCost::Path,
			Self::Path { top: false, .. } => MovementCost::Default,
			Self::Fence { top: false, .. } => MovementCost::Obstacle,
			_ => MovementCost::Infinite
		}
	}
	pub fn movement_cost_down(&self) -> MovementCost {
		match self {
			Self::Grass => MovementCost::Default,
			Self::Path { bottom: true, .. } => MovementCost::Path,
			Self::Path { bottom: false, .. } => MovementCost::Default,
			Self::Fence { bottom: false, .. } => MovementCost::Obstacle,
			Self::House { door: true, .. } => MovementCost::Path,
			_ => MovementCost::Infinite
		}
	}
	pub fn can_enter_house(&self) -> bool {
		#[allow(clippy::match_like_matches_macro)] // I believe this is better readable
		match self {
			Self::House { door: true, .. } => true,
			_ => false
		}
	}
}

/// The size of a chunk (both width and height). This value squared gives the amount of
/// tiles in the chunk.
const CHUNK_SIZE: u32 = 50;

/// Chunks
#[derive(Debug)]
#[allow(dead_code)]
pub struct Chunk {
	/// All tiles within this chunk.
	tiles: [Tile; (CHUNK_SIZE * CHUNK_SIZE) as usize],
	/// All paths that leave this chunk on the left hand side.
	paths_left: Vec<u32>,
	/// All paths that leave this chunk on the right hand side.
	paths_right: Vec<u32>,
	/// All paths that leave this chunk on the bottom side.
	paths_bottom: Vec<u32>,
	/// All paths that leave this chunk on the top side.
	paths_top: Vec<u32>
}

impl Chunk {
	pub fn generate_chunk() -> Self {
		info!("gen chunk");
		// TODO real worldgen
		// for the time being we just copy this pre-made house block into the chunk

		fn house(texture: TextureHandle) -> Tile {
			Tile::House {
				texture,
				door: false
			}
		}

		let path_horiz = Tile::Path {
			left: true,
			right: true,
			top: false,
			bottom: false
		};
		let path_vert = Tile::Path {
			left: false,
			right: false,
			top: true,
			bottom: true
		};
		let path_crossing = Tile::Path {
			left: true,
			right: true,
			top: true,
			bottom: true
		};
		let fence_horiz = Tile::Fence {
			left: true,
			right: true,
			top: false,
			bottom: false
		};
		let fence_vert = Tile::Fence {
			left: false,
			right: false,
			top: true,
			bottom: true
		};
		let grass = Tile::Grass;

		let block = [
			[
				path_horiz,
				path_horiz,
				path_horiz,
				path_horiz,
				path_horiz,
				path_horiz,
				path_horiz,
				path_horiz,
				path_horiz,
				path_horiz,
				path_horiz,
				path_crossing
			],
			[
				Tile::Fence {
					left: false,
					right: true,
					top: false,
					bottom: true
				},
				fence_horiz,
				fence_horiz,
				fence_horiz,
				fence_horiz,
				fence_horiz,
				fence_horiz,
				fence_horiz,
				fence_horiz,
				fence_horiz,
				Tile::Fence {
					left: true,
					right: false,
					top: false,
					bottom: true
				},
				path_vert
			],
			[
				fence_vert,
				grass,
				grass,
				grass,
				house(ASSETS.overworld.house_roof_top),
				grass,
				grass,
				grass,
				grass,
				grass,
				fence_vert,
				path_vert
			],
			[
				fence_vert,
				grass,
				grass,
				house(ASSETS.overworld.house_roof_mid_left),
				house(ASSETS.overworld.house_mid_window),
				house(ASSETS.overworld.house_roof_mid_right),
				grass,
				grass,
				grass,
				grass,
				fence_vert,
				path_vert
			],
			[
				fence_vert,
				grass,
				house(ASSETS.overworld.house_roof_left),
				house(ASSETS.overworld.house_mid_window),
				house(ASSETS.overworld.house_mid),
				house(ASSETS.overworld.house_mid_window),
				house(ASSETS.overworld.house_roof_right),
				grass,
				grass,
				grass,
				fence_vert,
				path_vert
			],
			[
				fence_vert,
				grass,
				house(ASSETS.overworld.house_bottom_left),
				Tile::House {
					door: true,
					texture: ASSETS.overworld.house_bottom_door
				},
				house(ASSETS.overworld.house_bottom_window),
				house(ASSETS.overworld.house_bottom_window),
				house(ASSETS.overworld.house_bottom_right),
				grass,
				grass,
				grass,
				fence_vert,
				path_vert
			],
			[
				fence_vert,
				grass,
				grass,
				Tile::Path {
					left: false,
					right: true,
					top: true,
					bottom: false
				},
				path_horiz,
				path_horiz,
				path_horiz,
				path_horiz,
				path_horiz,
				path_horiz,
				fence_vert,
				Tile::Path {
					left: true,
					right: false,
					top: true,
					bottom: true
				}
			],
			[
				fence_vert, grass, grass, grass, grass, grass, grass, grass, grass,
				grass, fence_vert, path_vert
			],
			[
				fence_vert, grass, grass, grass, grass, grass, grass, grass, grass,
				grass, fence_vert, path_vert
			],
			[
				Tile::Fence {
					left: false,
					right: true,
					top: true,
					bottom: false
				},
				fence_horiz,
				fence_horiz,
				fence_horiz,
				fence_horiz,
				fence_horiz,
				fence_horiz,
				fence_horiz,
				fence_horiz,
				fence_horiz,
				Tile::Fence {
					left: true,
					right: false,
					top: true,
					bottom: false
				},
				path_vert
			]
		];

		// and then we copy this pre-made block into our chunk
		let mut tiles = [Tile::Grass; (CHUNK_SIZE * CHUNK_SIZE) as usize];
		for y in 0 .. CHUNK_SIZE {
			for x in 0 .. CHUNK_SIZE {
				let row = block[y as usize % block.len()];
				tiles[(y * CHUNK_SIZE + x) as usize] = row[x as usize % row.len()];
			}
		}
		Self {
			tiles,
			paths_left: vec![],
			paths_right: vec![],
			paths_bottom: vec![],
			paths_top: vec![]
		}
	}

	pub fn get_tile(&self, local_chunk_coords: UVec2) -> Option<&Tile> {
		self.tiles
			.get((local_chunk_coords.y * CHUNK_SIZE + local_chunk_coords.x) as usize)
	}

	/// iterate over all tiles and its local chunk coords
	pub fn iter_tiles(&self) -> impl Iterator<Item = (UVec2, &Tile)> {
		self.tiles.iter().enumerate().map(|(i, tile)| {
			let i = i as u32;
			(UVec2::new(i % CHUNK_SIZE, i / CHUNK_SIZE), tile)
		})
	}
}

#[derive(Debug, Default)]
pub struct Overworld {
	chunks: HashMap<IVec2, Chunk>,

	pub sound_playing: bool
}

fn world_to_chunk_and_local_coords(world_coords: IVec2) -> (IVec2, UVec2) {
	let chunk_coords = IVec2 {
		x: world_coords.x.div_euclid(CHUNK_SIZE as _),
		y: world_coords.y.div_euclid(CHUNK_SIZE as _)
	};
	let local_chunk_coords = UVec2 {
		x: world_coords.x.rem_euclid(CHUNK_SIZE as _) as _,
		y: world_coords.y.rem_euclid(CHUNK_SIZE as _) as _
	};
	(chunk_coords, local_chunk_coords)
}

impl Overworld {
	/// Return a [`Tile`] at the given world coordinates, or `None` if that tile has not
	/// been generated yet. Uses engine/world cordinates.
	pub fn get_tile(&self, world_coords: IVec2) -> Option<&Tile> {
		let mut coords = world_coords;
		coords.y *= -1;
		self.get_tile_private(coords)
	}

	fn get_tile_private(&self, world_coords: IVec2) -> Option<&Tile> {
		let (chunk_coords, local_chunk_coords) =
			world_to_chunk_and_local_coords(world_coords);

		let chunk = self.chunks.get(&chunk_coords)?;
		chunk.get_tile(local_chunk_coords)
	}

	/// Return a [`Tile`] at the given world coordinates. Generates tiles if necessary.
	/// Uses engine/world cordinates.
	pub fn get_or_generate_tile(&mut self, world_coords: IVec2) -> &Tile {
		let mut coords = world_coords;
		coords.y *= -1;
		self.get_or_generate_tile_private(coords)
	}

	fn get_or_generate_tile_private(&mut self, world_coords: IVec2) -> &Tile {
		let (chunk_coords, local_chunk_coords) =
			world_to_chunk_and_local_coords(world_coords);

		let chunk = self
			.chunks
			.entry(chunk_coords)
			.or_insert_with(Chunk::generate_chunk);
		chunk.get_tile(local_chunk_coords).unwrap()
	}

	/// Iterate over all generated tiles in all generated chunks and their engine/world
	/// cordinates.
	pub fn iter_tiles(&self) -> impl Iterator<Item = (IVec2, &Tile)> {
		self.iter_tiles_private().map(|(coords, tile)| {
			let mut w_coords = coords;
			w_coords.y *= -1;
			(w_coords, tile)
		})
	}

	/// Iterate over all generated tiles in all generated chunks.
	fn iter_tiles_private(&self) -> impl Iterator<Item = (IVec2, &Tile)> {
		self.chunks.iter().flat_map(|(chunk_coords, chunk)| {
			chunk.iter_tiles().map(|(local_coords, tile)| {
				// never fail because chunksize fits always into i32
				let local_coords: IVec2 = local_coords.try_into().unwrap();
				(local_coords + (*chunk_coords * CHUNK_SIZE as i32), tile)
			})
		})
	}
}
