#include "player.h"
#include "scene.h"
#include "input.h"
#include "../common/utils.h"
#include "transform.h"
#include "camera.h"
#include "bounding_volumes.h"
#include "../common/hashmap.h"
#include "../common/log.h"
#include "entity.h"
#include "../system/config_vars.h"
#include "../system/platform.h"
#include "game.h"
#include "debug_vars.h"
#include "geometry.h"
#include "im_render.h"
#include "event.h"
#include "sound_source.h"
#include "entity.h"
#include "gui_game.h"
#include "texture.h"
#include "enemy.h"

#include <float.h>
#include <string.h>
#include <stdlib.h>


static void player_on_mousebutton_released(const struct Event* event);
static void player_on_input_map_released(const struct Event* event);
static void player_on_scene_loaded(const struct Event* event);

void player_init(struct Player* player, struct Scene* scene)
{
	struct Game_State* game_state = game_state_get();
    entity_init(player, "Player", &scene->root_entity);
    player->base.flags            |= EF_ACTIVE;
    player->base.id               =  1;
    player->base.type             =  ET_PLAYER;
	player->base.bounding_box.min = (vec3){ -1.5f, -1.5f, -1.0f };
	player->base.bounding_box.max = (vec3){  1.5f,  1.5f,  1.0f };

    struct Hashmap* config = game_state->cvars;
    player->move_speed                   = hashmap_float_get(config, "player_move_speed");
    player->move_speed_multiplier        = hashmap_float_get(config, "player_move_speed_multiplier");
    player->turn_speed                   = hashmap_float_get(config, "player_turn_speed");
    player->jump_speed                   = hashmap_float_get(config, "player_jump_speed");
    player->gravity                      = hashmap_float_get(config, "player_gravity");
    player->min_downward_distance        = hashmap_float_get(config, "player_min_downward_distance");
    player->min_forward_distance         = hashmap_float_get(config, "player_min_forward_distance");
	player->grounded                     = true;
	player->can_jump                     = true;
	player->health                       = MAX_PLAYER_HEALTH;
	player->key_mask                     = 0;
	player->weapon_light_intensity_min   = 1.f;
	player->weapon_light_intensity_max   = 5.f;
	player->weapon_light_intensity_decay = 30.f;
	player->damage                       = 25;

    player->body_mesh = scene_static_mesh_create(scene, "Player_Body_Mesh", player, "sphere.symbres", MAT_BLINN);


    struct Camera* player_camera = &scene->cameras[CAM_GAME];
    entity_rename(player_camera, "Player_Camera");
    player_camera->base.flags |= EF_ACTIVE;
    player_camera->clear_color.x = 0.6f;
    player_camera->clear_color.y = 0.6f;
    player_camera->clear_color.z = 0.9f;
    player_camera->clear_color.w = 1.f;
    player->camera = player_camera;

	vec3 translation = { 0.f, -0.3f, -1.75f };
	player->weapon_light = scene_light_create(scene, "Player_Weapon_Light", player_camera, LT_SPOT);
	transform_translate(player->weapon_light, &translation, TS_LOCAL);
	player->weapon_light->intensity   = 0.f;
	player->weapon_light->radius      = 25.f;
	player->weapon_light->outer_angle = 80.f;
	player->weapon_light->inner_angle = 50.f;
	vec3_fill(&player->weapon_light->color, 0.9f, 0.4f, 0.f);

	player->weapon_mesh = scene_static_mesh_create(scene, "Player_Weapon_Mesh", player_camera, "player_weapon.symbres", MAT_BLINN);
	transform_translate(player->weapon_mesh, &translation, TS_LOCAL);
	transform_rotate(player->weapon_mesh, &UNIT_Y, 90.f, TS_LOCAL);
	transform_scale(player->weapon_mesh, &(vec3){0.3f, 0.1f, 0.1f});

	player->muzzle_flash_mesh = scene_static_mesh_create(scene, "Player_Muzzle_Flash_Mesh", player_camera, "muzzle_flash.symbres", MAT_BLINN);
	transform_translate(player->muzzle_flash_mesh, &(vec3) {0.f, -0.3f, -2.75f}, TS_LOCAL);
	transform_rotate(player->muzzle_flash_mesh, &UNIT_X, 90.f, TS_LOCAL);
	transform_scale(player->muzzle_flash_mesh, &(vec3){0.f});
	player->muzzle_flash_mesh->model.material_params[MMP_DIFFUSE_TEX].val_int = texture_create_from_file("white.tga", TU_DIFFUSE);
	player->muzzle_flash_mesh->model.material_params[MMP_DIFFUSE].val_float = 6.f;
	vec4_fill(&player->muzzle_flash_mesh->model.material_params[MMP_DIFFUSE_COL].val_vec4, 0.9f, 0.4f, 0.f, 1.f);
	
	struct Sound_Source* weapon_sound = scene_sound_source_create(scene, "Player_Weapon_Sound_Source", player, "sounds/bullet_1.wav", ST_WAV, false, false);
	if(weapon_sound)
		player->weapon_sound = weapon_sound;
	else
		log_error("player:init", "Could not add weapon entity to player");

	struct Sound_Source* footstep_sound = scene_sound_source_create(scene, "Player_Footstep_Sound_Source", player, "sounds/player_walk.wav", ST_WAV, false, false);
	if(footstep_sound)
		player->footstep_sound = footstep_sound;
	else
		log_error("player:init", "Could not add footstep entity to player");

	struct Sound_Source* grunt_sound = scene_sound_source_create(scene, "Player_Grunt_Sound_Source", player, "sounds/player_jump_grunt.wav", ST_WAV, false, false);
	if(grunt_sound)
		player->grunt_sound = grunt_sound;
	else
		log_error("player:init", "Could not add grunt entity to player");

	// Mark player camera and mesh as transient for now. We don't need to save them to file since we recreate them here anyway
	player->camera->base.flags            |= EF_TRANSIENT;
	player->footstep_sound->base.flags    |= EF_TRANSIENT;
	player->grunt_sound->base.flags       |= EF_TRANSIENT;
	player->body_mesh->base.flags         |= EF_TRANSIENT | EF_IGNORE_COLLISION | EF_ALWAYS_RENDER;
	player->weapon_mesh->base.flags       |= EF_TRANSIENT | EF_IGNORE_COLLISION | EF_ALWAYS_RENDER;
	player->muzzle_flash_mesh->base.flags |= EF_TRANSIENT | EF_IGNORE_COLLISION | EF_ALWAYS_RENDER;
	player->weapon_light->base.flags      |= EF_TRANSIENT;
	player->weapon_sound->base.flags      |= EF_TRANSIENT;

    transform_parent_set(player_camera, player, true);

    vec3 cam_translation = {0.f, 1.5f, 0.f};
    transform_translate(player_camera, &cam_translation, TS_LOCAL);

	sound_listener_set(game_state->sound, player_camera);
	sound_listener_update(game_state->sound);

	event_manager_subscribe(game_state->event_manager, EVT_MOUSEBUTTON_RELEASED, &player_on_mousebutton_released);
	event_manager_subscribe(game_state->event_manager, EVT_INPUT_MAP_RELEASED, &player_on_input_map_released);
	event_manager_subscribe(game_state->event_manager, EVT_SCENE_LOADED, &player_on_scene_loaded);
}

