#include "scene.h"
#include "v3.h"
#include "m33.h"
#include "ppc.h"

Scene *scene;

using namespace std;

#include <iostream>

Scene::Scene() {

	cgi = 0; soi = 0;

	morphFraction = 0.0f;

	gui = new GUI();
	gui->show();

	L = V3(0.0f, 0.0f, 0.0f);
	specc = 180.0f;

	int u0 = 20;
	int v0 = 20;
	int sci = 2;
	int w = sci * 320;
	int h = sci * 240;

	fb = new FrameBuffer(u0, v0, w, h, 0);
	fb->label("SW First person");
	fb->show();

	hwfb = new FrameBuffer(u0 /*+ w + 20*/, v0, w, h, 0);
	hwfb->isHW = 1;
	hwfb->label("Fixed Pipeline First person");
//	hwfb->show();

	gpufb = new FrameBuffer(u0 + w + 20, v0, w, h, 0);
	gpufb->isHW = 2;
	gpufb->label("GPU First person");
//	gpufb->show();

	fb3 = new FrameBuffer(u0, v0, w, h, 1);
	fb3->label("Third person");
	fb3->position(u0 + fb->w + 20, v0);
	//fb3->show();

	gui->uiw->position(u0, v0 + fb->h + 60);

	float hfov = 55.0f;
	ppc = new PPC(fb->w, fb->h, hfov);
	ppc3 = new PPC(fb3->w, fb3->h, 90.0f);

	tmsN = 7;
	tms = new TM[tmsN];
	tms[0].id = 0;
	tms[1].id = 1;
	tms[0].SetToBox(V3(0.0f, 0.0f, -100.0f), V3(30.0f, 30.0f, 30.0f), 
		V3(1.0f, 0.5f, 0.0f));
	tms[0].enabled = 0;
	tms[1].LoadBin("geometry/teapot1K.bin");
	tms[1].enabled = 1;
	AABB aabb = tms[1].ComputeAABB();
	cerr << "INFO: teapot aabb: " << aabb.corners[0] << "; " << aabb.corners[1] << endl;
	ppc->C = tms[1].GetCenterOfMass() + V3(0.0f, 0.0f, 110.0f);
	L = ppc->C;

	ppc3->PositionAndOrient(ppc->C + V3(50.0f, 100.0f, 50.0f), tms[1].GetCenterOfMass(), V3(0.0f, 1.0f, 0.0f));
	tms[2].SetToRectangle(V3(0.0f, 120.0f, -1000.0f), V3(1000.0f, 230.0f, 0.0f), V3(0.0f, 0.0f, 0.0f));
	tms[2].cols[0] = tms[2].cols[1] = V3(1.0f, 1.0f, 1.0f);
	tms[2].id = 2;
	tms[3].SetToRectangle(V3(0.0f, -120.0f, -1000.0f), V3(1000.0f, 230.0f, 0.0f), V3(0.0f, 0.0f, 0.0f));
	tms[3].cols[0] = tms[3].cols[1] = V3(1.0f, 1.0f, 1.0f);
	tms[3].id = 3;
	tms[2].enabled = tms[3].enabled = 0;

	texts = new FrameBuffer(0, 0, 128, 128, 2);
	texts->SetBWCheckerboard(16);
	texts->label("texture");
//	texts->show();
	tms[4].SetToRectangle(V3(0.0f, 0.0f, -100.0f), V3(100.0f, 100.0f, 0.0f), V3(1.0f, 1.0f, 1.0f));
	tms[4].tex = texts;
	tms[4].enabled = 0;
	tms[4].id = 4;

	aabb = tms[1].ComputeAABB();
	V3 bC = tms[1].GetCenterOfMass();
	bC[1] = aabb.corners[0][1];
	tms[5].SetToBox(bC, V3(200.0f, 1.0f, 200.0f), V3(1.0f, 0.0f, 0.0f));
	tms[5].id = 5;
	L = L + V3(30.0f, 80.0f, -20.0f);
	tms[5].enabled = 0;

	tms[6].SetToRectangle(bC, V3(200, 200.0f, 0.0f), V3(0.0f, 0.0f, 0.3f));
	tms[6].RotateAboutAxis(bC, V3(1.0f, 0.0f, 0.0f), 90.0f);
	tms[6].enabled = 0;

	smppc = 0;
	smfb = 0;
	gfb = 0;

//	ppc->LoadFromTextFile("view.txt");
	RenderAll();

}

