/****************************************************************************
 * MeshLab                                                           o o     *
 * A versatile mesh processing toolbox                             o     o   *
 *                                                                _   O  _   *
 * Copyright(C) 2005                                                \/)\/    *
 * Visual Computing Lab                                            /\/|      *
 * ISTI - Italian National Research Council                           |      *
 *                                                                    \      *
 * All rights reserved.                                                      *
 *                                                                           *
 * This program is free software; you can redistribute it and/or modify      *
 * it under the terms of the GNU General Public License as published by      *
 * the Free Software Foundation; either version 2 of the License, or         *
 * (at your option) any later version.                                       *
 *                                                                           *
 * This program is distributed in the hope that it will be useful,           *
 * but WITHOUT ANY WARRANTY; without even the implied warranty of            *
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the             *
 * GNU General Public License (http://www.gnu.org/licenses/gpl.txt)          *
 * for more details.                                                         *
 *                                                                           *
 ****************************************************************************/

#ifndef EDITPAINT_H
#define EDITPAINT_H

#include <GL/glew.h>
#include <QObject>
#include <QDockWidget>

#include <meshlab/glarea.h>
#include <common/plugins/interfaces/edit_plugin.h>
#include <wrap/gl/pick.h>

#include "paintbox.h"

 /**
  * These options are chosen by each tool in the method setToolType(TolType t)
  */
enum PaintOptions {
	EPP_NONE = 0x00000,
	EPP_PICK_FACES = 0x00001, //On press, pick faces
	EPP_PICK_VERTICES = 0x00003, //On press, find the vertices (implies face picking too)
	EPP_AVG_NORMAL = 0x00007, //During vertex picking, find the average normal of all vertices (implies vertex picking)
	EPP_DRAW_CURSOR = 0x00008  //On mouse move display a shape representing the given cursor
};


/**
 * EditPaint plugin main class (MeshEditing plugin)
 */
class EditPaintPlugin : public QObject, public EditTool {
	Q_OBJECT

public:
	EditPaintPlugin();
	virtual ~EditPaintPlugin();

	static const QString info();

	void suggestedRenderingData(MeshModel &/*m*/, MLRenderingData& /*dt*/);
	bool startEdit(MeshModel &/*m*/, GLArea * /*parent*/, MLSceneGLSharedDataContext* /*cont*/);
	void endEdit(MeshModel &/*m*/, GLArea * /*parent*/, MLSceneGLSharedDataContext* /*cont*/);
	void decorate(MeshModel &/*m*/, GLArea * /*parent*/);
	void mousePressEvent(QMouseEvent *event, MeshModel &/*m*/, GLArea *);
	void mouseMoveEvent(QMouseEvent *event, MeshModel &/*m*/, GLArea *);
	void mouseReleaseEvent(QMouseEvent *event, MeshModel &/*m*/, GLArea *);
	void tabletEvent(QTabletEvent *, MeshModel &, GLArea *);


signals:
	void setSelectionRendering(bool);

public slots:
	void update();
	void setToolType(ToolType t);
	void setBrushSettings(int size, int opacity, int hardness);

private:
	struct PickingData { QPoint position; QPointF rel_position; float distance; };

	void updateSelection(MeshModel &m, std::vector< std::pair<CVertexO *, PickingData> > * vertex_result = NULL);
	void updateColorBuffer(MeshModel& m,MLSceneGLSharedDataContext* shared);
	void updateGeometryBuffers(MeshModel& m,MLSceneGLSharedDataContext* shared);

	double modelview_matrix[16]; //modelview
	double projection_matrix[16]; //projection
	GLint viewport[4];

	GLArea * glarea;
	GLfloat* zbuffer; /*< last known zbuffer, always kept up-to-date */

	QDockWidget* dock;
	Paintbox* paintbox; /*< The current Paintbox*/

	std::vector<CMeshO::FacePointer> * selection; //currently selected faces
	std::vector< std::pair<CVertexO *, PickingData> > vertices; //touched vertices during last updateSelection
	Point3m normal; //average normal of all vertices in "vertices"

	std::vector<QPointF> circle;
	std::vector<QPointF> dense_circle;
	std::vector<QPointF> square;
	std::vector<QPointF> dense_square;

	//New code under this line---

	//Memoization of type and type-settings
	ToolType 		current_type;
	int			 	current_options;

	//Input Events Handling:
	struct InputEvent {
		Qt::MouseButton 		button;
		QEvent::Type 			type;
		QPoint					position;
		QPoint					gl_position;
		Qt::KeyboardModifiers	modifiers;
		qreal					pressure;
		bool					processed;
		bool					valid;
	};

