#include "model.h"
#include "renderer.h"
#include "util_window.h"
#include "camera.h"
#include "util_renderer.h"

#define HORIZONTAL_CAMERA_SPEED           0.1
#define VERTICAL_CAMERA_SPEED             0.1
#define VERTICAL_CAMERA_CLAMP_UP           90
#define VERTICAL_CAMERA_CLAMP_DOWN        -90
#define NEAR_CLIP_PLANE                     0 
#define FAR_CLIP_PLANE                     10
#define FOV                                30
#define CAMERA_MOVEMENT_SPEED           0.05f
#define DEFAULT_CAMERA_POS     Vec3f(0, 0, 5)
#define DEFAULT_CAMERA_ROT     Vec3f(0, 0, 0)
#define LIGHT_INTENSITY                   2.f

const TGAColor white = TGAColor(255, 255, 255, 255);
const TGAColor red = TGAColor(255, 0, 0, 255);
const TGAColor green = TGAColor(0, 255, 0, 255);
const TGAColor blue = TGAColor(0, 0, 255, 255);

Matrix ViewPort = Matrix::identity();
Matrix ModelView = Matrix::identity();
Matrix Projection = Matrix::identity();

Model* model = new Model("african_head.obj");
Camera camera;

Vec3f light_dir = Vec3f(1, 1, 1).normalize();

void init_camera() {
	camera.SetPosition(DEFAULT_CAMERA_POS);
	camera.SetRotation(DEFAULT_CAMERA_ROT);
	camera.SetFOV(FOV);
	camera.SetNearPlane(NEAR_CLIP_PLANE);
	camera.SetFarPlane(FAR_CLIP_PLANE);
	camera.SetClampRotDown(VERTICAL_CAMERA_CLAMP_DOWN);
	camera.SetClampRotUp(VERTICAL_CAMERA_CLAMP_UP);
	camera.SetHorizontalRotSpeed(HORIZONTAL_CAMERA_SPEED);
	camera.SetVerticalRotSpeed(VERTICAL_CAMERA_SPEED);
	camera.SetMovementSpeed(CAMERA_MOVEMENT_SPEED);
	camera.ApplyChanges();
}

void clear_zbuffer()
{
	for (int i = 0; i < screen_width * screen_height; i++)
		z_buffer[i] = 0;
}


struct TextureShader : public IShader {
	mat<2, 3, float> varying_uv_coords;
	Matrix uniform_mit;
	Matrix uniform_m;

	virtual Vec4f vertex(int iface, int nthvert) {
		varying_uv_coords.set_col(nthvert, model->uv(iface, nthvert));
		Vec4f gl_Vertex = embed<4>(model->vert(iface, nthvert));
		return ViewPort * Projection * ModelView * gl_Vertex;									// transform it to screen coordinates
	}

	virtual bool fragment(Vec3f bar, TGAColor &color) {
		Vec2f uv = varying_uv_coords * bar;
		Vec3f normal = Vec3f(uniform_mit * Vec4f(model->normal(uv))).normalize();
		Vec3f light =  Vec3f(uniform_m * Vec4f(light_dir)).normalize();
		Vec3f reflection = (normal * (normal*light*2.f) - light).normalize();
		float spec_intensity = pow(std::fmax(reflection.z, 0.f), model->specular(uv));
		float diff_intensity = std::fmax(0.f, (normal*light));
		TGAColor c = model->diffuse(uv);
		color = c;
		for (int i = 0; i < 3; i++)
			color[i] = std::fmin(1 + c[i] * (diff_intensity + 0.8 * spec_intensity), 255) * LIGHT_INTENSITY;
		return false;                         
	}
};


void render()
{
	{
		//light_dir = camera.GetForward().normalize() * -1;
	}

	{
		viewport(0, 0, screen_width, screen_height, FAR_CLIP_PLANE, NEAR_CLIP_PLANE);
		Projection = camera.GetProjectionMatrix();
		ModelView = camera.GetModelViewMatrix();
	}
	

	//Matrix z = ViewPort * Projection * ModelView * model->Transform;

	clear_zbuffer();
	TextureShader shader;
	shader.uniform_m =   (Projection);
	shader.uniform_mit = (Projection).invert_transpose();

	#pragma omp parallel for
	for (int i = 0; i < model->nfaces(); i++) {
		Vec4f screen_coords[3];
		bool out = true;
		#pragma omp parallel for
		for (int j = 0; j < 3; j++) {
			screen_coords[j] = shader.vertex(i, j);
			Vec3f screen3(screen_coords[j]);

			if (screen3.x > 0 && screen3.x < screen_width && screen3.y > 0 && screen3.y < screen_height) out = false;
		}
		if(!out)
			triangle(screen_coords, shader);
	}
}