void player_destroy(struct Player* player)
{
	struct Game_State* game_state = game_state_get();
	struct Event_Manager* event_manager = game_state->event_manager;
	event_manager_unsubscribe(event_manager, EVT_MOUSEBUTTON_RELEASED, &player_on_mousebutton_released);
	event_manager_unsubscribe(event_manager, EVT_INPUT_MAP_RELEASED, &player_on_input_map_released);
	event_manager_unsubscribe(event_manager, EVT_SCENE_LOADED, &player_on_scene_loaded);
    entity_reset(player, player->base.id);
	scene_entity_base_remove(game_state_get()->scene, &player->base);
    player->base.flags = EF_NONE;
}

void player_update_physics(struct Player* player, struct Scene* scene, float fixed_dt)
{
	struct Sound* sound = game_state_get()->sound;

    /* Look around */
	float total_pitch    = quat_get_pitch(&player->camera->base.transform.rotation);
    float pitch          = 0.f;
    float yaw            = 0.f;
    float max_pitch      = 80.f;
	vec3  rot_axis_pitch = { 1, 0, 0 };
	vec3  rot_axis_yaw   = { 0, 1, 0 };

    if(input_map_state_get("Turn_Up",    KS_PRESSED)) pitch += player->turn_speed;
    if(input_map_state_get("Turn_Down",  KS_PRESSED)) pitch -= player->turn_speed;
    if(input_map_state_get("Turn_Right", KS_PRESSED)) yaw   += player->turn_speed;
    if(input_map_state_get("Turn_Left",  KS_PRESSED)) yaw   -= player->turn_speed;

	int cursor_yaw, cursor_pitch;
	input_mouse_delta_get(&cursor_yaw, &cursor_pitch);
	if(input_mouse_mode_get() != MM_RELATIVE)
	{
		input_mouse_mode_set(MM_RELATIVE);
		cursor_yaw = cursor_pitch = 0;
	}

	pitch = -cursor_pitch * player->turn_speed * fixed_dt;
	yaw = cursor_yaw * player->turn_speed * fixed_dt;

    total_pitch += pitch;
    if(total_pitch >= max_pitch)
    {
		total_pitch = max_pitch;
		pitch = 0.f;
    }
    else if(total_pitch <= -max_pitch)
    {
		total_pitch = -max_pitch;
		pitch = 0.f;
    }

    if(yaw != 0.f)
		transform_rotate(player, &rot_axis_yaw, -yaw, TS_WORLD);

    if(pitch != 0.f)
		transform_rotate(player->camera, &rot_axis_pitch, pitch, TS_LOCAL);

    /* Movement */
    float move_speed    = player->move_speed;
	vec3 move_direction = { 0.f };

	static bool  jumping             = false;
	static bool  landed              = false;
	static float move_speed_vertical = 0.f;

	// If we started jumping last frame, set jumpig to false
	if(jumping) jumping = false;

	if(landed) landed = false;

    if(input_map_state_get("Sprint",        KS_PRESSED))  move_speed *= player->move_speed_multiplier;
    if(input_map_state_get("Move_Forward",  KS_PRESSED))  move_direction.z -= 1.f;
    if(input_map_state_get("Move_Backward", KS_PRESSED))  move_direction.z += 1.f;
    if(input_map_state_get("Move_Left",     KS_PRESSED))  move_direction.x -= 1.f;
    if(input_map_state_get("Move_Right",    KS_PRESSED))  move_direction.x += 1.f;
	if(input_map_state_get("Jump", KS_PRESSED))
	{
		if(player->grounded && player->can_jump)
		{
			move_speed_vertical += player->jump_speed;
			jumping = true;
			player->grounded = false;
			player->can_jump = false;
		}
		else
		{
			player->can_jump = false;
		}
	}

	vec3_norm(&move_direction, &move_direction);
	if(move_direction.x != 0 || move_direction.z != 0)
		quat_mul_vec3(&move_direction, &player->base.transform.rotation, &move_direction);

	// Check for collisions in the directions we want to move
	int mouse_x = 0, mouse_y = 0;
	platform_mouse_position_get(&mouse_x, &mouse_y);
	struct Ray forward_ray = { 0 };
	transform_get_absolute_position(player, &forward_ray.origin);
	vec3_assign(&forward_ray.direction, &move_direction);

	// Get all the entities that intersect then check the distance if it is less than
	// or equal to min_collision_distance then we are colliding
	struct Raycast_Result ray_result;
	scene_ray_intersect(scene, &forward_ray, &ray_result, ERM_STATIC_MESH | ERM_DEFAULT);
	debug_vars_show_int("Colliding Entities", ray_result.num_entities_intersected);
	if(ray_result.num_entities_intersected > 0)
	{
		for(int i = 0; i < ray_result.num_entities_intersected; i++)
		{
			struct Entity* colliding_entity = ray_result.entities_intersected[i];

			if(colliding_entity->flags & EF_IGNORE_COLLISION) 
				continue;

			float distance = bv_distance_ray_bounding_box(&forward_ray, &colliding_entity->derived_bounding_box);
			if(distance > 0.f && distance <= player->min_forward_distance && colliding_entity != player->body_mesh)
			{
				vec3 intersection_point = forward_ray.direction;
				vec3_scale(&intersection_point, &intersection_point, distance);
				vec3_add(&intersection_point, &intersection_point, &forward_ray.origin);

				struct Bounding_Box* box = &colliding_entity->derived_bounding_box;
				vec3 normal = bv_bounding_box_normal_from_intersection_point(box, intersection_point);

				//im_ray_origin_dir(intersection_point, normal, 5.f, (vec4) { 1.f, 0.f, 0.f, 1.f }, 3);
				//im_ray(&forward_ray, player->min_forward_distance, (vec4) { 0.f, 1.f, 0.f, 1.f }, 3);

				float dot = (vec3_dot(&move_direction, &normal));
				vec3 norm_scaled = { 0.f };
				vec3_scale(&normal, &normal, dot);
				vec3_sub(&move_direction, &move_direction, &normal);
			}
		}
	}

	// Check for collisions below
	move_speed_vertical += player->gravity;
	struct Raycast_Result down_ray_result;
	struct Ray downward_ray;
	transform_get_absolute_position(player->body_mesh, &downward_ray.origin);
	vec3_fill(&downward_ray.direction, 0.f, -1.f, 0.f);
	scene_ray_intersect(scene, &downward_ray, &down_ray_result, ERM_STATIC_MESH | ERM_DEFAULT);
	if(down_ray_result.num_entities_intersected > 0)
	{
		for(int i = 0; i < down_ray_result.num_entities_intersected; i++)
		{
			struct Entity* colliding_entity = down_ray_result.entities_intersected[i];

			if(colliding_entity == player->body_mesh)
				continue;
			if(colliding_entity->flags & EF_IGNORE_COLLISION)
				continue;

			float distance = bv_distance_ray_bounding_box(&downward_ray, &colliding_entity->derived_bounding_box);
			if(distance > 0.f && distance <= player->min_downward_distance && !jumping)
			{
				move_speed_vertical = 0.f;
				if(!player->grounded)
				{
					player->grounded = true;
					landed = true;
				}
			}
		}
	}

	// Apply speed to direction then translate
    vec3 translation = {0.f, 0.f, 0.f};
	vec3_assign(&translation, &move_direction);

	translation.x *= move_speed * fixed_dt;
	translation.z *= move_speed * fixed_dt;
	translation.y  = move_speed_vertical * fixed_dt;
	transform_translate(player, &translation, TS_WORLD);

	// Sounds
	if(translation.x != 0.f || translation.z != 0.f || landed)
	{
		if(player->grounded)
		{
			if(sound_source_is_paused(sound, player->footstep_sound)) // if a sound is already playing, let it finish first then switch to avoid abrupt transition
			{
				// Sprinting/Walking/Jump Landing
				if(landed)
				{
					if(strncmp(player->footstep_sound->source_buffer->filename, "sounds/player_jump_land.wav", MAX_FILENAME_LEN) != 0)
						sound_source_buffer_set(sound, player->footstep_sound, "sounds/player_jump_land.wav", ST_WAV);
				}
				else if(input_map_state_get("Sprint", KS_PRESSED))
				{
					if(strncmp(player->footstep_sound->source_buffer->filename, "sounds/player_sprint.wav", MAX_FILENAME_LEN) != 0)
						sound_source_buffer_set(sound, player->footstep_sound, "sounds/player_sprint.wav", ST_WAV);
				}
				else if(strncmp(player->footstep_sound->source_buffer->filename, "sounds/player_walk.wav", MAX_FILENAME_LEN) != 0)
				{
					sound_source_buffer_set(sound, player->footstep_sound, "sounds/player_walk.wav", ST_WAV);
				}

				sound_source_play(sound, player->footstep_sound);
			}
		}
	}

	if(jumping)
	{
		if(sound_source_is_paused(sound, player->grunt_sound))
		{
			if(strncmp(player->grunt_sound->source_buffer->filename, "sounds/player_jump_grunt.wav", MAX_FILENAME_LEN) != 0)
				sound_source_buffer_set(sound, player->grunt_sound, "sounds/player_jump_grunt.wav", ST_WAV);

			sound_source_play(sound, player->grunt_sound);
		}
	}

	debug_vars_show_bool("Grounded", player->grounded);
}