void Scene::Render(FrameBuffer *currfb, PPC *currppc) {

	unsigned int bgr = 0xFFFF0000;
	currfb->Clear(bgr, 0.0f);

	for (int tmi = 0; tmi < tmsN; tmi++) {
		if (currfb->id == 1) {
			continue;
		}
		if (!tms[tmi].enabled)
			continue;
//		tms[tmi].RenderPoints(ppc, fb);
//		tms[tmi].RenderWireframe(currppc, currfb);
		tms[tmi].RenderFilled(currppc, currfb);

	}

	if (currfb->id == 1) {
		float visz = 40.0f;
		ppc->Visualize(visz, currppc, currfb);
		fb->VisualizeImagePoints(visz, ppc, ppc3, fb3);
		fb->Visualize3DPoints(ppc, ppc3, fb3);
	}

	currfb->Draw3DSegment(L, L + V3(0.0f, 5.0f, 0.0f), V3(1.0f, 1.0f, 0.0f), V3(1.0f, 1.0f, 0.0f), currppc);
	currfb->Draw3DSegment(L, L + V3(5.0f, 0.0f, 0.0f), V3(1.0f, 1.0f, 0.0f), V3(1.0f, 1.0f, 0.0f), currppc);
	currfb->Draw3DSegment(L, L + V3(0.0f, 0.0f, 5.0f), V3(1.0f, 1.0f, 0.0f), V3(1.0f, 1.0f, 0.0f), currppc);

	currfb->redraw();
}

void Scene::ShadowMapSetup() {


	int smw = 128;
	smppc = new PPC(smw, smw, 55.0f);
	smppc->PositionAndOrient(L, tms[1].GetCenterOfMass(), V3(0.0f, 1.0f, 0.0f));
	FrameBuffer *tmpsmfb = new FrameBuffer(100, 100, smw, smw, 2);
	Render(tmpsmfb, smppc);
	tmpsmfb->label("Shadow Map");
	tmpsmfb->show();

	smfb = tmpsmfb;

}


