#include "pch.h"
#include "Camera.h"

void Camera::shake() {
	t_since_shake = 0;
}


// Camera::Camera() { }

void Camera::update_matrices() {
	
	this->prev_jitt_x = this->curr_jitt_x;
	this->prev_jitt_y = this->curr_jitt_y;
	// #ifdef EDITOR
	if (WEngine->io->mouse_grabbed) {
		float* pitch = &this->pitch_yaw_roll[0];
		float* yaw = &this->pitch_yaw_roll[1];
		*pitch += WEngine->io->delta_mouse_y * 1.0;
		*yaw += WEngine->io->delta_mouse_x * 1.0;
		*pitch = glm::max(*pitch,-0.49f);
		*pitch = glm::min(*pitch,0.49f);
	}
	// #endif

	float pitch = this->pitch_yaw_roll[0];
	float yaw = this->pitch_yaw_roll[1];

	if(this->mode == CameraMode::PILOT) {
		dir = glm::vec3(0,0,1);
		dir = glm::normalize(dir);

		dir = get_rot_mat(0, -pitch * tau * 0.5f ) * dir;
		dir = get_rot_mat(1, yaw * tau * 0.5f ) * dir;
	} else {
		glm::vec3 gdir = glm::vec3(this->look_at.x, this->look_at.y, this->look_at.z);
		gdir = gdir - glm::vec3(this->pos.x, this->pos.y, this->pos.z);
		gdir = normalize(gdir);
		dir = gdir;
	}

	right = glm::cross(this->world_up * -1.0f, dir);
	right = glm::normalize(right);

	up = glm::cross(dir, right);
	up = glm::normalize(up);
	
	this->V_prev = this->V;
	this->P_prev = this->P;

	glm::mat4 identity = glm::mat4(
		1,0,0,pos.x,
		0,1,0,pos.y,
		0,0,1,pos.z,
		0,0,0,1
	);
	transformation_matrix = identity;

	this->V = glm::lookAt(this->pos, this->pos + dir, glm::vec3(0,1,0));
	this->V = glm::rotate(this->V, this->roll, vec3(0,0,1));
	
	float ratio = float(WEngine->RESX) / float(WEngine->RESY);

	if(this->camera_projection == CameraProjection::PERSPECTIVE) {
		this->P = glm::perspective(this->fov*2.0f, ratio, this->_near, this->_far);
	} else if(this->camera_projection == CameraProjection::ORTHOGRAPHIC) {
		float orth_fov = 1.0f;
		if(scale_orth_fov_with_cam_pos){
			orth_fov = length(this->pos);
			orth_fov *= tan(this->fov*1);
		} else {
			orth_fov *= this->fov;
		}

		vec2 scale = vec2(
			float(WEngine->RESX)/ float(WEngine->RESY),
			1.0f
		) * orth_fov;
		
		this->P = orthoNO(
			-scale.x, scale.x,
			-scale.y, scale.y,
			this->_near,
			this->_far
		);

		for(int i = 0; i < 4; i++) {
			for(int j = 0; j < 4; j++) {
				this->P[i][j] = glm::mix(
					this->P[i][j],
					glm::perspective(this->fov*2.0f, ratio, this->_near, this->_far)[i][j],
					this->interp_fac_persp_orth
				);
			}
		}
	}
	
	float shake_amt = 10*glm::smoothstep<float>(0.0,0.02, t_since_shake) * (1.-glm::smoothstep<float>(0.2,0.5, t_since_shake));

	this->curr_jitt_x = Camera::radical_inverse[((WEngine->frame) % 16) * 2] * (
		this->taa_jitt_amt
		+ shake_amt
	) / WEngine->RESX;
	this->curr_jitt_y = Camera::radical_inverse[((WEngine->frame + 1) % 16) * 2] * (
		this->taa_jitt_amt
		+ shake_amt
	) / WEngine->RESY;
	
	if(this->camera_projection == CameraProjection::ORTHOGRAPHIC) {
		// this->curr_jitt_x = 0.;
		// this->curr_jitt_y = 0.;
	}

	this->curr_offs_x = 0;
	this->curr_offs_y = 0;
	this->curr_offs_x += viewspace_offs.x;
	this->curr_offs_y += viewspace_offs.y;


	this->P[2][0] += this->curr_jitt_x;
	this->P[2][1] += this->curr_jitt_y;
	this->P[2][0] += this->curr_offs_x/ WEngine->RESX;
	this->P[2][1] += this->curr_offs_y/ WEngine->RESY;

	this->P_inv = glm::inverse(this->P);
	this->V_inv = glm::inverse(this->V);

	if(this->prev_pos != this->pos || this->prev_right != this->right) {
		camera_moved_this_frame = true;
	}
	prev_pos = pos;
	prev_right = right;
}