void player_on_input_map_released(const struct Event* event)
{
	struct Game_State* game_state = game_state_get();
	if(strncmp("Jump", event->input_map.name, MAX_HASH_KEY_LEN) == 0)
	{
		struct Player* player = &game_state->scene->player;
		player->can_jump = true;
	}
}

void player_on_mousebutton_released(const struct Event* event)
{
	int button = event->mousebutton.button;
	int state = event->mousebutton.state;
	struct Game_State* game_state = game_state_get();
	struct Scene* scene = game_state->scene;
	struct Player* player = &scene->player;

	if(game_state->game_mode != GAME_MODE_GAME || game_state->gui_game->show_next_level_dialog || game_state->gui_game->show_restart_level_dialog)
		return;

	/* Aiming and Projectiles*/
	if(button == MSB_LEFT)
	{
		int half_width = 0, half_height = 0;
		window_get_drawable_size(game_state->window, &half_width, &half_height);
		half_width /= 2;
		half_height /= 2;
		struct Ray bullet_ray = camera_screen_coord_to_ray(player->camera, half_width, half_height);

		struct Entity* colliding_entity = scene_ray_intersect_closest(scene, &bullet_ray, ERM_ENEMY | ERM_STATIC_MESH | ERM_DEFAULT);

		int intensity = player->weapon_light_intensity_min + rand() % player->weapon_light_intensity_max;
		player->weapon_light->intensity = intensity;
		transform_scale(player->muzzle_flash_mesh, &(vec3){1.f, 1.f, 1.f});
		sound_source_play(game_state->sound, player->weapon_sound);

		if(!colliding_entity || colliding_entity == player->body_mesh)
			return;

		if(colliding_entity->type != ET_ENEMY) // If we did not hit an enemy, check if we hit it's mesh instead. If the mesh has a parent enemy entity, assume we hit an enemy otherwise stop
		{
			if(colliding_entity->transform.parent->type == ET_ENEMY)
				colliding_entity = colliding_entity->transform.parent;
			else
				return;
		}

		float distance = bv_distance_ray_bounding_box(&bullet_ray, &colliding_entity->derived_bounding_box);
		if(distance > 0.f)
		{
			enemy_apply_damage((struct Enemy*)colliding_entity, player->damage);
		}
	}
}