void Scene::DBG() {

	{

		ppc->LoadFromTextFile("view.txt");
		RenderAll();
		return;
	}

	{

		for (int fi = 0; fi < 1000; fi++) {
			morphFraction = (float)fi / 1299.0f;
			RenderAll();
			Fl::check();
		}
		return;


	}

	{
		V3 L0 = tms[1].GetCenterOfMass() + V3(0.0f, 0.0f, 100.0f);
		V3 L1 = tms[1].GetCenterOfMass() + V3(0.0f, 100.0f, 0.0f);
		for (int fi = 0; fi < 100; fi++) {
			L = L0 + (L1 - L0)*(float)fi / 99.0f;
			cerr << L << "        \r";
			RenderAll();
			Fl::check();
		}
		return;

	}

	{
		PPC ppc1(*ppc);
		ppc1.LoadFromTextFile("view.txt");
		PPC ppc0(*ppc);
		for (int fi = 0; fi < 100; fi++) {
			ppc->SetInterpolated(&ppc0, &ppc1, (float)fi / 99.0f);
			RenderAll();
			Fl::check();
		}
		return;
	}

	{
		ppc->LoadFromTextFile("view.txt");
		Fl::check();
		ShadowMapSetup();
		Fl::check();
		RenderAll();
		Fl::check();
		int gfbw = smfb->w, gfbh = smfb->h;
		gfb = new GFB(gfbw, gfbh);
		// add sampling locations from output image
		gfb->AddSLs(ppc, fb, smppc);
		gfb->SLStats();
		// render scene from light viewpoint on generalized framebuffer
		for (int tmi = 0; tmi < tmsN; tmi++) {
			if (!tms[tmi].enabled)
				continue;
			tms[tmi].RenderGFB(smppc, gfb);
		}
		// collecting shadow information
		gfb->ApplyShadow(fb);
		fb->redraw();
		Fl::check();
		return;

	}

	{

		V3 L0 = L;
		V3 L1 = L + V3(50.0f, -20.0f, -30.0f);

		ppc->LoadFromTextFile("view.txt");
		int fsN = 30;
		for (int fi = 0; fi < fsN; fi++) {
			L = L0 + (L1 - L0)*(float)fi / (float) fsN;
			RenderAll();
			ShadowMapSetup();
			RenderAll();
			Fl::check();
		}
		L = L0;
		return;

	}

	{
		float s0 = 10000.0f;
		float s1 = 10.0f;
		for (int fi = 0; fi < 100; fi++) {
			specc = s0 + (s1 - s0)*(float)fi / 99.0f;
			RenderAll();
			Fl::check();
		}
		return;

	}

	{
		V3 L0 = tms[1].GetCenterOfMass() + V3(0.0f, 0.0f, 100.0f);
		V3 L1 = tms[1].GetCenterOfMass() + V3(0.0f, 100.0f, 0.0f);
		for (int fi = 0; fi < 100; fi++) {
			L = L0 + (L1 - L0)*(float)fi / 99.0f;
			RenderAll();
			Fl::check();
		}
		return;

	}

	{

		float len = 10.0f;
		tms[1].VisualizeNormals(ppc, fb, len);
		fb->redraw();
		return;

	}

	{

		fb->Clear(0xFFFFFFFF, 0.0f);
		fb->Draw3DPoint(ppc->C+V3(0.0f, 0.0f, -100.0f), V3(0.0f, 0.0f, 1.0f),
			11.2f, ppc);
		fb->redraw();
		return;

	}

	{

		V3 a(1.0f, 2.0f, 0.25f);
		a = a.UnitVector();
		V3 C = tms[1].GetCenterOfMass();
		for (int i = 0; i < 360; i++) {
			tms[1].RotateAboutAxis(C, a, 1.0f);
			Render(fb, ppc);
			Fl::check();
		}
		return;

	}


	{

		PPC ppc0 = *ppc;
		V3 O = tms[0].GetCenterOfMass();
		V3 newC = ppc->C + V3(50.0f, 40.0f, 30.0f);
		ppc->PositionAndOrient(newC, O, V3(0.0f, 1.0f, 0.0f));
		PPC ppc1 = *ppc;
		int framesN = 300;
		for (int fi = 0; fi < framesN; fi++) {
			float fracf = (float)fi / (float)(framesN - 1);
			ppc->SetInterpolated(&ppc0, &ppc1, fracf);
			Render(fb, ppc);
			Fl::check();
		}
		*ppc = ppc0;
		return;

	}

	{

		float hfov = 55.0f;
		PPC ppc(fb->w, fb->h, hfov);

		V3 P(0.0f, 0.0f, -100.0f);
		V3 projP;
		if (ppc.Project(P, projP)) {
			cerr << projP << endl;
			fb->DrawCircle((int) projP[0], (int) projP[1], 5.0f, 0xFF00FF00);
			fb->redraw();
		}
		else {
			cerr << "point behind head" << endl;
		}


		return;

	}

	{

		M33 m;
		m[0] = V3(1.0f, -3.0f, 7.0f);
		m[1] = V3(2.0f, 10.0f, 4.3f);
		m[2] = V3(-8.0f, -50.0f, 1.3f);

		M33 m1 = m.Inverted();
		cerr << m1*m << endl << m*m1 << endl;
		return;


	}


	{
		V3 p0(23.5f, 100.1f, 0.0f);
		V3 p1 = p0 + V3(300.0f, 0.0f, 0.0f);
		V3 p2(23.5f, 300.1f, 0.0f);
		V3 p3 = p2 + V3(300.0f, 0.0f, 0.0f);
		int stepsN = 1000;
		for (int i = 0; i < stepsN; i++) {
			float frac = (float)i / (float)stepsN;
			V3 p01 = p0 + (p1 - p0)*frac;
			V3 p32 = p3 + (p2 - p3)*frac;
			V3 p02 = p0 + (p2 - p0)*frac;
			V3 p31 = p3 + (p1 - p3)*frac;
			fb->SetBGR(0xFFFFFFFF);
			V3 c1(0.0f, 1.0f, 0.0f);
			V3 c0(1.0f, 0.0f, 0.0f);
			fb->Draw2DSegment(p01, p32, c0, c0);
			fb->Draw2DSegment(p02, p31, c1, c1);
			fb->redraw();
			Fl::check();
		}
		return;

	}


	{

		M33 m;
		m[0] = V3(1.0f, 0.0f, 0.0f);
		m[1] = V3(0.0f, 1.0f, 0.0f);
		m[2] = V3(0.0f, 0.0f, 1.0f);
		cerr << m << endl;
		V3 v(2.0f, 10.0f, -1.0f);
		cerr << m*v << endl;
		return;

	}

	{
		int u0 = 100;
		int v0 = 200;
		int u1 = 500;
		int v1 = 400;
		float r = 34.3f;
		int stepsN = 100;
		unsigned int color = 0xFF00FFFF;
		for (int stepi = 0; stepi < stepsN; stepi++) {
			int curru = u0 + (u1 - u0)*stepi / stepsN;
			int currv = v0 + (v1 - v0)*stepi / stepsN;
			fb->SetBGR(0xFFFFFFFF);
			fb->DrawCircle(curru, currv, r, color);
			fb->redraw();
			Fl::check();
		}
		return;
	}

	{
		V3 v0(4.0f, 3.0f, 0.0f);
		V3 v1(5.0f, 2.0f, 1.0f);
		cerr << v0.Length() << endl;
		return;
		cerr << v0 << endl << v1 << endl << v0-v1 << endl;
		return;
		v0[0] = 3.0f;
		cerr << "v0[0]= " << v0[0] << endl;
		return;
	}

	{
		int u0 = 20;
		int v0 = 40;
		int u1 = 400;
		int v1 = 200;
		unsigned int color = 0xFFFF0000;
		fb->Draw2DRectangle(u0, v0, u1, v1, color);
	}

	fb->redraw();
	return;

	for (int u = 0; u < fb->w; u++) {
		fb->Set(u, fb->h / 2, 0xFF000000);
	}

	fb->redraw();

}