	InputEvent 		latest_event;
	InputEvent		previous_event;

	inline void pushInputEvent(QEvent::Type t, QPoint p,
		Qt::KeyboardModifiers k, qreal pres, Qt::MouseButton b, GLArea * gla)
	{
		if (latest_event.processed) previous_event = latest_event;

		latest_event.type = t;
		latest_event.position = p;
		latest_event.gl_position = QPoint(p.x(), gla->height() - p.y());
		latest_event.modifiers = k;
		latest_event.button = b;
		latest_event.pressure = pres;
		latest_event.processed = false;
		latest_event.valid = true;
	}

	//Brush frequently accessed setting memoization
	struct Brush {
		int 	size;
		int 	opacity;
		int 	hardness;
		float	radius;
		bool	size_map;
		bool	opacity_map;
		bool	hardness_map;
	};

	Brush current_brush;

	/****** Painting, Cloning and Noise Tools ******/
	void paint(std::vector< std::pair<CVertexO *, PickingData> > * vertices);
	void capture();
	bool accessCloneBuffer(int, int, vcg::Color4b &);
	void computeNoiseColor(CVertexO *, vcg::Color4b &);
	QHash<CVertexO *, std::pair<vcg::Color4b, int> >	painted_vertices; /*<active vertices during painting */

	vcg::Color4b	color;

	GLubyte* 		color_buffer; /*< buffer used as color source in cloning*/
	GLfloat* 		clone_zbuffer; /*<buffer to determine if the source is legal or not */
	QPoint 			clone_delta;
	QPoint			source_delta;
	QPoint			apply_start;
	int				buffer_width;
	int				buffer_height;

	float			noise_scale;
	/****** Pull and Push Tools ******/
	void sculpt(MeshModel &, std::vector< std::pair<CVertexO *, PickingData> > * vertices);

	QHash<CVertexO *, std::pair<vcg::Point3f, float> > displaced_vertices; /*<active vertices during sculpting */


	/****** Gradient Tool ******/
	void gradient(MeshModel & m, GLArea * gla);

	QPoint gradient_start;


	/****** Color and Position Smoothing ******/
	void smooth(std::vector< std::pair<CVertexO *, PickingData> > * vertices);
	QHash<CVertexO *, CVertexO *> smoothed_vertices;

	/****** Color Fill ******/
	void fill(MeshModel & m, CFaceO * face);
};

/**
 * Undo Class to store color changes. Intergrates in the Qt Undo framework.
 * Undoes the change to a single vertex only. Must use the Command compression
 * system by Qt.
 * What if the vertex has been deleted in the meantime?
 */
class SingleColorUndo : public QUndoCommand
{

public:
	SingleColorUndo(CVertexO * v, vcg::Color4b c,QUndoCommand * parent = 0) : QUndoCommand(parent) {
		vertex = v; 
		original = c; // setText("Single Vertex Color Change");
	}

	virtual void undo() override
	{ 
		vcg::Color4b temp = vertex->C(); 
		vertex->C() = original; 
		original = temp; 
	}
	virtual void redo() override { undo(); }
	virtual int id() const override { return COLOR_PAINT; }

private:
	CVertexO* vertex;
	vcg::Color4b original;
};

//TODO Create MultipleColorUndo

class SinglePositionUndo : public QUndoCommand
{

public:
	SinglePositionUndo(CVertexO * v, Point3m p, Point3m n, QUndoCommand * parent = 0) : QUndoCommand(parent) {
		vertex = v; original = p; normal = n;
	}

	virtual void undo() override {
		Point3m temp = vertex->P(); vertex->P() = original; original = temp;
		temp = vertex->N(); vertex->N() = normal; normal = temp;
	}
	virtual void redo() override { undo(); }
	virtual int id() const override { return MESH_PULL; }

private:
	CVertexO* vertex;
	Point3m original;
	Point3m normal;
};


/**
 * Undoes changes in selected faces.
 *
 * Same doubt as above about vertex deletion!
 */
class SelectionUndo : public QUndoCommand
{
public:
	SelectionUndo(CFaceO * f, bool sel, QUndoCommand * parent = 0) : QUndoCommand(parent) {
		face = f; original = sel; // setText("Single Vertex Color Change");
	}

	virtual void undo() override { bool temp = face->IsS(); original ? face->SetS() : face->ClearS(); original = temp; }
	virtual void redo() override { undo(); }
	virtual int id() const override { return MESH_SELECT; }
private:
	CFaceO * face;
	bool original;
};

/*********Generic Utility Routines************/