void player_apply_damage(struct Player* player, struct Enemy* enemy)
{
	log_message("Player hit!");
	player->health -= enemy->damage;

	if(player->health <= 0)
	{
		log_message("Player Ded!");
		struct Event_Manager* event_manager = game_state_get()->event_manager;
		struct Event* player_death_event = event_manager_create_new_event(event_manager);
		player_death_event->type = EVT_PLAYER_DIED;
		player_death_event->player_death.player = player;
		player_death_event->player_death.enemy = enemy;
		event_manager_send_event(event_manager, player_death_event);
	}
}

void player_on_pickup(struct Player* player, struct Pickup* pickup)
{
	switch(pickup->type)
	{
	case PICKUP_HEALTH:
		player->health = min(MAX_PLAYER_HEALTH, player->health + pickup->health);
		break;
	case PICKUP_KEY:
		player->key_mask |= pickup->key_type;
		break;
	}
}

void player_update(struct Player* player, float dt)
{
	if(player->weapon_light->intensity > 0.f)
	{
		player->weapon_light->intensity -= player->weapon_light_intensity_decay * dt;
		if(player->weapon_light->intensity < 0.f)
			player->weapon_light->intensity = 0.f;
	}

	if(player->muzzle_flash_mesh->base.transform.scale.x > 0.f)
	{
		player->muzzle_flash_mesh->base.transform.scale.x -= player->weapon_light_intensity_decay * dt;
		player->muzzle_flash_mesh->base.transform.scale.y -= player->weapon_light_intensity_decay * dt;
		player->muzzle_flash_mesh->base.transform.scale.z -= player->weapon_light_intensity_decay * dt;
		transform_update_transmat(player->muzzle_flash_mesh);
	}
}

void player_on_scene_loaded(const struct Event* event)
{
	struct Scene* scene = game_state_get()->scene;
	struct Player* player = &scene->player;

	struct Entity* player_start = scene_entity_find(scene, "PLAYER_START");
	if(player_start)
	{
		vec3 abs_pos = { 0.f };
		transform_get_absolute_position(player_start, &abs_pos);
		transform_set_position(player, &abs_pos);
	}
}
