#include <main.h>
#include <Fenster.h>
#include <Bildschirm.h>
#include <RenderThread.h>
#include <Welt3D.h>
#include <Cube.h>
#include <Kam3D.h>
#include <Bild.h>
#include <Rahmen.h>
#include <Textur.h>
#include <Model3D.h>
#include <Model3DList.h>
#include <DXBuffer.h>
#include <DateiSystem.h>
#include <M3Datei.h>
#include <GraphicsApi.h>
#include <Mat4.h>
#include <M3Datei.h>
#include <ObjFile.h>
#include <Vec3.h>
#include <TextFeld.h>

#define ambient  0.4f
#define diffus   0.3f
#define specular 0.3f

using namespace Framework;


void makeQuader(Vertex3D* vertecies, int startIndex, Vec3< float > ltf, Vec3< float > rtf, Vec3< float > lbf, Vec3< float > rbf, Vec3< float > ltb, Vec3< float > rtb, Vec3< float > lbb, Vec3< float > rbb, Model3DData* zModel)
{
	// front
	vertecies[startIndex].pos = ltf;
	vertecies[startIndex].tPos = Vec2< float >(0.f, 0.f);
	vertecies[startIndex + 1].pos = rtf;
	vertecies[startIndex + 1].tPos = Vec2< float >(1.f, 0.f);
	vertecies[startIndex + 2].pos = lbf;
	vertecies[startIndex + 2].tPos = Vec2< float >(0.f, 1.f);
	vertecies[startIndex + 3].pos = rbf;
	vertecies[startIndex + 3].tPos = Vec2< float >(1.f, 1.f);
	// back
	vertecies[startIndex + 4].pos = rtb;
	vertecies[startIndex + 4].tPos = Vec2< float >(0.0f, 0.0f);
	vertecies[startIndex + 5].pos = ltb;
	vertecies[startIndex + 5].tPos = Vec2< float >(1.0f, 0.0f);
	vertecies[startIndex + 6].pos = rbb;
	vertecies[startIndex + 6].tPos = Vec2< float >(0.0f, 1.0f);
	vertecies[startIndex + 7].pos = lbb;
	vertecies[startIndex + 7].tPos = Vec2< float >(1.0f, 1.0f);
	// left
	vertecies[startIndex + 8].pos = ltb;
	vertecies[startIndex + 8].tPos = Vec2< float >(1.f, 0.f);
	vertecies[startIndex + 9].pos = ltf;
	vertecies[startIndex + 9].tPos = Vec2< float >(0.f, 0.f);
	vertecies[startIndex + 10].pos = lbb;
	vertecies[startIndex + 10].tPos = Vec2< float >(1.f, 1.f);
	vertecies[startIndex + 11].pos = lbf;
	vertecies[startIndex + 11].tPos = Vec2< float >(0.f, 1.f);
	// right
	vertecies[startIndex + 12].pos = rtf;
	vertecies[startIndex + 12].tPos = Vec2< float >(0.0f, 0.0f);
	vertecies[startIndex + 13].pos = rtb;
	vertecies[startIndex + 13].tPos = Vec2< float >(1.0f, 0.0f);
	vertecies[startIndex + 14].pos = rbf;
	vertecies[startIndex + 14].tPos = Vec2< float >(0.0f, 1.0f);
	vertecies[startIndex + 15].pos = rbb;
	vertecies[startIndex + 15].tPos = Vec2< float >(1.0f, 1.0f);
	// top
	vertecies[startIndex + 16].pos = ltb;
	vertecies[startIndex + 16].tPos = Vec2< float >(0.f, 0.f);
	vertecies[startIndex + 17].pos = rtb;
	vertecies[startIndex + 17].tPos = Vec2< float >(1.f, 0.f);
	vertecies[startIndex + 18].pos = ltf;
	vertecies[startIndex + 18].tPos = Vec2< float >(0.f, 1.f);
	vertecies[startIndex + 19].pos = rtf;
	vertecies[startIndex + 19].tPos = Vec2< float >(1.f, 1.f);
	// botom
	vertecies[startIndex + 20].pos = lbf;
	vertecies[startIndex + 20].tPos = Vec2< float >(0.0f, 0.0f);
	vertecies[startIndex + 21].pos = rbf;
	vertecies[startIndex + 21].tPos = Vec2< float >(1.0f, 0.0f);
	vertecies[startIndex + 22].pos = lbb;
	vertecies[startIndex + 22].tPos = Vec2< float >(0.0f, 1.0f);
	vertecies[startIndex + 23].pos = rbb;
	vertecies[startIndex + 23].tPos = Vec2< float >(1.0f, 1.0f);

	// front side
	Polygon3D* p = new Polygon3D();
	p->indexAnz = 6;
	p->indexList = new int[p->indexAnz];
	p->indexList[0] = startIndex + 0;
	p->indexList[1] = startIndex + 2;
	p->indexList[2] = startIndex + 1;
	p->indexList[3] = startIndex + 1;
	p->indexList[4] = startIndex + 2;
	p->indexList[5] = startIndex + 3;
	zModel->addPolygon(p);
	// back side
	p = new Polygon3D();
	p->indexAnz = 6;
	p->indexList = new int[p->indexAnz];
	p->indexList[0] = startIndex + 0 + 4;
	p->indexList[1] = startIndex + 2 + 4;
	p->indexList[2] = startIndex + 1 + 4;
	p->indexList[3] = startIndex + 1 + 4;
	p->indexList[4] = startIndex + 2 + 4;
	p->indexList[5] = startIndex + 3 + 4;
	zModel->addPolygon(p);
	// left side
	p = new Polygon3D();
	p->indexAnz = 6;
	p->indexList = new int[p->indexAnz];
	p->indexList[0] = startIndex + 0 + 8;
	p->indexList[1] = startIndex + 2 + 8;
	p->indexList[2] = startIndex + 1 + 8;
	p->indexList[3] = startIndex + 1 + 8;
	p->indexList[4] = startIndex + 2 + 8;
	p->indexList[5] = startIndex + 3 + 8;
	zModel->addPolygon(p);
	// right side
	p = new Polygon3D();
	p->indexAnz = 6;
	p->indexList = new int[p->indexAnz];
	p->indexList[0] = startIndex + 0 + 12;
	p->indexList[1] = startIndex + 2 + 12;
	p->indexList[2] = startIndex + 1 + 12;
	p->indexList[3] = startIndex + 1 + 12;
	p->indexList[4] = startIndex + 2 + 12;
	p->indexList[5] = startIndex + 3 + 12;
	zModel->addPolygon(p);
	// top side
	p = new Polygon3D();
	p->indexAnz = 6;
	p->indexList = new int[p->indexAnz];
	p->indexList[0] = startIndex + 0 + 16;
	p->indexList[1] = startIndex + 2 + 16;
	p->indexList[2] = startIndex + 1 + 16;
	p->indexList[3] = startIndex + 1 + 16;
	p->indexList[4] = startIndex + 2 + 16;
	p->indexList[5] = startIndex + 3 + 16;
	zModel->addPolygon(p);
	// bottom side
	p = new Polygon3D();
	p->indexAnz = 6;
	p->indexList = new int[p->indexAnz];
	p->indexList[0] = startIndex + 0 + 20;
	p->indexList[1] = startIndex + 2 + 20;
	p->indexList[2] = startIndex + 1 + 20;
	p->indexList[3] = startIndex + 1 + 20;
	p->indexList[4] = startIndex + 2 + 20;
	p->indexList[5] = startIndex + 3 + 20;
	zModel->addPolygon(p);
}