void Scene::NewButton() {
	cerr << "INFO: pressed New Button" << endl;
	ppc->SaveToTextFile("view.txt");
}

void Scene::RenderAll() {

//	RenderRT(fb, ppc);
//	return;

	Render(fb, ppc);
	return;

	if (hwfb)
		hwfb->redraw();
	if (gpufb)
		gpufb->redraw();
	//	Render(fb3, ppc3);

}


void Scene::RenderHW() {

	// clear the framebuffer
	glClearColor(0.0, 0.0f, 0.5f, 1.0f);
	glEnable(GL_DEPTH_TEST);
	glClear(GL_COLOR_BUFFER_BIT |
		GL_DEPTH_BUFFER_BIT);

	// set view
	// set intrinsics
	ppc->SetIntrinsicsHW(1.0f, 1000.0f);
	// set extrinsics
	ppc->SetExtrinsicsHW();

	// render geometry
	for (int tmi = 0; tmi < tmsN; tmi++) {
		if (!tms[tmi].enabled)
			continue;
		tms[tmi].RenderHW();
	}

}

void Scene::RenderGPU() {

	// if the first time, call per session initialization
	if (cgi == NULL) {
		cgi = new CGInterface();
		cgi->PerSessionInit();
		soi = new ShaderOneInterface();
		soi->PerSessionInit(cgi);
	}

	// clear the framebuffer
	glClearColor(0.0, 0.0f, 0.5f, 1.0f);
	glEnable(GL_DEPTH_TEST);
	glClear(GL_COLOR_BUFFER_BIT |
		GL_DEPTH_BUFFER_BIT);

	// set view
	// set intrinsics
	ppc->SetIntrinsicsHW(1.0f, 1000.0f);
	// set extrinsics
	ppc->SetExtrinsicsHW();

	// per frame initialization
	cgi->EnableProfiles();
	soi->PerFrameInit();
	soi->BindPrograms();

	// render geometry
	for (int tmi = 0; tmi < tmsN; tmi++) {
		if (!tms[tmi].enabled)
			continue;
		tms[tmi].RenderHW();
	}

	cgi->DisableProfiles();


}


