use super::{
	room::{Room, SCALE},
	Grid
};
use comfy::{
	delta, draw_circle, draw_sprite, is_key_down, is_key_pressed, main_camera_mut,
	texture_id, vec2, KeyCode, Vec2, RED, WHITE
};
use log::error;
use std::collections::HashSet;

#[derive(Debug)]
pub struct Player {
	position: Vec2,
	speed: f32,
	last_speed: f32,
	current_acceleration: f32,
	movement_time: f32,
	connection: usize,
	next_connections: Vec<usize>,
	pub successfull_attack: bool
}

impl Player {
	pub fn new(room: &Room) -> Self {
		Player {
			position: vec2(
				((0.25) - room.size.0 as f32 / 2.0) * SCALE,
				room.grid.nodes.first().unwrap().y
			),
			speed: 0.0,
			last_speed: 0.0,
			current_acceleration: 0.0,
			movement_time: 0.0,
			connection: 0,
			next_connections: vec![1],
			successfull_attack: false
		}
	}

	pub fn draw(&self) {
		//draw_circle(self.position, 0.5, RED, 0);
		draw_sprite(
			texture_id("ghost_house"),
			self.position,
			WHITE,
			5,
			vec2(1.25, 1.25)
		);
	}

	pub fn update(&mut self, room: &Room) {
		let grid = &room.grid;
		let allowed_movement = get_allowed_movement(self, grid);

		move_player(self, allowed_movement);

		if !on_current_connection(self, grid) && !update_connections(self, grid) {
			snap_to_closest_node(self, grid);
		}

		attack_human_routine(self, room);
	}

	pub fn is_moving_to_right_room(&self, room: &Room) -> bool {
		self.position.x > (room.size.0 as f32 / 2.0) * SCALE
	}

	pub fn is_moving_to_left_room(&self, room: &Room) -> bool {
		self.position.x < -(room.size.0 as f32 / 2.0) * SCALE
	}

	pub fn reset_on_room(&mut self, room: &Room, place_left: bool) {
		let offset = 0.1;
		let x = if place_left {
			(offset - room.size.0 as f32 / 2.0) * SCALE
		} else {
			(room.size.0 as f32 / 2.0 - offset) * SCALE
		};

		self.position = vec2(x, room.grid.nodes.first().unwrap().y);

		if place_left {
			self.connection = 0;
			self.next_connections = vec![1];
		} else {
			let mut current_index = 0;
			for (i, node_pos) in room.grid.nodes.iter().enumerate().rev() {
				if in_node_range(
					&vec2((room.size.0 as f32 / 2.0 + 0.5) * SCALE, self.position.y),
					node_pos,
					1.0
				) {
					current_index = i;
				}
			}

			let connction_index = room
				.grid
				.connections
				.iter()
				.enumerate()
				.find(|(_i, (_node_1, node_2))| *node_2 == current_index)
				.map(|(i, _)| i)
				.unwrap();

			self.connection = connction_index;
			self.next_connections = vec![connction_index - 1];
		}
	}

	pub fn can_see_metal(&self, threshold: f32) -> bool {
		self.current_acceleration > threshold
	}
}

fn move_player(player: &mut Player, allowed_movement: (bool, bool, bool, bool)) {
	let (
		allow_up_movement,
		allow_down_movement,
		allow_left_movement,
		allow_right_movement
	) = allowed_movement;

	if is_key_down(KeyCode::Up)
		|| is_key_down(KeyCode::Down)
		|| is_key_down(KeyCode::Left)
		|| is_key_down(KeyCode::Right)
	{
		player.movement_time += delta();
		player.speed = calc_player_speed(player.movement_time);
		player.speed = player.speed.clamp(0.0, 10.0);
	} else {
		player.speed = 0.0;
		player.last_speed = 0.0;
		player.movement_time = 0.0;
	}

	player.current_acceleration = (player.speed - player.last_speed).abs();

	if allow_up_movement && is_key_down(KeyCode::Up) {
		player.position += vec2(0.0, player.speed) * delta();
	}

	if allow_down_movement && is_key_down(KeyCode::Down) {
		player.position += vec2(0.0, -player.speed) * delta();
	}

	if allow_right_movement && is_key_down(KeyCode::Right) {
		player.position += vec2(player.speed, 0.0) * delta();
	}

	if allow_left_movement && is_key_down(KeyCode::Left) {
		player.position += vec2(-player.speed, 0.0) * delta();
	}

	player.last_speed = player.speed;
}

