#ifndef FLOOR2D_H
#define FLOOR2D_H

#include <Indoor/floorplan/v2/Floorplan.h>
#include "Renderable2D.h"
#include <QLinearGradient>

/**
 * draw the floor itself (outline, obstacles)
 */
class Floor2D : public Renderable2D {

private:

	Floorplan::Floor* floor;

public:

	/** ctor */
	Floor2D(Floorplan::Floor* floor) : floor(floor) {

	}

protected:

	void doRender(QPainter& qp, const Scaler2D& s, const RenderParams2D& r) override {

		if (floor->atHeight < r.clip.belowHeight_m) {return;}
		if (floor->atHeight > r.clip.aboveHeight_m) {return;}

		for (const Floorplan::FloorObstacle* obs : floor->obstacles) {
			const Floorplan::FloorObstacleLine* line = dynamic_cast<const Floorplan::FloorObstacleLine*>(obs);
			if (line) {drawObstacle(qp, s, line);}
		}

		for (const Floorplan::FloorOutlinePolygon* poly : floor->outline) {
			drawOutline(qp, s, poly);
		}

		for (const Floorplan::Stair* stair : floor->stairs) {
			const Floorplan::StairFreeform* sf = dynamic_cast<const Floorplan::StairFreeform*>(stair);
			if (sf) {drawStair(qp, s, sf);}
		}

		for (const Floorplan::Elevator* elevator : floor->elevators) {
			drawElevator(qp, s, elevator);
		}


	}

private:

	static inline QPen getPen(Floorplan::Material mat, Floorplan::ObstacleType type) {
		using namespace Floorplan;
		QPen pen; pen.setColor(Qt::darkGray);
		if (mat == Material::CONCRETE) {pen.setWidth(3);}
		if (mat == Material::GLASS) {pen.setStyle(Qt::PenStyle::DotLine);}
		if (type == ObstacleType::HANDRAIL) {pen.setStyle(Qt::PenStyle::DashLine);}
		if (type == ObstacleType::UNKNOWN) {pen.setColor(Qt::red); pen.setWidth(5);}
		if (type == ObstacleType::PILLAR) {pen.setColor(Qt::red); pen.setWidth(5);}

		return pen;
	}

	void drawObstacle(QPainter& qp, const Scaler2D& s, const Floorplan::FloorObstacleLine* line) {
		const Point2 pt1 = s.mapToScreen(line->from);
		const Point2 pt2 = s.mapToScreen(line->to);
		qp.setPen(getPen(line->material, line->type));
		qp.drawLine(pt1.x, pt1.y, pt2.x, pt2.y);
	}

	void drawElevator(QPainter& qp, const Scaler2D& s, const Floorplan::Elevator* elevator) {
		const QPolygon qpoly = toQPolygon(elevator->getPoints(), s);
		qp.setBrush(Qt::gray);
		qp.setPen(Qt::black);
		qp.drawPolygon(qpoly);
	}

	void drawStair(QPainter& qp, const Scaler2D& s, const Floorplan::StairFreeform* stair) {

		std::vector<Floorplan::StairPart> parts = stair->getParts();
		std::vector<Floorplan::Quad3> quads = Floorplan::getQuads(parts, floor);

		for (int i = 0; i < (int) parts.size(); ++i) {

			const Floorplan::StairPart& part = parts[i];
			const Floorplan::Quad3& quad = quads[i];

			const Point2 start = s.mapToScreen(part.start.xy());
			const Point2 end = s.mapToScreen(part.end.xy());

			// fill the polygon with a gradient corresponding with the stair's height relative to the floor's height
			QLinearGradient gradient(start.x, start.y, end.x, end.y);
			const float p1 = 0.1 + clamp01( part.start.z / floor->height) * 0.8;
			const float p2 = 0.1 + clamp01( part.end.z / floor->height) * 0.8;
			gradient.setColorAt(0, QColor(p1*255, p1*255, p1*255));
			gradient.setColorAt(1, QColor(p2*255, p2*255, p2*255));
			qp.setBrush(gradient);
			qp.setPen(QColor(0,0,0,128));

			// polygon-construction
			QPolygon poly;
			poly.push_back(toQPoint(s.mapToScreen(quad.p1.xy())));
			poly.push_back(toQPoint(s.mapToScreen(quad.p2.xy())));
			poly.push_back(toQPoint(s.mapToScreen(quad.p3.xy())));
			poly.push_back(toQPoint(s.mapToScreen(quad.p4.xy())));
			qp.drawPolygon(poly);

		}

	}

	/** draw the given outline polygon */
	void drawOutline(QPainter& qp, const Scaler2D& s, const Floorplan::FloorOutlinePolygon* poly) {

		// configure the drawing
		if (poly->method == Floorplan::OutlineMethod::ADD) {
			qp.setPen(Qt::gray);
			qp.setBrush(Qt::NoBrush);
		} else if (poly->method == Floorplan::OutlineMethod::REMOVE) {
			QBrush brush;
			brush.setStyle(Qt::BrushStyle::DiagCrossPattern);
			brush.setColor(QColor(0,0,0));
			qp.setPen(Qt::gray);
			qp.setBrush(brush);
		}

		// construct the polygon
		const int num = poly->poly.points.size();
		QPolygon qpoly;
		for (int i = 0; i < num; ++i) {
			qpoly.push_back(toQPoint(s.mapToScreen(poly->poly.points[(i+0)])));
		}

		// draw the polygon
		qp.drawPolygon(qpoly);

	}

	/** convert floorplan polygon to QPolygon */
	static inline QPolygon toQPolygon(const Floorplan::Polygon2& poly) {
		QPolygon qpoly;
		for (const Point2 p : poly.points) {
			qpoly.push_back(QPoint(p.x, p.y));
		}
		return qpoly;
	}

	/** convert floorplan polygon to QPolygon */
	static inline QPolygon toQPolygon(const Floorplan::Polygon2& poly, const Scaler2D& s) {
		QPolygon qpoly;
		for (const Point2 _p : poly.points) {
			const Point2 p = s.mapToScreen(_p);
			qpoly.push_back(QPoint(p.x, p.y));
		}
		return qpoly;
	}

	/** convert Point2 to QPoint */
	static inline QPoint toQPoint(const Point2 p) {
		return QPoint(p.x, p.y);
	}

	/** helper method. limit val to  [0:1] */
	static inline float clamp01(const float val) {
		if (val < 0) {return 0;}
		if (val > 1) {return 1;}
		return val;
	}

};

#endif // FLOOR2D_H