/**
 * Colorizes a vertex with the new color.

 * Has been converted in a for loop. No performance
 * loss since compiler will unroll constant loops
 *
 * O(1) complexity
 */
inline void applyColor(CVertexO * vertex, const vcg::Color4b& newcol, int opac)
{
	vcg::Color4b orig = vertex->C();

	opac *= ((float)newcol[3] / 255.0);

	for (int i = 0; i < 3; i++)
		orig[i] = std::min(255, ((newcol[i] - orig[i]) * opac + orig[i] * 100) / 100);

	vertex->C() = orig;
}

/**
 * Same modification as above
 *
 * O(1) complexity
 */
inline void mergeColors(double percent, const vcg::Color4b& c1, const vcg::Color4b& c2, vcg::Color4b* dest)
{
	for (int i = 0; i < 4; i++)
		(*dest)[i] = (char)std::min(255.0, ((c1[i] - c2[i])*percent + c2[i]));
}

/**
 * The weighted average of the two vectors c1 and c2 is
 * stored in dest. c1's weight is given by percent, while
 * c2's weight is given by 1.0 - percent
 *
 * Can actually merge any 3d array, not just positions
 */
inline void mergePositions(double percent, const float c1[3], const float c2[3], float dest[3])
{
	for (int i = 0; i < 3; i++)
		dest[i] = c1[i] * percent + c2[i] * (1.0 - percent);
}

/*********Geometric Utility Routines************/

/**
 * Quickly ransforms a 3 point with a 44 matrix,
 * avoiding unnecessary computations (the w component is
 * assumed to be 1)
 *
 * Original Code by Gfrei Andreas
 */
inline void fastMultiply(float x, float y, float z, double matrix[], double *xr, double *yr, double *zr)
{
	*xr = x * matrix[0] + y * matrix[4] + z * matrix[8] + matrix[12];
	*yr = x * matrix[1] + y * matrix[5] + z * matrix[9] + matrix[13];
	*zr = x * matrix[2] + y * matrix[6] + z * matrix[10] + matrix[14];
}

/**
 * Finds the nearest point to "center" among "points".
 *
 * O(num)
 */
inline int getNearest(QPointF center, QPointF *points, int num)
{
	using std::abs;
	int index = 0;

	float dist = abs(center.x() - points[0].x())*abs(center.x() - points[0].x()) + abs(center.y() - points[0].y())*abs(center.y() - points[0].y());

	float temp = 0;

	for (int i = 1; i < num; i++)
	{
		temp = abs(center.x() - points[i].x())*abs(center.x() - points[i].x()) +
			abs(center.y() - points[i].y())*abs(center.y() - points[i].y());

		if (temp < dist) {
			index = i;
			dist = temp;
		}
	}
	return index;
}

/**
 * Checks if a point (x, y) is contained in either:
 *
 * a) the rectangle aligned to the segment connecting p0 to p1
 *    whose height is the distance between p0 and p1 and width
 *    is radius * 2
 * b) the circle centered in p0 with given radius
 * c) the circle centered in p1 with same radius
 *
 * To check condition (a): let v be the vector going from p0 to p1
 * and w the vector from p0 to (x, y). If theta is the angle between
 * v and w, then:
 *
 *     |v| |w| cos(theta)     |w|
 * r = ------------------  =  --- cos(theta)
 *            |v|²            |v|
 *
 * is the ratio between the length of vector v and the projection
 * of vector w on vector v.
 *
 * Since w' = v * r is the projection of w on v, pf = p0 + w' is
 * the orthogonal projection of (x, y) on the segment
 * connecting p0 to p1. If the distance between (x, y) and pf is less
 * than radius, then (x, y) lays inside the rectangle
 *
 */
