/*
 *  This file is part of RawTherapee.
 *
 *  Copyright (c) 2004-2010 Gabor Horvath <hgabor@rawtherapee.com>
 *
 *  RawTherapee 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 3 of the License, or
 *  (at your option) any later version.
 *
 *  RawTherapee 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 for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with RawTherapee.  If not, see <http://www.gnu.org/licenses/>.
 */
#ifndef _EDIT_H_
#define _EDIT_H_

#include <gtkmm.h>
#include "../rtengine/imagefloat.h"
#include "editid.h"
#include "cursormanager.h"
#include "../rtengine/rt_math.h"

class EditDataProvider;

namespace rtengine {
class EditBuffer;
}

/** @file
 *
 * The Edit mechanism is designed to let tools (subscribers) communicate with the preview area (provider).
 * Subscribers will be tools that need to create some graphics in the preview area, to let the user interact
 * with it in a more user friendly way.
 *
 * Do not confuse with a _local_ editing, which is another topic implemented in another class. The Edit feature
 * is also not supported in batch editing from the File Browser.
 *
 * Each Edit tool must have a unique ID, that will identify them, and which let the ImProcCoordinator or other
 * mechanism act as appropriated. They are all defined in rtgui/editid.h.
 *
 * ## How does it works?
 *
 * When a tool is being constructed, unique IDs are affected to the EditSubscribers. Then the EditorPanel class
 * will ask all ToolPanel to register the 'after' preview ImageArea object as data provider. The Subscribers
 * have now to provide a toggle button to click on to start the Edit listening. When toggling on, the Subscriber
 * register itself to the DataProvider, then an event is thrown through the standard ToolPanelListener::panelChanged
 * method to update the preview with new graphics to be displayed, and eventually buffers to create and populate.
 *
 * When the Edit process stops, the Subscriber is removed from the DataProvider, so buffer can be freed up.
 * A new ToolPanelListener::panelChanged event is also thrown to update the preview again, without the tool's
 * graphical objects. The Edit button is also toggled off (by the user or programmatically).
 *
 * It means that each Edit buttons toggled on will start an update of the preview which might or might not create
 * a new History entry, depending on the event.
 *
 */

class PolarCoord;

// Do not confuse with rtengine::Coord2D, this one is for the GUI
class Coord {
public:
	int x;
	int y;

	Coord() : x(-1), y(-1) {}
	Coord(int x, int y) : x(x), y(y) {}

	void set (int x, int y) {
		this->x = x;
		this->y = y;
	}

	void setFromPolar(PolarCoord polar);

	/// @brief Clip the coord to stay in the width x height bounds
	/// @return true if the x or y coordinate has changed
	bool clip(int width, int height) {
		int trimmedX = rtengine::LIM<int>(x, 0, width);
		int trimmedY = rtengine::LIM<int>(y, 0, height);
		bool retval = trimmedX!=x || trimmedY!=y;
		x = trimmedX;
		y = trimmedY;
		return retval;
	}

	void operator+=(const Coord & rhs) {
		x += rhs.x;
		y += rhs.y;
	}
	void operator-=(const Coord & rhs) {
		x -= rhs.x;
		y -= rhs.y;
	}
	void operator*=(double scale) {
		x *= scale;
		y *= scale;
	}
	Coord operator+(Coord & rhs) {
		Coord result(x+rhs.x, y+rhs.y);
		return result;
	}
	Coord operator-(Coord & rhs) {
		Coord result(x-rhs.x, y-rhs.y);
		return result;
	}
	Coord operator*(double scale) {
		Coord result(x*scale, y*scale);
		return result;
	}
};

class PolarCoord {
public:
	double radius;
	double angle; // degree

	PolarCoord() : radius(1.), angle(0.) {}
	PolarCoord(double radius, double angle) : radius(radius), angle(angle) {}

	void set (double radius, double angle) {
		this->radius = radius;
		this->angle = angle;
	}