void makeQuader(Vertex3D* vertecies, int startIndex, Vec3< float > pos, Vec3< float > size, Model3DData* zModel)
{
	float left, right, top, bottom;
	// Calculate the screen coordinates of the left side of the bitmap.
	left = pos.x;
	// Calculate the screen coordinates of the right side of the bitmap.
	right = pos.x + size.x;
	// Calculate the screen coordinates of the top of the bitmap.
	top = pos.z + size.z;
	// Calculate the screen coordinates of the bottom of the bitmap.
	bottom = pos.z;
	float front = pos.y;
	float back = pos.y + size.y;

	makeQuader(vertecies, startIndex, Vec3< float >(left, front, top), Vec3< float >(right, front, top), Vec3< float >(left, front, bottom), Vec3< float >(right, front, bottom),
		Vec3< float >(left, back, top), Vec3< float >(right, back, top), Vec3< float >(left, back, bottom), Vec3< float >(right, back, bottom), zModel);
}

class Block : public Model3D
{
public:
	// Konstruktor
	//  size: Die Größe des Würfels
	Block(GraphicsApi* zApi)
	{
		model = zApi->createModel("block");
		model->setAmbientFactor(ambient);
		model->setDiffusFactor(diffus);
		model->setSpecularFactor(specular);

		Vertex3D* vertecies = new Vertex3D[24];
		for (int i = 0; i < 24; i++)
			vertecies[i].knochenId = 0;
		makeQuader(vertecies, 0, Vec3< float >(-0.5, -0.5, -0.5), Vec3< float >(1, 1, 1), model);
		model->setVertecies(vertecies, 24);

		model->calculateNormals();

		textur = new Model3DTextur();
		Bild* b = new Bild();
		b->neuBild(10, 10, 0xFFFF5050);
		textur->setPolygonTextur(0, zApi->createOrGetTextur("blockA", b)); // front
		b = new Bild();
		b->neuBild(10, 10, 0xFFA02020);
		textur->setPolygonTextur(1, zApi->createOrGetTextur("blockB", b)); // back
		b = new Bild();
		b->neuBild(10, 10, 0xFF5050FF);
		textur->setPolygonTextur(2, zApi->createOrGetTextur("blockC", b)); // left
		b = new Bild();
		b->neuBild(10, 10, 0xFF2020A0);
		textur->setPolygonTextur(3, zApi->createOrGetTextur("blockD", b)); // right
		b = new Bild();
		b->neuBild(10, 10, 0xFF50FF50);
		textur->setPolygonTextur(4, zApi->createOrGetTextur("blockE", b)); // top
		b = new Bild();
		b->neuBild(10, 10, 0xFF20A020);
		textur->setPolygonTextur(5, zApi->createOrGetTextur("blockF", b)); // botom
		rend = 1;
	}
};