inline bool isIn(const QPointF &p0, const QPointF &p1, float x, float y, float radius, float *dist, QPointF &pos)
{
	float radius_sq = radius * radius;

	if (p0 != p1) //otherwise condition (a) needs not to be tested: rectangle is null
	{
		float v_x = (p1.x() - p0.x());
		float v_y = (p1.y() - p0.y());

		float v_length_squared = v_x * v_x + v_y * v_y;

		float w_x = x - p0.x();
		float w_y = y - p0.y();

		float v_dot_w = w_x * v_x + w_y * v_y; //scalar product of v and w

		float r = v_dot_w / v_length_squared;

		float pf_x = p0.x() + r * v_x;
		float pf_y = p0.y() + r * v_y;

		float delta_x = x - pf_x;
		float delta_y = y - pf_y;

		if (r >= 0 && r <= 1 && (delta_x * delta_x + delta_y * delta_y < radius_sq))
		{
			float delta_len = sqrt(delta_x * delta_x + delta_y * delta_y);

			*dist = delta_len / radius;
			pos.setY(delta_y / radius);
			pos.setX(delta_x / radius);
			return true;
		}
	}

	// there could be some problem when point is nearer p0 or p1 and vice-versa
	// so i have to check both. is only needed with smooth_borders
	bool found = false;
	float dx = (x - p1.x());
	float dy = (y - p1.y());
	float delta_len_sq = dx*dx + dy*dy;

	if (delta_len_sq < radius_sq)
	{
		float delta_len = sqrt(delta_len_sq);
		*dist = delta_len;
		pos.setY(dy / radius);
		pos.setX(dx / radius);
		found = true;
	}

	if (p0 == p1) {
		*dist /= radius;
		return found;
	}

	dx = (x - p0.x());
	dy = (y - p0.y());
	delta_len_sq = dx*dx + dy*dy;

	if (delta_len_sq < radius_sq)
	{
		float delta_len = sqrt(delta_len_sq);
		if ((found && delta_len < *dist) || !found)
		{
			*dist = delta_len;
			//	pos.setY(x / delta_len );
			pos.setY(dy / radius);
			pos.setX(dx / radius);
		}
		found = true;
	}

	*dist /= radius;

	return found;
}

inline bool pointInTriangle(const float p_x, const float p_y, const float a_x, const float a_y,
	const float b_x, const float b_y, const float c_x, const float c_y)
{
	float fab = (p_y - a_y)*(b_x - a_x) - (p_x - a_x)*(b_y - a_y);
	float fbc = (p_y - c_y)*(a_x - c_x) - (p_x - c_x)*(a_y - c_y);
	float fca = (p_y - b_y)*(c_x - b_x) - (p_x - b_x)*(c_y - b_y);
	if (fab*fbc > 0 && fbc*fca > 0) return true;
	return false;
}

/**
 * checks if a point is in a triangle (2D)
 */
inline bool pointInTriangle(const QPointF &p, const QPointF &a, const QPointF &b, const QPointF &c)
{
	return pointInTriangle(p.x(), p.y(), a.x(), a.y(), b.x(), b.y(), c.x(), c.y());
}

/**
 * checks if a triangle is front or backfaced
 */
inline bool isFront(const float a_x, const float a_y, const float b_x, const float b_y, const float c_x, const float c_y) {
	return (b_x - a_x)*(c_y - a_y) - (b_y - a_y)*(c_x - a_x) > 0;
}

/**
 * checks if a triangle is front or backfaced
 */
inline bool isFront(const QPointF &a, const QPointF &b, const QPointF &c) {
	return isFront(a.x(), a.y(), b.x(), b.y(), c.x(), c.y());
}


/**
 * Checks if a line from LineStart to LineEnd intersects the circle defined
 * by CircleCenter and Radius in one or two points.
 *
 * pOut will either store
 * a) LineStart, if it is inside the circle
 * b) The one intersection between the line and the circle
 * c) One of the two intersections
 *
 * Let L(t) = LineStart + (t * Direction) the parametric equation
 * of the line, with 0 <= t <= 1 and Dircetion = LineEnd - LineStart
 *
 * Let |P - CircleCenter|² = radius² be the equation of the circle. Then, substituting L(t) to P
 *
 * |LineStart + (t * Direction) - CircleCenter|² = radius²
 *
 * Let Delta be LineStart - CircleCenter, then the equation above becomes
 *
 * t² * |Direction|² + 2 * t * (Direction · Delta) + |Delta|² - radius² = 0
 *
 * whose determinant is
 *
 * d = (Direction · Delta)² − |Direction|²(|Delta|² − radius²)
 *
 * if d < 0 there are no intersection, if d = 0 there is one, if d > 0 there are two.
 */
inline bool lineHitsCircle(QPointF& LineStart, QPointF& LineEnd, QPointF& CircleCenter, float radius)
{
	const float radius_sq = radius * radius;
	QPointF Delta = LineStart - CircleCenter;

	float delta_length_sq = Delta.x()*Delta.x() + Delta.y()*Delta.y();

	if (delta_length_sq <= radius_sq) return true;

	QPointF Direction = LineEnd - LineStart;

	const float direction_dot_delta = Direction.x()*Delta.x() + Direction.y()*Delta.y();

	const float direction_length_sq = Direction.x()*Direction.x() + Direction.y()*Direction.y();

	const float d = (direction_dot_delta * direction_dot_delta) - direction_length_sq * (delta_length_sq - radius_sq);

	if (d < 0.0f) return false; //no intersection
	else if (d < 0.0001f) //one intersection
	{
		const float s = -direction_dot_delta / direction_length_sq;
		if (s < 0.0f || s > 1.0f) return false;
		else return true;
	}
	else //two intersections
	{
		const float s = (-direction_dot_delta - sqrtf(d)) / direction_length_sq; //only one intersection is chosen
		if (s < 0.0f || s > 1.0f) return false;
		else return true;
	}
}

