#pragma warning(push)
#pragma warning(disable : 4459; disable : 4244)

#include "geometry.cpp"
#include "model.cpp"
#include "tgaimage.cpp"
#include <cmath>
#include <limits>
#include <vector>

const int trWidth  = 800;
const int trHeight = 800;
const int trDepth  = 255;

Model *model    = NULL;
int *zbuffer    = NULL;
Vec3f light_dir = Vec3f(1, -1, 1).normalize();

Matrix viewport(int x, int y, int w, int h)
{
	Matrix m = Matrix::identity(4);
#if 0
	m[0][3]  = x; // + w / 2.f;
	m[1][3]  = y; // + h / 2.f;
	// m[2][3]  = trDepth / 2.f;

	m[0][0] = w / 2.f;
	m[1][1] = h / 2.f;
	// m[2][2] = trDepth / 2.f;
#else
	m[0][3] = x + w / 2.f;
	m[1][3] = y + h / 2.f;
	m[2][3] = trDepth / 2.f;

	m[0][0] = w / 2.f;
	m[1][1] = h / 2.f;
	m[2][2] = trDepth / 2.f;
#endif
	return m;
}

Matrix lookat(Vec3f eye, Vec3f center, Vec3f up)
{
	Vec3f z    = (eye - center).normalize();
	Vec3f x    = (up ^ z).normalize();
	Vec3f y    = (z ^ x).normalize();
	Matrix res = Matrix::identity(4);

	res[0][0] = x[0];
	res[0][1] = x[1];
	res[0][2] = x[2];

	res[1][0] = y[0];
	res[1][1] = y[1];
	res[1][2] = y[2];

	res[2][0] = z[0];
	res[2][1] = z[1];
	res[2][2] = z[2];

	res[0][3] = -center[0];
	res[1][3] = -center[1];
	res[2][3] = -center[2];

	for (int i = 0; i < 3; i++)
	{
		res[0][i] = x[i];
		res[1][i] = y[i];
		res[2][i] = z[i];
		res[i][3] = -center[i];
	}
	return res;
}

void triangle(Vec3i t0, Vec3i t1, Vec3i t2, float ity0, float ity1, float ity2, TGAImage &image,
              int *zbuffer)
{
	if (t0.y == t1.y && t0.y == t2.y) return; // i dont care about degenerate triangles
	if (t0.y > t1.y)
	{
		std::swap(t0, t1);
		std::swap(ity0, ity1);
	}
	if (t0.y > t2.y)
	{
		std::swap(t0, t2);
		std::swap(ity0, ity2);
	}
	if (t1.y > t2.y)
	{
		std::swap(t1, t2);
		std::swap(ity1, ity2);
	}

	int total_trHeight = t2.y - t0.y;
	for (int i = 0; i < total_trHeight; i++)
	{
		bool second_half     = i > t1.y - t0.y || t1.y == t0.y;
		int segment_trHeight = second_half ? t2.y - t1.y : t1.y - t0.y;
		float alpha          = (float)i / total_trHeight;
		float beta           = (float)(i - (second_half ? t1.y - t0.y : 0)) /
		             segment_trHeight; // be careful: with above conditions no division by zero here
		Vec3i A    = t0 + Vec3f(t2 - t0) * alpha;
		Vec3i B    = second_half ? t1 + Vec3f(t2 - t1) * beta : t0 + Vec3f(t1 - t0) * beta;
		float ityA = ity0 + (ity2 - ity0) * alpha;
		float ityB = second_half ? ity1 + (ity2 - ity1) * beta : ity0 + (ity1 - ity0) * beta;
		if (A.x > B.x)
		{
			std::swap(A, B);
			std::swap(ityA, ityB);
		}
		for (int j = A.x; j <= B.x; j++)
		{
			float phi  = B.x == A.x ? 1. : (float)(j - A.x) / (B.x - A.x);
			Vec3i P    = Vec3f(A) + Vec3f(B - A) * phi;
			float ityP = ityA + (ityB - ityA) * phi;
			int idx    = P.x + P.y * trWidth;
			if (P.x >= trWidth || P.y >= trHeight || P.x < 0 || P.y < 0) continue;
			if (zbuffer[idx] < P.z)
			{
				zbuffer[idx] = P.z;
#if 1
				image.set(P.x, P.y, TGAColor(255 * ity0, 255 * ity0, 255 * ity0));
#else
				image.set(P.x, P.y, TGAColor(255, 255,  255) * ityP);
#endif
			}
		}
	}
}

int tinyrenderer(int argc, char **argv)
{
	if (2 == argc)
	{
		model = new Model(argv[1]);
	}
	else
	{
		model = new Model("african_head.obj");
	}

	zbuffer = new int[trWidth * trHeight];
	for (int i = 0; i < trWidth * trHeight; i++)
	{
		zbuffer[i] = std::numeric_limits<int>::min();
	}

	Vec3f eye(1, 1, 3);
	Vec3f center(0, 0, 0);

	{ // draw the model
		Matrix viewMatrix  = lookat(eye, center, Vec3f(0, 1, 0));
		Matrix perspective = Matrix::identity(4);
		Matrix viewp   = viewport(0, 0, trWidth, trHeight);
		auto cam          = -1.f / (eye - center).norm();
		perspective[3][2]  = cam;

		std::cerr << viewMatrix << std::endl;
		std::cerr << perspective << std::endl;
		std::cerr << viewp << std::endl;
		Matrix z = (viewp * perspective * viewMatrix);
		std::cerr << z << std::endl;

		TGAImage image(trWidth, trHeight, TGAImage::RGB);
		for (int i = 0; i < model->nfaces(); i++)
		{
			std::vector<int> face = model->face(i);
			Vec3f screen_coords[3];
			Vec3f world_coords[3];
			float intensity[3];
			for (int j = 0; j < 3; j++)
			{
#if 1
				Vec3f v                              = model->vert(face[j]);
				v                                    = Vec3f(0.1347f, -0.1472f, 0.4880f);
				Matrix perspectiveViewMatrix         = perspective * viewMatrix;
				Matrix viewpPerspectiveViewMatrix    = viewp * perspectiveViewMatrix;
				Vec3f screen                         = Vec3f(viewpPerspectiveViewMatrix * Matrix(v));
				screen_coords[j]                     = screen;
				world_coords[j]                      = v;
				intensity[j]                         = model->norm(i, j) * light_dir;
#else
				Vec3f v          = model->vert(face[j]);
				screen_coords[j] = Vec3f(viewp * perspective * viewMatrix * Matrix(v));
				world_coords[j]  = v;
				intensity[j]     = model->norm(i, j) * light_dir;
#endif
			}
			triangle(screen_coords[0], screen_coords[1], screen_coords[2], intensity[0],
			         intensity[1], intensity[2], image, zbuffer);
		}
		image.flip_vertically(); // i want to have the origin at the left bottom corner of the image
		image.write_tga_file("output.tga");
	}

	{ // dump z-buffer (debugging purposes only)
		TGAImage zbimage(trWidth, trHeight, TGAImage::GRAYSCALE);
		for (int i = 0; i < trWidth; i++)
		{
			for (int j = 0; j < trHeight; j++)
			{
				zbimage.set(i, j, TGAColor(zbuffer[i + j * trWidth]));
			}
		}
		zbimage
		    .flip_vertically(); // i want to have the origin at the left bottom corner of the image
		zbimage.write_tga_file("zbuffer.tga");
	}
	delete model;
	delete[] zbuffer;
	return 0;
}
#pragma warning(pop)