class ObjModel : public Model3D
{
private:
	Text modelName;

public:
	ObjModel(GraphicsApi* zApi, Text path, Text textur, Text modelName)
		: Model3D()
	{
		Text* p = path.getTeilText(0, path.positionVon(".obj/", path.anzahlVon(".obj/") - 1) + 4);
		ObjFile file(p->getText());
		Model3DData* data = zApi->createModel(path);
		data->setAmbientFactor(ambient);
		data->setDiffusFactor(diffus);
		data->setSpecularFactor(specular);
		file.loadModel(path.getText() + p->getLength() + 1, data);
		Vertex3D* vb = new Vertex3D[data->getVertexAnzahl()];
		for (int i = 0; i < data->getVertexAnzahl(); i++)
		{
			vb[i] = data->zVertexBuffer()[i];
			vb[i].tPos.y = 1 - vb[i].tPos.y;
		}
		data->setVertecies(vb, data->getVertexAnzahl());
		setModelDaten(data);
		p->release();
		p = textur.getTeilText(0, textur.positionVon(".ltdb/", textur.anzahlVon(".ltdb/") - 1) + 5);
		LTDBDatei dat;
		dat.setDatei(p);
		dat.leseDaten(0);
		Bild* t = dat.laden(0, textur.getTeilText(textur.positionVon(".ltdb/", textur.anzahlVon(".ltdb/") - 1) + 6));
		Model3DTextur* tex = new Model3DTextur();
		tex->setPolygonTextur(0, zApi->createOrGetTextur(textur, t));
		this->textur = tex;
		adjustCenter();
		rend = 1;
		this->modelName = modelName;
	}