fn calc_player_speed(time: f32) -> f32 {
	//3x^2-2x^3; x=t
	let t = time * 20.0;
	3.0 * t * t - 2.0 * t * t
}

//(UP, DOWN, LEFT, RIGHT)
fn get_allowed_movement(player: &Player, grid: &Grid) -> (bool, bool, bool, bool) {
	let (conn_i1, conn_i2) = grid.connections.get(player.connection).unwrap();
	let node_1 = grid.nodes.get(*conn_i1).unwrap();
	let node_2 = grid.nodes.get(*conn_i2).unwrap();

	let range = 0.25;

	if in_node_range(&player.position, node_1, range)
		|| in_node_range(&player.position, node_2, range)
	{
		(true, true, true, true)
	} else {
		let (
			mut allow_up_movement,
			mut allow_down_movement,
			mut allow_left_movement,
			mut allow_right_movement
		) = get_allowed_connection_movement(&player.position, node_1, node_2);

		for conn in &player.next_connections {
			let (next_conn_i1, next_conn_i2) = grid.connections.get(*conn).unwrap();
			let next_node_1 = grid.nodes.get(*next_conn_i1).unwrap();
			let next_node_2 = grid.nodes.get(*next_conn_i2).unwrap();

			let (
				next_allow_up_movement,
				next_allow_down_movement,
				next_allow_left_movement,
				next_allow_right_movement
			) = get_allowed_connection_movement(&player.position, next_node_1, next_node_2);

			(
				allow_up_movement,
				allow_down_movement,
				allow_left_movement,
				allow_right_movement
			) = (
				allow_up_movement || next_allow_up_movement,
				allow_down_movement || next_allow_down_movement,
				allow_left_movement || next_allow_left_movement,
				allow_right_movement || next_allow_right_movement
			);
		}
		(
			allow_up_movement,
			allow_down_movement,
			allow_left_movement,
			allow_right_movement
		)
	}
}

//(UP, DOWN, LEFT, RIGHT)
fn get_allowed_connection_movement(
	player_position: &Vec2,
	node_1: &Vec2,
	node_2: &Vec2
) -> (bool, bool, bool, bool) {
	let allow_left_movement = {
		if node_1.x <= node_2.x {
			node_1.x < player_position.x
				&& player_position.x <= node_2.x
				&& player_position.y == node_1.y
		} else {
			node_2.x < player_position.x
				&& player_position.x <= node_1.x
				&& player_position.y == node_1.y
		}
	};

	let allow_right_movement = {
		if node_1.x <= node_2.x {
			node_1.x <= player_position.x
				&& player_position.x < node_2.x
				&& player_position.y == node_1.y
		} else {
			node_2.x <= player_position.x
				&& player_position.x < node_1.x
				&& player_position.y == node_1.y
		}
	};

	let allow_up_movement = {
		if node_1.y <= node_2.y {
			node_1.y <= player_position.y
				&& player_position.y < node_2.y
				&& player_position.x == node_1.x
		} else {
			node_2.y <= player_position.y
				&& player_position.y < node_1.y
				&& player_position.x == node_1.x
		}
	};

	let allow_down_movement = {
		if node_1.y <= node_2.y {
			node_1.y < player_position.y
				&& player_position.y <= node_2.y
				&& player_position.x == node_1.x
		} else {
			node_2.y < player_position.y
				&& player_position.y <= node_1.y
				&& player_position.x == node_1.x
		}
	};

	(
		allow_up_movement,
		allow_down_movement,
		allow_left_movement,
		allow_right_movement
	)
}