/*********CMeshO Utility Routines************/

/**
 * Simple displacement of vertex along its normal. Make sure normals
 * are precomputed!!
 *
 * O(1)
 */
inline void displaceAlongVector(CVertexO* vp, Point3m vector, float displacement)
{
	(*vp).P() += vector * displacement;
}

/**
 * Get the vertex currently pointed by the mouse at position cursor on the mesh m
 * This vertex is obtained by first finding the face nearest to the mouse, and then
 * finding the nearest vertex to the cursor among the face vertices.
 *
 * Same complexity as GLPickTri<CMeshO>::PickNearestFace(..)
 */
inline bool getVertexAtMouse(MeshModel &m, CMeshO::VertexPointer& value, QPoint& cursor,
	double* modelview_matrix, double* projection_matrix, GLint* viewport)
{

	double tx, ty, tz;

	//TODO e se i vertici sono stati cancellati? (IsD())
	std::vector<CFaceO*> res;
	int nface = vcg::GLPickTri<CMeshO>::PickVisibleFace(cursor.x(), cursor.y(), m.cm, res, 2, 2);
	//if (GLPickTri<CMeshO>::PickClosestFace(latest_event.gl_position.x(), latest_event.gl_position.y(), m.cm, face, 2, 2))
	if ((nface > 0) && (res[0] != NULL) && !(res[0]->IsD()))
	//if (vcg::GLPickTri<CMeshO>::PickClosestFace(cursor.x(), cursor.y(), m.cm, fp, 2, 2))
	{
		CFaceO * fp = res[0];
		QPointF point[3];

		for (int i = 0; i < 3; i++)
		{
			gluProject(fp->V(i)->P()[0], fp->V(i)->P()[1], fp->V(i)->P()[2],
				modelview_matrix,
				projection_matrix,
				viewport,
				&tx, &ty, &tz);

			point[i] = QPointF(tx, ty);
		}

		value = fp->V(getNearest(cursor, point, 3));

		return true;
	}
	return false;
}


/**
 * calcs the surrounding faces of a vertex with VF topology
 */
inline void getSurroundingFacesVF(CFaceO * fac, int vert_pos, std::vector<CFaceO *> *surround) {
	CVertexO * vert = fac->V(vert_pos);
	int pos = vert->VFi();
	CFaceO * first_fac = vert->VFp();
	CFaceO * curr_f = first_fac;
	do {
		CFaceO * temp = curr_f->VFp(pos);
		if (curr_f != 0 && !curr_f->IsD()) {
			surround->push_back(curr_f);
			pos = curr_f->VFi(pos);
		}
		curr_f = temp;
	} while (curr_f != first_fac && curr_f != 0);
}

inline bool hasSelected(MeshModel &m) {
	CMeshO::FaceIterator fi;
	for (fi = m.cm.face.begin(); fi != m.cm.face.end(); fi++) {
		if (!(*fi).IsD() && (*fi).IsS()) return true;
	}
	return false;
}

inline void updateNormal(CVertexO * v)
{
	CFaceO * f = v->VFp();
	CFaceO * one_face = f;
	int pos = v->VFi();
	v->N()[0] = 0; v->N()[1] = 0; v->N()[2] = 0;
	do {
		CFaceO * temp = one_face->VFp(pos);
		if (one_face != 0 && !one_face->IsD())
		{
			one_face->N() = TriangleNormal(*one_face).Normalize();
			v->N() += one_face->N();
			pos = one_face->VFi(pos);
		}
		one_face = temp;
	} while (one_face != f && one_face != 0);
	v->N().Normalize();
}

/*********OpenGL Drawing Routines************/


void drawNormalPercentualCircle(GLArea *, QPoint &, MeshModel &, GLfloat*, double*, double*, GLint*, float);
void drawVertex(CVertexO*);
void drawLine(GLArea *, QPoint &, QPoint &);
void drawSimplePolyLine(GLArea * gla, QPoint & gl_cur, float scale, std::vector<QPointF> * points);
void drawPercentualPolyLine(GLArea *, QPoint &, MeshModel &, GLfloat*, double*, double*, GLint*, float, std::vector<QPointF> *);

void generateCircle(std::vector<QPointF> &, int segments = 18);
void generateSquare(std::vector<QPointF> &, int segments = 1);

#endif