	void setFromCartesian(Coord start, Coord end) {
		Coord delta(end.x-start.x, end.y-start.y);
		setFromCartesian(delta);
	}

	void setFromCartesian(Coord delta) {
		if (!delta.x && !delta.y) {
			// null vector, we set to a default value
			radius = 1.;
			angle = 0.;
			return;
		}
		double x_ = double(delta.x);
		double y_ = double(delta.y);
		radius = sqrt(x_*x_+y_*y_);
		if (delta.x>0.) {
			if (delta.y>=0.)
				angle = atan(y_/x_)/(2*M_PI)*360.;
			else if (delta.y<0.)
				angle = (atan(y_/x_)+2*M_PI)/(2*M_PI)*360.;
		}
		else if (delta.x<0.)
			angle = (atan(y_/x_)+M_PI)/(2*M_PI)*360.;
		else if (delta.x==0.) {
			if (delta.y>0.)
				angle = 90.;
			else
				angle = 270.;
		}
	}

	void operator+=(const PolarCoord & rhs) {
		Coord thisCoord, rhsCoord;
		thisCoord.setFromPolar(*this);
		rhsCoord.setFromPolar(rhs);
		thisCoord += rhsCoord;
		setFromCartesian(thisCoord);
	}
	void operator-=(const PolarCoord & rhs) {
		Coord thisCoord, rhsCoord;
		thisCoord.setFromPolar(*this);
		rhsCoord.setFromPolar(rhs);
		thisCoord -= rhsCoord;
		setFromCartesian(thisCoord);
	}
	void operator*=(double scale) {
		radius *= scale;
	}
	PolarCoord operator+(PolarCoord & rhs) {
		Coord thisCoord, rhsCoord;
		thisCoord.setFromPolar(*this);
		rhsCoord.setFromPolar(rhs);
		thisCoord += rhsCoord;
		PolarCoord result;
		result.setFromCartesian(thisCoord);
		return result;
	}
	PolarCoord operator-(PolarCoord & rhs) {
		Coord thisCoord, rhsCoord;
		thisCoord.setFromPolar(*this);
		rhsCoord.setFromPolar(rhs);
		thisCoord -= rhsCoord;
		PolarCoord result;
		result.setFromCartesian(thisCoord);
		return result;
	}
	Coord operator*(double scale) {
		Coord result(radius*scale, angle);
		return result;
	}

};

/** @brief Coordinate system where the widgets will be drawn
 *
 * The EditCoordSystem is used to define a screen and an image coordinate system.
 */
class EditCoordSystem {
public:
	virtual ~EditCoordSystem() {}

	/// Convert the widget's DrawingArea (i.e. preview area) coords to the edit buffer coords
	virtual void screenCoordToCropBuffer (int phyx, int phyy, int& cropx, int& cropy) =0;
	/// Convert the widget's DrawingArea (i.e. preview area) coords to the full image coords
	virtual void screenCoordToImage (int phyx, int phyy, int& imgx, int& imgy) =0;
	/// Convert the image coords to the widget's DrawingArea (i.e. preview area) coords
	virtual void imageCoordToScreen (int imgx, int imgy, int& phyx, int& phyy) =0;
	/// Convert the image coords to the edit buffer coords
	virtual void imageCoordToCropBuffer (int imgx, int imgy, int& phyx, int& phyy) =0;
	/// Convert a size value from the preview's scale to the image's scale
	virtual int scaleValueToImage (int value) =0;
	/// Convert a size value from the preview's scale to the image's scale
	virtual float scaleValueToImage (float value) =0;
	/// Convert a size value from the preview's scale to the image's scale
	virtual double scaleValueToImage (double value) =0;
	/// Convert a size value from the image's scale to the preview's scale
	virtual int scaleValueToScreen (int value) =0;
	/// Convert a size value from the image's scale to the preview's scale
	virtual float scaleValueToScreen (float value) =0;
	/// Convert a size value from the image's scale to the preview's scale
	virtual double scaleValueToScreen (double value) =0;
};