fn on_current_connection(player: &Player, grid: &Grid) -> bool {
	let (conn_i1, conn_i2) = grid.connections.get(player.connection).unwrap();
	let node_1 = grid.nodes.get(*conn_i1).unwrap();
	let node_2 = grid.nodes.get(*conn_i2).unwrap();

	on_connection(&player.position, node_1, node_2)
}

fn on_connection(player_position: &Vec2, node_1: &Vec2, node_2: &Vec2) -> bool {
	let on_x = if node_1.x <= node_2.x {
		node_1.x <= player_position.x && player_position.x <= node_2.x
	} else {
		node_2.x <= player_position.x && player_position.x <= node_1.x
	};

	let on_y = if node_1.y <= node_2.y {
		node_1.y <= player_position.y && player_position.y <= node_2.y
	} else {
		node_2.y <= player_position.y && player_position.y <= node_1.y
	};

	on_x && on_y
}

fn update_connections(player: &mut Player, grid: &Grid) -> bool {
	for conn in &player.next_connections {
		let (next_conn_i1, next_conn_i2) = grid.connections.get(*conn).unwrap();
		let next_node_1 = grid.nodes.get(*next_conn_i1).unwrap();
		let next_node_2 = grid.nodes.get(*next_conn_i2).unwrap();

		if on_connection(&player.position, next_node_1, next_node_2) {
			player.connection = *conn;
			//Update next connections
			let mut next_connections = Vec::new();
			for (i, (poss_conn_i1, poss_conn_i2)) in grid.connections.iter().enumerate() {
				if (next_conn_i1, next_conn_i2) != (poss_conn_i1, poss_conn_i2)
					&& next_conn_i1 == poss_conn_i1
					|| next_conn_i1 == poss_conn_i2
					|| next_conn_i2 == poss_conn_i1
					|| next_conn_i2 == poss_conn_i2
				{
					next_connections.push(i);
				}
			}
			player.next_connections = next_connections;

			return true;
		}
	}
	false
}

fn in_node_range(player_position: &Vec2, node: &Vec2, range: f32) -> bool {
	node.x - range <= player_position.x
		&& player_position.x <= node.x + range
		&& node.y - range <= player_position.y
		&& player_position.y <= node.y + range
}

fn get_squared_node_range(player_position: &Vec2, node: &Vec2) -> f32 {
	(player_position.x - node.x).abs() + (player_position.y - node.y).abs()
}

fn snap_to_closest_node(player: &mut Player, grid: &Grid) {
	let (current_connection_index_1, current_connection_index_2) =
		grid.connections.get(player.connection).unwrap();

	let mut nodes = HashSet::new();
	nodes.insert(current_connection_index_1);
	nodes.insert(current_connection_index_2);
	for conn in &player.next_connections {
		let (next_connection_index_1, next_connection_index_2) =
			grid.connections.get(*conn).unwrap();

		nodes.insert(next_connection_index_1);
		nodes.insert(next_connection_index_2);
	}

	let mut closest_sqared_range = f32::MAX;
	let mut closest_node = &vec2(0.0, 0.0);
	for node_index in nodes {
		let current_node = grid.nodes.get(*node_index).unwrap();
		let current_squard_range = get_squared_node_range(&player.position, current_node);
		if closest_sqared_range > current_squard_range {
			closest_sqared_range = current_squard_range;
			closest_node = current_node;
		}
	}

	player.position = *closest_node;
}

fn attack_human_routine(player: &mut Player, room: &Room) {
	let range = 1.0;

	if is_key_pressed(KeyCode::Space) {
		//In some node range?
		let mut in_range = false;
		for node in &room.grid.nodes {
			if in_node_range(&player.position, node, range) {
				in_range = true;
				break;
			}
		}

		if in_range {
			let (human_pos, human_nodes) = room.get_human_information();

			let mut human_attacked = false;

			if human_pos.is_some() {
				for node_index in human_nodes {
					if in_node_range(
						&player.position,
						room.grid.nodes.get(*node_index).unwrap(),
						range
					) {
						human_attacked = true;
					}
				}
			}

			if human_attacked {
				player.successfull_attack = true;
			} else {
				main_camera_mut().shake(1.0, 1.0);
			}
		}
	}
}