	void adjustCenter()
	{
		Vec3<float> maxPos = { -INFINITY, -INFINITY, -INFINITY };
		Vec3<float> minPos = { INFINITY, INFINITY, INFINITY };
		for (int i = 0; i < model->getVertexAnzahl(); i++)
		{
			if (model->zVertexBuffer()[i].pos.x < minPos.x)
				minPos.x = model->zVertexBuffer()[i].pos.x;
			if (model->zVertexBuffer()[i].pos.y < minPos.y)
				minPos.y = model->zVertexBuffer()[i].pos.y;
			if (model->zVertexBuffer()[i].pos.z < minPos.z)
				minPos.z = model->zVertexBuffer()[i].pos.z;
			if (model->zVertexBuffer()[i].pos.x > maxPos.x)
				maxPos.x = model->zVertexBuffer()[i].pos.x;
			if (model->zVertexBuffer()[i].pos.y > maxPos.y)
				maxPos.y = model->zVertexBuffer()[i].pos.y;
			if (model->zVertexBuffer()[i].pos.z > maxPos.z)
				maxPos.z = model->zVertexBuffer()[i].pos.z;
		}
		Vec3<float> center = minPos + (maxPos - minPos) / 2;
		if (center != Vec3<float>(0.f, 0.f, 0.f))
		{
			for (int i = 0; i < model->getVertexAnzahl(); i++)
			{
				// !! update of vertex data is not supported by the framework and does not update some stored information on the model like bounding box and radius
				((Vertex3D*)model->zVertexBuffer())[i].pos -= center;
			}
			model->zDXVertexBuffer()->setChanged();
			model->updateGPUMemory();
		}
	}

	bool tick(double time) override
	{
		if (getTastenStand(VK_OEM_MINUS))
		{
			setSize(size - (size - (size * 0.995)) * (time * 3));
		}
		if (getTastenStand(VK_OEM_PLUS))
		{
			setSize(size + (size * 1.005 - size) * (time * 3));
		}
		if (getTastenStand(VK_NUMPAD1))
		{
			setDrehungX(getDrehung().x + time * 0.1);
		}
		if (getTastenStand(VK_NUMPAD4))
		{
			setDrehungX(getDrehung().x - time * 0.1);
		}
		if (getTastenStand(VK_NUMPAD2))
		{
			setDrehungY(getDrehung().y + time * 0.1);
		}
		if (getTastenStand(VK_NUMPAD5))
		{
			setDrehungY(getDrehung().y - time * 0.1);
		}
		if (getTastenStand(VK_NUMPAD3))
		{
			setDrehungZ(getDrehung().z + time * 0.1);
		}
		if (getTastenStand(VK_NUMPAD6))
		{
			setDrehungZ(getDrehung().z - time * 0.1);
		}
		if (getTastenStand(T_Enter))
		{
			if (size != 1.f || getDrehung() != Vec3<float>(0.f, 0.f, 0.f))
			{
				// aply scale and rotation on model verticies
				Mat4<float> matrix = Mat4<float>::identity() * Mat4<float>::rotationZ(angle.z) * Mat4<float>::rotationX(angle.x) * Mat4<float>::rotationY(angle.y) * Mat4<float>::scaling(size);
				for (int i = 0; i < model->getVertexAnzahl(); i++)
				{
					// !! update of vertex data is not supported by the framework and does not update some stored information on the model like bounding box and radius
					((Vertex3D*)model->zVertexBuffer())[i].pos = matrix * model->zVertexBuffer()[i].pos;
					((Vertex3D*)model->zVertexBuffer())[i].normal = (matrix * model->zVertexBuffer()[i].normal).normalize();
				}
				model->zDXVertexBuffer()->setChanged();
				model->updateGPUMemory();
				size = 1.f;
				setDrehung(0.f, 0.f, 0.f);
				adjustCenter();
			}
		}
		if (getTastenStand(T_Ende))
		{
			// save model
			M3Datei dat("m3/blocks.m3");
			dat.leseDaten();
			dat.removeModel(modelName);
			if (dat.saveModel(model, modelName))
			{
				std::cout << "erfolgreich gespeichert\n";
			}
			else
			{
				std::cout << "fehler beim speichern\n";
			}
		}
		return Model3D::tick(time);
	}
};

// Aktion beim schließen des Fensters
void schließen(void* p, void* f)
{
	// Stoppe Nachrichten Loop
	StopNachrichtenSchleife(((WFenster*)f)->getFensterHandle());
}

