#ifndef FLOORRENDERER_H
#define FLOORRENDERER_H

#include <QOpenGLFunctions>
#include <QOpenGLShaderProgram>
#include <QOpenGLBuffer>
#include <Indoor/floorplan/v2/Floorplan.h>
#include "GL.h"


class FloorRenderer : protected QOpenGLFunctions {

private:

	Floorplan::Floor* floor;

	QOpenGLBuffer arrayBuf;
	QOpenGLBuffer indexBuf;
	QOpenGLTexture* texture = nullptr;

	std::vector<VertNormTex> vertices;
	std::vector<GLushort> indices;


public:

	/** ctor */
	FloorRenderer(Floorplan::Floor* floor) : floor(floor), arrayBuf(QOpenGLBuffer::VertexBuffer), indexBuf(QOpenGLBuffer::IndexBuffer) {
		;
	}

	/** dctor */
	~FloorRenderer() {
		arrayBuf.destroy();
		indexBuf.destroy();
		delete texture;
	}

	/** render the floor */
	void render(QOpenGLShaderProgram *program) {

		// Tell OpenGL which VBOs to use
		arrayBuf.bind();
		indexBuf.bind();
		texture->bind();

		// vertices
		int vertLoc = program->attributeLocation("a_position");
		program->enableAttributeArray(vertLoc);
		program->setAttributeBuffer(vertLoc, GL_FLOAT, vertices[0].getVertOffset(), 3, sizeof(vertices[0]));

		// Tell OpenGL programmable pipeline how to locate vertex texture coordinate data
		int normLoc = program->attributeLocation("a_normal");
		program->enableAttributeArray(normLoc);
		program->setAttributeBuffer(normLoc, GL_FLOAT, vertices[0].getNormOffset(), 3, sizeof(vertices[0]));

		int texcoordLocation = program->attributeLocation("a_texcoord");
		program->enableAttributeArray(texcoordLocation);
		program->setAttributeBuffer(texcoordLocation, GL_FLOAT, vertices[0].getTexOffset(), 2, sizeof(vertices[0]));

		// texture
		program->setUniformValue("texture", 0);

		// Draw cube geometry using indices from VBO 1
		glDrawElements(GL_QUADS, indices.size(), GL_UNSIGNED_SHORT, 0);

	}

	void initGL() {
		initializeOpenGLFunctions();
		build();
	}

private:

	void build() {

		QImage img(":/res/gl/tex/wall1.jpg");
		texture = new QOpenGLTexture(img);
		texture->setMinificationFilter(QOpenGLTexture::Nearest);
		texture->setMagnificationFilter(QOpenGLTexture::Linear);
		texture->setWrapMode(QOpenGLTexture::Repeat);

		// build array of vertices and indices
		for (Floorplan::FloorObstacle* obstacle : floor->obstacles) {
			add(floor, obstacle, vertices, indices);
		}

		// Transfer vertex data to VBO 0
		arrayBuf.create();
		arrayBuf.bind();
		arrayBuf.allocate(vertices.data(), vertices.size() * sizeof(vertices[0]));

		// Transfer index data to VBO 1
		indexBuf.create();
		indexBuf.bind();
		indexBuf.allocate(indices.data(), indices.size() * sizeof(indices[0]));

	}

	void add(Floorplan::Floor* floor, Floorplan::FloorObstacle* obstacle, std::vector<VertNormTex>& vertices, std::vector<GLushort>& indices) {

		if (dynamic_cast<Floorplan::FloorObstacleLine*>(obstacle)) {

			Floorplan::FloorObstacleLine* line = dynamic_cast<Floorplan::FloorObstacleLine*>(obstacle);
			if (line->type != Floorplan::ObstacleType::WALL) {return;}

//			QVector3D p1(line->from.x, floor->getStartingZ(), line->from.y);
//			QVector3D p2(line->to.x, floor->getStartingZ(), line->to.y);
//			QVector3D p3(line->to.x, floor->getEndingZ(), line->to.y);
//			QVector3D p4(line->from.x, floor->getEndingZ(), line->from.y);

//			addFace(p1,p2,p3,p4, vertices, indices);


			addFace(line->from, line->to, floor->getStartingZ(), floor->getEndingZ(), vertices, indices);
			addFace(line->to, line->from, floor->getStartingZ(), floor->getEndingZ(), vertices, indices);

		}

	}