class RGBColor {
	double r;
	double g;
	double b;

public:
	RGBColor () : r(0.), g(0.), b(0.) {}
	explicit RGBColor (double r, double g, double b) : r(r), g(g), b(b) {}
	explicit RGBColor (char r, char g, char b) : r(double(r)/255.), g(double(g)/255.), b(double(b)/255.) {}

	void setColor(double r, double g, double b) {
		this->r = r;
		this->g = g;
		this->b = b;
	}

	void setColor(char r, char g, char b) {
		this->r = double(r)/255.;
		this->g = double(g)/255.;
		this->b = double(b)/255.;
	}

	double getR() { return r; }
	double getG() { return g; }
	double getB() { return b; }
};

class Geometry {
public:
	enum State {
		NORMAL,
		PRELIGHT,
		DRAGGED
	};
	enum Datum {
		IMAGE,
		CLICKED_POINT,
		CURSOR
	};
	enum Flags {
		ACTIVE      = 1<<0,  // true if the geometry is active and have to be drawn
		AUTO_COLOR  = 1<<1,  // true if the color depend on the state value, not the color field above
	};

protected:
	RGBColor innerLineColor;
	RGBColor outerLineColor;
	short flags;

public:
	float innerLineWidth;  // ...outerLineWidth = innerLineWidth+2
	Datum datum;
	State state;  // set by the Subscriber

	Geometry () : innerLineColor(char(255), char(255), char(255)), outerLineColor(char(0), char(0), char(0)), flags(ACTIVE|AUTO_COLOR), innerLineWidth(1.f), datum(IMAGE), state(NORMAL) {}
	virtual ~Geometry() {}

	void     setInnerLineColor     (double r, double g, double b) { innerLineColor.setColor(r, g, b); flags &= ~AUTO_COLOR; }
	void     setInnerLineColor     (char   r, char   g, char   b) { innerLineColor.setColor(r, g, b); flags &= ~AUTO_COLOR; }
	RGBColor getInnerLineColor     ();
	void     setOuterLineColor     (double r, double g, double b) { outerLineColor.setColor(r, g, b); flags &= ~AUTO_COLOR; }
	void     setOuterLineColor     (char   r, char   g, char   b) { outerLineColor.setColor(r, g, b); flags &= ~AUTO_COLOR; }
	RGBColor getOuterLineColor     ();
	double   getOuterLineWidth     () { return double(innerLineWidth)+2.; }
	double   getMouseOverLineWidth () { return getOuterLineWidth()+2.; }
	void     setAutoColor          (bool aColor) { if (aColor) flags |= AUTO_COLOR; else flags &= ~AUTO_COLOR; }
	void     setActive             (bool active) { if (active) flags |= ACTIVE; else flags &= ~ACTIVE; }

	virtual void drawOuterGeometry (Cairo::RefPtr<Cairo::Context> &cr, rtengine::EditBuffer *parent, EditCoordSystem &coordSystem) =0;
	virtual void drawInnerGeometry (Cairo::RefPtr<Cairo::Context> &cr, rtengine::EditBuffer *parent, EditCoordSystem &coordSystem) =0;
	virtual void drawToMOChannel   (Cairo::RefPtr<Cairo::Context> &cr, Cairo::RefPtr<Cairo::Context> &cr2, unsigned short id, rtengine::EditBuffer *editBuffer, EditCoordSystem &coordSystem) =0;
};

class Circle : public Geometry {
public:
	Coord center;
	int radius;
	bool filled;
	bool radiusInImageSpace; /// If true, the radius depend on the image scale; if false, it is a fixed 'screen' size

	Circle () : center(100,100), radius(10), filled(false), radiusInImageSpace(false) {}
	Circle (Coord &center, int radius, bool filled=false, bool radiusInImageSpace=false) : center(center), radius(radius), filled(filled), radiusInImageSpace(radiusInImageSpace) {}
	Circle (int centerX, int centerY, int radius, bool filled=false, bool radiusInImageSpace=false) : center(centerX, centerY), radius(radius), filled(filled), radiusInImageSpace(radiusInImageSpace) {}