int __stdcall Framework::Start(Startparam p)
{
	// Fenster erstellen
	WNDCLASS wc = F_Normal(p.hinst);
	wc.lpszClassName = "3D Creator";

	WFenster* f = new WFenster();
	f->erstellen(WS_OVERLAPPEDWINDOW, wc);
	f->setVSchließAktion(schließen);
	f->setAnzeigeModus(SW_SHOWNORMAL);
	f->setSize(1300, 820);
	f->setPosition(Punkt(100, 100));

	// 3D Umgebung erstellen
	Bildschirm3D* b = new Bildschirm3D(dynamic_cast<WFenster*>(f->getThis()), DIRECTX11);
	f->setBildschirm(dynamic_cast<Bildschirm*>(b->getThis()));
	b->setFillFarbe(0);
	b->setTestRend(0);

	Welt3D* welt = new Welt3D();

	ObjModel* mdl = new ObjModel(b->zGraphicsApi(), "obj/sebling.obj/Cube", "tex/blocks.ltdb/sebling.png", "sebling");
	mdl->setPosition(0, 0, 0);
	welt->addZeichnung(mdl);
	Block* blk = new Block(b->zGraphicsApi());
	blk->setPosition(0, 0, -1);
	welt->addZeichnung(blk);

	welt->addDiffuseLight({ Vec3<float>(-0.5f, -0.5f, -0.5f), Vec3<float>(1.f, 1.f, 1.f) });

	Kam3D* kamA = new Kam3D(); // look at front
	kamA->setBildschirmPosition(10, 10);
	kamA->setBildschirmSize(385, 385);
	kamA->setPosition(Vec3<float>(0, -10, 0));
	kamA->setRotation({ -(float)PI / 2.f, -(float)PI, 0 });
	kamA->setWelt(dynamic_cast<Welt3D*>(welt->getThis()));
	b->addKamera(kamA);

	Kam3D* kamB = new Kam3D(); // look at left
	kamB->setMovementSpeed(1);
	kamB->setBildschirmPosition(10, 405);
	kamB->setBildschirmSize(385, 385);
	kamB->setPosition(Vec3<float>(10, 0, 0));
	kamB->setRotation({ -PI / 2, (float)PI, (float)PI / 2.f });
	kamB->setWelt(dynamic_cast<Welt3D*>(welt->getThis()));
	b->addKamera(kamB);

	Kam3D* kamC = new Kam3D();
	kamC->setBildschirmPosition(405, 10);
	kamC->setBildschirmSize(385, 385);
	kamC->setPosition(Vec3<float>(0, 0, 10));
	kamC->setRotation({ -(float)PI, 0, 0});
	kamC->setWelt(dynamic_cast<Welt3D*>(welt->getThis()));
	b->addKamera(kamC);

	Kam3D* kamD = new Kam3D();
	kamD->setStyle(Kam3D::Style::Movable | Kam3D::Style::Rotatable | Kam3D::Style::Tick);
	kamD->setBildschirmPosition(405, 405);
	kamD->setBildschirmSize(385, 385);
	kamD->setPosition(Vec3<float>(0, -10, 0));
	kamD->setRotation({ -(float)PI / 2.f, -(float)PI, 0 });
	kamD->setWelt(dynamic_cast<Welt3D*>(welt->getThis()));
	b->addKamera(kamD);

	LTDSDatei dat;
	dat.setPfad(new Text("normal.ltds"));
	dat.leseDaten();
	Schrift *schrift = dat.ladeSchrift();
	TextFeld *kamPos = new TextFeld();
	kamPos->setSchriftZ(schrift);
	kamPos->setPosition(810, 10);
	kamPos->setSize(200, 200);
	kamPos->setText("Kamera Position");
	kamPos->setStyle(TextFeld::Style::Text);
	b->addMember(kamPos);

	LRahmen* kamAR = new LRahmen();
	kamAR->setPosition(10, 10);
	kamAR->setFarbe(0xFFFFFFFF);
	kamAR->setRamenBreite(1);
	kamAR->setSize(385, 385);
	b->addMember(kamAR);
	LRahmen* kamBR = new LRahmen();
	kamBR->setPosition(404, 10);
	kamBR->setFarbe(0xFFFFFFFF);
	kamBR->setRamenBreite(1);
	kamBR->setSize(385, 385);
	b->addMember(kamBR);
	LRahmen* kamCR = new LRahmen();
	kamCR->setPosition(10, 405);
	kamCR->setFarbe(0xFFFFFFFF);
	kamCR->setRamenBreite(1);
	kamCR->setSize(385, 385);
	b->addMember(kamCR);
	LRahmen* kamDR = new LRahmen();
	kamDR->setPosition(405, 405);
	kamDR->setFarbe(0xFFFFFFFF);
	kamDR->setRamenBreite(1);
	kamDR->setSize(385, 385);
	b->addMember(kamDR);

	// Render Loop starten
	RenderTh* r = new RenderTh();
	r->setMaxFps(10000);
	r->setBildschirm(dynamic_cast<Bildschirm*>(b->getThis()));
	r->setTickFunktion([b, kamD, kamA, kamB, kamC, kamPos](void*, void*, double t)
		{
			if (Framework::getTastenStand('1'))
			{
				kamA->setStyle(Kam3D::Style::Movable | Kam3D::Style::Rotatable | Kam3D::Style::Tick);
				kamB->removeStyle(Kam3D::Style::Movable | Kam3D::Style::Rotatable | Kam3D::Style::Tick);
				kamC->removeStyle(Kam3D::Style::Movable | Kam3D::Style::Rotatable | Kam3D::Style::Tick);
				kamD->removeStyle(Kam3D::Style::Movable | Kam3D::Style::Rotatable | Kam3D::Style::Tick);
			}
			if (Framework::getTastenStand('2'))
			{
				kamA->removeStyle(Kam3D::Style::Movable | Kam3D::Style::Rotatable | Kam3D::Style::Tick);
				kamB->setStyle(Kam3D::Style::Movable | Kam3D::Style::Rotatable | Kam3D::Style::Tick);
				kamC->removeStyle(Kam3D::Style::Movable | Kam3D::Style::Rotatable | Kam3D::Style::Tick);
				kamD->removeStyle(Kam3D::Style::Movable | Kam3D::Style::Rotatable | Kam3D::Style::Tick);
			}
			if (Framework::getTastenStand('3'))
			{
				kamA->removeStyle(Kam3D::Style::Movable | Kam3D::Style::Rotatable | Kam3D::Style::Tick);
				kamB->removeStyle(Kam3D::Style::Movable | Kam3D::Style::Rotatable | Kam3D::Style::Tick);
				kamC->setStyle(Kam3D::Style::Movable | Kam3D::Style::Rotatable | Kam3D::Style::Tick);
				kamD->removeStyle(Kam3D::Style::Movable | Kam3D::Style::Rotatable | Kam3D::Style::Tick);
			}
			if (Framework::getTastenStand('4'))
			{
				kamA->removeStyle(Kam3D::Style::Movable | Kam3D::Style::Rotatable | Kam3D::Style::Tick);
				kamB->removeStyle(Kam3D::Style::Movable | Kam3D::Style::Rotatable | Kam3D::Style::Tick);
				kamC->removeStyle(Kam3D::Style::Movable | Kam3D::Style::Rotatable | Kam3D::Style::Tick);
				kamD->setStyle(Kam3D::Style::Movable | Kam3D::Style::Rotatable | Kam3D::Style::Tick);
			}
			Text txt = Text("A: ") + kamA->getWorldPosition().x + ", " + kamA->getWorldPosition().y + ", " + kamA->getWorldPosition().z + "\n";
			txt += Text("B: ") + kamB->getWorldPosition().x + ", " + kamB->getWorldPosition().y + ", " + kamB->getWorldPosition().z + "\n";
			txt += Text("C: ") + kamC->getWorldPosition().x + ", " + kamC->getWorldPosition().y + ", " + kamC->getWorldPosition().z + "\n";
			txt += Text("D: ") + kamD->getWorldPosition().x + ", " + kamD->getWorldPosition().y + ", " + kamD->getWorldPosition().z + "\n";
			kamPos->setText(txt);
		});
	r->beginn();

	// Nachrichten Loop starten und warten
	StartNachrichtenSchleife();

	// Speicher Freigeben
	r->beenden();
	r->release();
	b->release();
	f->setBildschirm(0);
	f->zerstören();
	f->release();
	welt->release();

	return 0;
}