	void addFace(const Point2 from, const Point2 to, const float h1, const float h2, std::vector<VertNormTex>& vertices, std::vector<GLushort>& indices) {

		// unit-face with unit normal (facing the camera)
		QVector3D p1(-0.5, -0.0, 0);
		QVector3D p2(+0.5, -0.0, 0);
		QVector3D p3(+0.5, +1.0, 0);
		QVector3D p4(-0.5, +1.0, 0);
		QVector3D norm(0, 0, 1);

		// how to scale the unit-face to match the wall
		const float h = h2-h1;
		const float s = from.getDistance(to);
		QMatrix4x4 scale; scale.scale(s, h, 1);

		// how to rotate the unit-face to match the wall
		const float angle = std::atan2(to.y - from.y, to.x - from.x);
		const float deg = angle * 180 / M_PI;
		QMatrix4x4 rot; rot.rotate(deg, QVector3D(0,1,0));

		// how to translate the unit-face to match the wall
		const Point2 center = (from + to) / 2;
		QMatrix4x4 move; move.translate(center.x, h1, center.y);

		// final matrix
		QMatrix4x4 mat = move * rot * scale;

		// texture coordinates (scale only)
		const QVector2D tex1 = (scale * p1).toVector2D() / 5;
		const QVector2D tex2 = (scale * p2).toVector2D() / 5;
		const QVector2D tex3 = (scale * p3).toVector2D() / 5;
		const QVector2D tex4 = (scale * p4).toVector2D() / 5;

		// modify vertices
		p1 = mat * p1;
		p2 = mat * p2;
		p3 = mat * p3;
		p4 = mat * p4;
		norm = (rot * norm).normalized();

		const int start = vertices.size();
		vertices.push_back(VertNormTex(p1, norm, tex1));
		vertices.push_back(VertNormTex(p2, norm, tex2));
		vertices.push_back(VertNormTex(p3, norm, tex3));
		vertices.push_back(VertNormTex(p4, norm, tex4));

		indices.push_back(start+0);
		indices.push_back(start+1);
		indices.push_back(start+2);
		indices.push_back(start+3);

	}

//	void addFace(QVector3D p1, QVector3D p2, QVector3D p3, QVector3D p4, std::vector<VertNorm>& vertices, std::vector<GLushort>& indices) {

//		const float s = 50;

//		// ensure camera facing (for culling)
//		if (p1.x() != p2.x()) {
//			if (p1.x() > p2.x()) {std::swap(p1, p2), std::swap(p3, p4);}
//		} else {
//			if (p1.z() > p2.z()) {std::swap(p1, p2), std::swap(p3, p4);}
//		}

//		// corresponding normal vector
//		QVector3D norm = QVector3D::crossProduct((p2-p1), (p3-p1)).normalized();

//		// orient towards the viewport
//		const QVector3D view(-99,-99,-99);
//		if ((view-norm).length() > (view+norm).length()) {norm = -norm;}


//		const int start = vertices.size();

//		indices.push_back(start+0);
//		indices.push_back(start+1);
//		indices.push_back(start+2);
//		indices.push_back(start+3);

//		vertices.push_back(VertNorm(p1/s, norm));
//		vertices.push_back(VertNorm(p2/s, norm));
//		vertices.push_back(VertNorm(p3/s, norm));
//		vertices.push_back(VertNorm(p4/s, norm));

//		// and the other side (clockwise, negated normal)

//		indices.push_back(start+0);
//		indices.push_back(start+3);
//		indices.push_back(start+2);
//		indices.push_back(start+1);

//		vertices.push_back(VertNorm(p1/s, -norm));
//		vertices.push_back(VertNorm(p2/s, -norm));
//		vertices.push_back(VertNorm(p3/s, -norm));
//		vertices.push_back(VertNorm(p4/s, -norm));

//	}

};

#endif // FLOORRENDERER_H