void Scene::RenderRT(FrameBuffer *currfb, PPC *currppc) {

	for (int v = 0; v < currfb->h; v++) {
		for (int u = 0; u < currfb->w; u++) {
			V3 ray = currppc->c + currppc->a*(.5f + (float)u) +
				currppc->b*(.5f + (float)v);
			ray = ray.UnitVector();
			V3 O = currppc->C, rO, rray;
			unsigned int col = TraceRay(0, O, ray, rO, rray), rcol;
			if (rO[0] != FLT_MAX) {
				V3 rrO, rrray;
				rcol = TraceRay(1, rO, rray, rrO, rrray);
				if (rcol == 0xFF00FF00)
					rcol = col;
				V3 c0; c0.SetColor(col);
				V3 c1; c1.SetColor(rcol);
				float diffk = 0.75f;
				col = (c0*diffk + c1*(1.0f-diffk)).GetColor();
			}
			currfb->Set(u, v, col);
		}
		if (v < currfb->h - 1) {
			for (int u = 0; u < currfb->w; u++) {
				currfb->Set(u, v + 1, 0xFF0000FF);
			}
		}
		if ((v % 10) == 0 || v == currfb->h-1) {
			currfb->redraw();
			Fl::check();
		}
	}
}

void Scene::Intersect(V3 Vs[3], V3 O, V3 ray, V3 &abc, float &t) {

	M33 V;
	V.SetColumn(0, Vs[0]);
	V.SetColumn(1, Vs[1]);
	V.SetColumn(2, Vs[2]);
	V = V.Inverted();
	V3 q0 = V*O;
	V3 q1 = V*ray;
	t = (1.0f - q0[0] - q0[1] - q0[2]) / (q1[0] + q1[1] + q1[2]);
	abc = q0 + q1*t;
}

unsigned int Scene::TraceRay(int orderi, V3 O, V3 ray, V3 &rO, V3 &rray) {

	unsigned int ret = 0xFF00FF00;
	float tmin = FLT_MAX;
	rO[0] = FLT_MAX;
	for (int tmi = 0; tmi < tmsN; tmi++) {
		if (!tms[tmi].enabled)
			continue;
		TM &tm = tms[tmi];
		for (int tri = 0; tri < tm.trisN; tri++) {
			V3 Vs[3];
			Vs[0] = tm.verts[tm.tris[3 * tri + 0]];
			Vs[1] = tm.verts[tm.tris[3 * tri + 1]];
			Vs[2] = tm.verts[tm.tris[3 * tri + 2]];
			V3 Cs[3];
			Cs[0] = tm.cols[tm.tris[3 * tri + 0]];
			Cs[1] = tm.cols[tm.tris[3 * tri + 1]];
			Cs[2] = tm.cols[tm.tris[3 * tri + 2]];
			V3 Ns[3];
			if (tm.normals) {
				Ns[0] = tm.normals[tm.tris[3 * tri + 0]];
				Ns[1] = tm.normals[tm.tris[3 * tri + 1]];
				Ns[2] = tm.normals[tm.tris[3 * tri + 2]];
			}
			V3 abc; // barycentric coordinates of intersection point
			float t;
			Intersect(Vs, O, ray, abc, t);
			if (t < 0.01f || abc[0] < 0.0f || abc[1] < 0.0f || abc[2] < 0.0f ||
				t > tmin)
				continue;
			tmin = t;
			ret = (Cs[0] * abc[0] + Cs[1] * abc[1] + Cs[2] * abc[2]).GetColor();
			if (tm.normals && orderi < 1) {
				rO = O + ray*t;
				V3 normal = (Ns[0] * abc[0] + Ns[1] * abc[1] + Ns[2] * abc[2]).UnitVector();
				V3 ev = ray*-1.0f; 
				V3 evn = normal*(ev*normal); // normal component of eye vector
				rray = (evn*2.0f - ev).UnitVector(); // reflected vector
			}
		}
	}

	return ret;

}