	void drawOuterGeometry (Cairo::RefPtr<Cairo::Context> &cr, rtengine::EditBuffer *editBuffer, EditCoordSystem &coordSystem);
	void drawInnerGeometry (Cairo::RefPtr<Cairo::Context> &cr, rtengine::EditBuffer *editBuffer, EditCoordSystem &coordSystem);
	void drawToMOChannel   (Cairo::RefPtr<Cairo::Context> &cr, Cairo::RefPtr<Cairo::Context> &cr2, unsigned short id, rtengine::EditBuffer *editBuffer, EditCoordSystem &coordSystem);
};

class Line : public Geometry {
public:
	Coord begin;
	Coord end;

	Line () : begin(10,10), end(100,100) {}
	Line (Coord &begin, Coord &end) : begin(begin), end(end) {}
	Line (int beginX, int beginY, int endX, int endY) : begin(beginX, beginY), end(endX, endY) {}

	void drawOuterGeometry (Cairo::RefPtr<Cairo::Context> &cr, rtengine::EditBuffer *editBuffer, EditCoordSystem &coordSystem);
	void drawInnerGeometry (Cairo::RefPtr<Cairo::Context> &cr, rtengine::EditBuffer *editBuffer, EditCoordSystem &coordSystem);
	void drawToMOChannel   (Cairo::RefPtr<Cairo::Context> &cr, Cairo::RefPtr<Cairo::Context> &cr2, unsigned short id, rtengine::EditBuffer *editBuffer, EditCoordSystem &coordSystem);
};

class Polyline : public Geometry {
public:
	std::vector<Coord> points;
	bool filled;

	Polyline() : filled(false) {}

	void drawOuterGeometry (Cairo::RefPtr<Cairo::Context> &cr, rtengine::EditBuffer *editBuffer, EditCoordSystem &coordSystem);
	void drawInnerGeometry (Cairo::RefPtr<Cairo::Context> &cr, rtengine::EditBuffer *editBuffer, EditCoordSystem &coordSystem);
	void drawToMOChannel (Cairo::RefPtr<Cairo::Context> &cr, Cairo::RefPtr<Cairo::Context> &cr2, unsigned short id, rtengine::EditBuffer *editBuffer, EditCoordSystem &coordSystem);
};

class Rectangle : public Geometry {
public:
	Coord topLeft;
	Coord bottomRight;
	bool filled;

	Rectangle() : topLeft(0,0), bottomRight(10,10), filled(false) {}

	void setXYWH(int left, int top, int width, int height);
	void setXYXY(int left, int top, int right, int bottom);
	void setXYWH(Coord topLeft, Coord widthHeight);
	void setXYXY(Coord topLeft, Coord bottomRight);
	void drawOuterGeometry (Cairo::RefPtr<Cairo::Context> &cr, rtengine::EditBuffer *editBuffer, EditCoordSystem &coordSystem);
	void drawInnerGeometry (Cairo::RefPtr<Cairo::Context> &cr, rtengine::EditBuffer *editBuffer, EditCoordSystem &coordSystem);
	void drawToMOChannel (Cairo::RefPtr<Cairo::Context> &cr, Cairo::RefPtr<Cairo::Context> &cr2, unsigned short id, rtengine::EditBuffer *editBuffer, EditCoordSystem &coordSystem);
};

/// @brief Method for client tools needing Edit information
class EditSubscriber {

public:

private:
	EditUniqueID ID; /// this will be used in improcfun to locate the data that has to be stored in the buffer; it must be unique in RT
	EditType editingType;
	BufferType bufferType;
	EditDataProvider *provider;

protected:
	std::vector<Geometry*> visibleGeometry;
	std::vector<Geometry*> mouseOverGeometry;

public:
	EditSubscriber (EditType editType);
	virtual ~EditSubscriber () {}