void Camera::handle_movement() {
	#ifdef EDITOR
	if(this->noclip) {
		if (WEngine->io->mouse_grabbed) {
			glm::vec2 keyInput = glm::vec2(0, 0);
			if (WEngine->io->get_key(Key::A).down) {
				keyInput[0] -= 1;
			}
			if (WEngine->io->get_key(Key::D).down) {
				keyInput[0] += 1;
			}
			if (WEngine->io->get_key(Key::W).down) {
				keyInput[1] += 1;
			}
			if (WEngine->io->get_key(Key::S).down) {
				keyInput[1] -= 1;
			}

			float walk_speed = WEngine->delta_time * 5.0 * this->speed; // deltatime

			if (WEngine->io->get_key(Key::LShift).down) {
				walk_speed *= 2.0;
			}

			glm::vec3 deltaDir = dir * keyInput[1] * walk_speed;
			glm::vec3 deltaRight = right * keyInput[0] * walk_speed;

			glm::vec3 offsVec = deltaRight + deltaDir;
			// offsVec.normalize();
			this->pos += offsVec;
		}
	}
	if(!this->walking_locked) {
		// if (WEngine->editor->mouse_grabbed) {
		// 	Vec3 keyInput = Vec3({0, 0});
		// 	if (WEngine->editor->get_key(Key::A).down) {
		// 		keyInput.vals[0] -= 1;
		// 	}
		// 	if (WEngine->editor->get_key(Key::D).down) {
		// 		keyInput.vals[0] += 1;
		// 	}
		// 	if (WEngine->editor->get_key(Key::W).down) {
		// 		keyInput.vals[1] += 1;
		// 	}
		// 	if (WEngine->editor->get_key(Key::S).down) {
		// 		keyInput.vals[1] -= 1;
		// 	}
		//
		// 	float walk_speed = WEngine->delta_time * 5.0 * this->speed; // deltatime
		//
		// 	if (WEngine->editor->get_key(Key::LShift).down) {
		// 		walk_speed *= 2.0;
		// 	}
		//
		// 	Vec3 deltaDir = dir * keyInput[1] * walk_speed;
		// 	Vec3 deltaRight = right * keyInput[0] * walk_speed;
		//
		// 	Vec3 offsVec = deltaRight + deltaDir;
		// 	// offsVec.normalize();
		// 	pos += offsVec;
		// }
	}
	#endif
}

void Camera::unuse() {
	WEngine->camera->use();
}

void Camera::use() {
	this->update_matrices();
	float* buff_ptr = (float*)WEngine->shared_ssbo->cpu_data;
		
	const int offs_from_start = 256*4;
			
	Camera* camera = this;
		
	for (int i = 0; i < 16; i++) {
		(buff_ptr)[offs_from_start + i] = (&this->V[0][0])[i];
		(buff_ptr)[offs_from_start + 16 + i] = (&this->P[0][0])[i];
		(buff_ptr)[offs_from_start + 32 + i] = (&this->V_prev[0][0])[i];
		(buff_ptr)[offs_from_start + 48 + i] = (&this->P_prev[0][0])[i];
		(buff_ptr)[offs_from_start + 64 + i] = (&this->V_inv[0][0])[i];
		(buff_ptr)[offs_from_start + 80 + i] = (&this->P_inv[0][0])[i];
	}
	int offs = offs_from_start + 64 + 16 + 16;
		
	// vec4
	(buff_ptr)[offs++] = camera->pos.x;
	(buff_ptr)[offs++] = camera->pos.y;
	(buff_ptr)[offs++] = camera->pos.z;
	(buff_ptr)[offs++];
	// offs++; // padding
		
	// vec3
	(buff_ptr)[offs++] = camera->dir.x;
	(buff_ptr)[offs++] = camera->dir.y;
	(buff_ptr)[offs++] = camera->dir.z;
	(buff_ptr)[offs++] = camera->fov;
		
	// vec2
	(buff_ptr)[offs++] = camera->curr_jitt_x;
	(buff_ptr)[offs++] = camera->curr_jitt_y;
	(buff_ptr)[offs++] = camera->prev_jitt_x;
	(buff_ptr)[offs++] = camera->prev_jitt_y;
		
	// vec2
	(buff_ptr)[offs++];
	(buff_ptr)[offs++];
	(buff_ptr)[offs++];
	(buff_ptr)[offs++];
		
	// vec2 + 2x float
	(buff_ptr)[offs++];
	(buff_ptr)[offs++];
	(buff_ptr)[offs++];
	(buff_ptr)[offs++] = this->_near;
		
	// 2x float + int
	(buff_ptr)[offs++] = this->_far;
	(buff_ptr)[offs++];
	(buff_ptr)[offs++];
	(buff_ptr)[offs++];
	WEngine->shared_ssbo->upload_sub_data(buff_ptr,offs * sizeof(float), 0);
}

void Camera::update() {
	this->t_since_shake += WEngine->delta_time;
	if (this->mode == CameraMode::PILOT) {
		this->update_matrices();
		this->handle_movement();
	} else {
		this->update_matrices();
	}
}

glm::vec3 Camera::worldspace_to_ndc(glm::vec3 p) {
	auto world_to_view = [](glm::vec3 world, glm::mat4 proj, glm::mat4 view) -> glm::vec4{
		glm::vec4 aoP = glm::vec4(world, 1);
		aoP = view * aoP;
		float z = aoP.z;
		aoP = proj * aoP;
		aoP.x /= aoP.w;
		aoP.y /= aoP.w;
		aoP.z /= aoP.w;
		// aoP.y += 1.;
		// aoP.x += 1.;
		// aoP.x /= 2.;
		// aoP.y /= 2.;
		aoP.z = z;
		return aoP;
	};
	glm::vec4 p_view = world_to_view(p, this->P, this->V);

	return glm::vec3(p_view.x, p_view.y, p_view.z);
}

void Camera::lerp_with_another_cam(Camera* other_camera, float lerp_fac) {
	this->pos = glm::mix(this->pos, other_camera->pos, lerp_fac);
	this->look_at = glm::mix(this->look_at, other_camera->look_at, lerp_fac);
	this->pitch_yaw_roll = glm::mix(this->pitch_yaw_roll, other_camera->pitch_yaw_roll, lerp_fac);
	
	this->roll = glm::mix(this->roll, other_camera->roll, lerp_fac);
			
	this->interp_fac_persp_orth = glm::mix(this->interp_fac_persp_orth, other_camera->interp_fac_persp_orth, lerp_fac);
	this->fov = glm::mix(this->fov, other_camera->fov, lerp_fac);
	this->_near = glm::mix(this->_near, other_camera->_near, lerp_fac);
	this->_far = glm::mix(this->_far, other_camera->_far, lerp_fac);
}