	void              setEditProvider(EditDataProvider *provider);
	EditDataProvider* getEditProvider() { return provider; }
	void              setEditID(EditUniqueID ID, BufferType buffType);
	bool              isCurrentSubscriber();
	virtual void      subscribe();
	virtual void      unsubscribe();
	virtual void      switchOffEditMode ();    /// Occurs when the user want to stop the editing mode
	EditUniqueID      getEditID();
	EditType          getEditingType();
	BufferType        getEditBufferType();

	/** @brief Get the cursor to be displayed when above handles
	@param objectID object currently "hovered" */
	virtual CursorShape getCursor(int objectID) { return CSOpenHand; }

	/** @brief Triggered when the mouse is moving over an object
	This method is also triggered when the cursor is moving over the image in ET_PIPETTE mode
	@param modifierKey Gtk's event modifier key (GDK_CONTROL_MASK | GDK_SHIFT_MASK | ...)
	@param editBuffer buffer to get the pipette values and the from
	@return true if the preview has to be redrawn, false otherwise */
	virtual bool mouseOver(int modifierKey) { return false; }

	/** @brief Triggered when mouse button 1 is pressed, together with the CTRL modifier key if the subscriber is of type ET_PIPETTE
	@param modifierKey Gtk's event modifier key (GDK_CONTROL_MASK | GDK_SHIFT_MASK | ...)
	@return true if the preview has to be redrawn, false otherwise */
	virtual bool button1Pressed(int modifierKey) { return false; }

	/** @brief Triggered when mouse button 1 is released
	@return true if the preview has to be redrawn, false otherwise */
	virtual bool button1Released() { return false; }

	/** @brief Triggered when the user is moving while holding down mouse button 1
	@param modifierKey Gtk's event modifier key (GDK_CONTROL_MASK | GDK_SHIFT_MASK | ...)
	@return true if the preview has to be redrawn, false otherwise */
	virtual bool drag(int modifierKey) { return false; }

	/** @brief Get the geometry to be shown to the user */
	const std::vector<Geometry*> & getVisibleGeometry() { return visibleGeometry; }

	/** @brief Get the geometry to be drawn in the "mouse over" channel, hidden from the user */
	const std::vector<Geometry*> & getMouseOverGeometry() { return mouseOverGeometry; }
};

/** @brief Class to handle the furniture of data to the subscribers.
 *
 * It is admitted that only one Subscriber can ask data at a time. If the Subscriber is of type ET_PIPETTE, it will have to
 * trigger the usual event so that the image will be reprocessed to compute the buffer of the current subscriber.
 */
class EditDataProvider {

private:
	EditSubscriber *currSubscriber;

public:
	int object;            /// ET_OBJECTS mode: Object detected under the cursor, 0 otherwise; ET_PIPETTE mode: 1 if above the image, 0 otherwise
	float pipetteVal[3];   /// Current pipette values; if bufferType==BT_SINGLEPLANE_FLOAT, #2 & #3 will be set to 0

	Coord posScreen;       /// Location of the mouse button press, in preview image space
	Coord posImage;        /// Location of the mouse button press, in the full image space
	Coord deltaScreen;     /// Delta relative to posScreen
	Coord deltaImage;      /// Delta relative to posImage
	Coord deltaPrevScreen; /// Delta relative to the previous mouse location, in preview image space
	Coord deltaPrevImage;  /// Delta relative to the previous mouse location, in the full image space

	EditDataProvider();
	virtual ~EditDataProvider() {}

	virtual void        subscribe(EditSubscriber *subscriber);
	virtual void        unsubscribe();         /// Occurs when the subscriber has been switched off first
	virtual void        switchOffEditMode ();  /// Occurs when the user want to stop the editing mode
	virtual CursorShape getCursor(int objectID);
	int                 getPipetteRectSize() { return 8; } // TODO: make a GUI
	EditSubscriber*     getCurrSubscriber();
	virtual void        getImageSize (int &w, int&h) =0;
};

#endif
