/*
 *  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/>.
 */
#include "mydiagonalcurve.h"
#include "../rtengine/curves.h"
#include <cstring>
#include <gdkmm/types.h>

MyDiagonalCurve::MyDiagonalCurve () : activeParam(-1), bghistvalid(false) {

    graphW = get_allocation().get_width() - RADIUS * 2;
    graphH = get_allocation().get_height() - RADIUS * 2;
    prevGraphW = graphW;
    prevGraphH = graphH;
    grab_point = -1;
    lit_point = -1;
    buttonPressed = false;

    bghist = new unsigned int[256];

    signal_event().connect( sigc::mem_fun(*this, &MyDiagonalCurve::handleEvents) );

    curve.type = DCT_Spline;

    curve.x.push_back(0.);
    curve.y.push_back(0.);
    curve.x.push_back(1.);
    curve.y.push_back(1.);
}

MyDiagonalCurve::~MyDiagonalCurve () {

    delete [] bghist;
}

std::vector<double> MyDiagonalCurve::get_vector (int veclen) {

    std::vector<double> vector;
    vector.resize (veclen);

    if (curve.type != DCT_Parametric) {
        // count active points:
        double prev =- 1.0;
        int active = 0;
        int firstact = -1;
        for (int i = 0; i < (int)curve.x.size(); ++i)
            if (curve.x.at(i) > prev) {
                if (firstact < 0)
                  firstact = i;
                prev = curve.x.at(i);
                ++active;
            }
        // handle degenerate case:
        if (active < 2) {
            double ry;
            if (active > 0)
                ry = curve.y.at(firstact);
            else
                ry = 0.0;
            if (ry < 0.0) ry = 0.0;
            if (ry > 1.0) ry = 1.0;
            for (int x = 0; x < veclen; ++x)
                vector.at(x) = ry;
            return vector;
        }
    }

    // calculate remaining points
    std::vector<double> curveDescr = getPoints ();
    rtengine::DiagonalCurve* rtcurve = new rtengine::DiagonalCurve (curveDescr, veclen*1.2);
    std::vector<double> t;
    t.resize (veclen);
    for (int i = 0; i < veclen; i++)
        t[i] = (double) i / (veclen - 1.0);
    rtcurve->getVal (t, vector);
    delete rtcurve;
    return vector;
}

void MyDiagonalCurve::interpolate () {

    prevGraphW = graphW;
    prevGraphH = graphH;
    int nbPoints = graphW-2;
    point.resize (nbPoints);
    std::vector<double> vector = get_vector (nbPoints);
    for (int i = 0; i < nbPoints; ++i) {
        float currX = float(i)/float(nbPoints-1);
        point.at(i).setCoords(float(graphX)+1.5f+float(graphW-3)*currX, float(graphY)-1.5f-float(graphH-3)*float(vector.at(i)));
    }
    upoint.clear ();
    lpoint.clear ();

    if (curve.type==DCT_Parametric && activeParam>0) {
        double tmp = curve.x.at(activeParam-1);
        if (activeParam>=4) {
            upoint.resize(nbPoints);
            lpoint.resize(nbPoints);
            curve.x.at(activeParam-1) = 100;
            vector = get_vector (nbPoints);
            for (int i = 0; i < nbPoints; ++i) {
                float currX = float(i)/float(nbPoints-1);
                upoint.at(i).setCoords(float(graphX)+1.5f+float(graphW-3)*currX, float(graphY)-1.5f-float(graphH-3)*float(vector.at(i)));
            }
            curve.x.at(activeParam-1) = -100;
            vector = get_vector (nbPoints);
            for (int i = 0; i < nbPoints; ++i) {
                float currX = float(i)/float(nbPoints-1);
                 lpoint.at(i).setCoords(float(graphX)+1.5f+float(graphW-3)*currX, float(graphY)-1.5f-float(graphH-3)*float(vector.at(i)));
            }
            curve.x.at(activeParam-1) = tmp;
        }
    }

    curveIsDirty = false;
}

void MyDiagonalCurve::draw (int handle) {
    if (!isDirty()) {
        return;
    }

    Glib::RefPtr<Gdk::Window > win = get_window();
    if (!surfaceCreated() || !win)
        return;

    // re-calculate curve if dimensions changed
    if (curveIsDirty || prevGraphW != graphW || prevGraphH != graphH || int(point.size()) != graphW-2)
        interpolate ();

    Gtk::StateType state = !is_sensitive() ? Gtk::STATE_INSENSITIVE : Gtk::STATE_NORMAL;

    Glib::RefPtr<Gtk::Style> style = get_style ();
    Cairo::RefPtr<Cairo::Context> cr = getContext();
    cr->set_line_cap(Cairo::LINE_CAP_SQUARE);

    // clear background
    Gdk::Color c = style->get_bg (Gtk::STATE_NORMAL);
    cr->set_source_rgb (c.get_red_p(), c.get_green_p(), c.get_blue_p());
    cr->rectangle (0., 0., double(getWidth()), double(getHeight()));
    cr->fill ();

    // histogram in the background
    if (bghistvalid) {
        // find highest bin
        unsigned int valMax = 0;
        for (int i=0; i<256; i++)
            if (bghist[i]>valMax)
                valMax = bghist[i];
        // draw histogram
        cr->set_line_width (1.0);
        double stepSize = (graphW-3) / 255.0;
        cr->move_to ( double(graphX+1), double(graphY-1) );
        c = style->get_fg (Gtk::STATE_INSENSITIVE);
        cr->set_source_rgb (c.get_red_p(), c.get_green_p(), c.get_blue_p());
        for (int i=0; i<256; i++) {
            double val = double(bghist[i]) * double(graphH-2) / double(valMax);
            /*
            if (val>graphH-2)
                val = graphH-2;
            */
            //if (i>0)
                cr->line_to (double(graphX)+1.5+double(i)*stepSize, double(graphY-1)-val);
        }
        cr->line_to (double(graphX)+1.5+255.*stepSize, double(graphY-1));
        cr->close_path();
        cr->fill ();
    }

    // draw the grid lines:
    cr->set_line_width (1.0);
    c = style->get_dark (state);
    cr->set_source_rgb (c.get_red_p(), c.get_green_p(), c.get_blue_p());
    cr->set_antialias (Cairo::ANTIALIAS_NONE);
    for (int i = 0; i <= 10; i++) {
        // horizontal lines
        cr->move_to     (double(graphX)+0.5                                 , double(graphY) - max(0.5, double(graphH*i/10) - 0.5));
        cr->rel_line_to (double(graphW-1)                                   , 0.);
        // vertical lines
        cr->move_to     (double(graphX) + max(0.5, double(graphW*i/10) - 0.5), double(graphY));
        cr->rel_line_to (0.                                                 , double(-graphH+1));
    }
    cr->stroke ();

    // draw f(x)=x line
    if (snapToElmt == -2)
        cr->set_source_rgb (1.0, 0.0, 0.0);
    else
        cr->set_source_rgb (c.get_red_p(), c.get_green_p(), c.get_blue_p());
    std::valarray<double> ds (1);
    ds[0] = 4;
    cr->set_dash (ds, 0);
    cr->move_to (double(graphX)+1.5, double(graphY)-1.5);
    cr->rel_line_to (double(graphW-3), double(-graphH+3));
    cr->stroke ();
    cr->unset_dash ();

    cr->set_antialias (Cairo::ANTIALIAS_SUBPIXEL);
    cr->set_line_width (1.0);

    // draw upper and lower bounds
    if (curve.type==DCT_Parametric && activeParam>0 && lpoint.size()>1 && upoint.size()>1) {
        cr->set_source_rgba (0.0, 0.0, 0.0, 0.15);
        cr->move_to (upoint[0].x, upoint[0].y);
        for (int i=1; i<(int)upoint.size(); i++)
            cr->line_to (upoint[i].x, upoint[i].y);
        cr->line_to (lpoint[lpoint.size()-1].x, lpoint[lpoint.size()-1].y);
        for (int i=(int)lpoint.size()-2; i>=0; i--)
            cr->line_to (lpoint[i].x, lpoint[i].y);
        cr->line_to (upoint[0].x, upoint[0].y);
        cr->fill ();
    }

    // draw the pipette values
    if (pipetteR > -1.f || pipetteG > -1.f || pipetteB > -1.f) {
        int n=0;
        if (pipetteR > -1.f) ++n;
        if (pipetteG > -1.f) ++n;
        if (pipetteB > -1.f) ++n;

        if (n > 1) {
            if (pipetteR > -1.f) {
                cr->set_source_rgba (1., 0., 0., 0.5); // WARNING: assuming that red values are stored in pipetteR, which might not be the case!
                cr->move_to (double(graphX)+1.5 + double(graphW-3)*pipetteR, double(graphY)-1.5);
                cr->rel_line_to (0, double(-graphH+3));
                cr->stroke ();
            }
            if (pipetteG > -1.f) {
                cr->set_source_rgba (0., 1., 0., 0.5); // WARNING: assuming that green values are stored in pipetteG, which might not be the case!
                cr->move_to (double(graphX)+1.5 + double(graphW-3)*pipetteG, double(graphY)-1.5);
                cr->rel_line_to (0, double(-graphH+3));
                cr->stroke ();
            }
            if (pipetteB > -1.f) {
                cr->set_source_rgba (0., 0., 1., 0.5); // WARNING: assuming that blue values are stored in pipetteB, which might not be the case!
                cr->move_to (double(graphX)+1.5 + double(graphW-3)*pipetteB, double(graphY)-1.5);
                cr->rel_line_to (0, double(-graphH+3));
                cr->stroke ();
            }
        }
        if (pipetteVal > -1.f) {
            cr->set_line_width (2.);
            c = style->get_fg (state);
            cr->set_source_rgb (c.get_red_p(), c.get_green_p(), c.get_blue_p());
            cr->move_to (double(graphX)+1.5 + double(graphW-3)*pipetteVal, double(graphY)-1.5);
            cr->rel_line_to (0, double(-graphH+3));
            cr->stroke ();
            cr->set_line_width (1.);
        }
    }

    c = style->get_fg (state);

    // draw the cage of the NURBS curve
    if (curve.type==DCT_NURBS) {
        unsigned int nbPoints;
        std::valarray<double> ch_ds (1);
        ch_ds[0] = 2;
        cr->set_dash (ch_ds, 0);
        cr->set_line_width (0.75);
        cr->set_source_rgb (c.get_red_p(), c.get_green_p(), c.get_blue_p());
        std::vector<double> points = getPoints();
        nbPoints = ((int)points.size()-1)/2;
        for (unsigned int i = 1; i < nbPoints; i++) {
            int pos = i*2+1;

            double x1 = double(graphX)+1.5 + double(graphW-3)*points[pos-2];  // project (curve.at(i), 0, 1, graphW);
            double y1 = double(graphY)-1.5 - double(graphH-3)*points[pos-1];  // project (curve.y.at(i)i], 0, 1, graphH);
            double x2 = double(graphX)+0.5 + double(graphW-3)*points[pos];    // project (curve.at(i), 0, 1, graphW);
            double y2 = double(graphY)-1.5 - double(graphH-3)*points[pos+1];  // project (curve.y.at(i), 0, 1, graphH);

            // set the color of the line when the point is snapped to the cage
            if (curve.x.size() == nbPoints && snapToElmt >= 1000 && ((i == (snapToElmt-1000)) || (i == (snapToElmt-999))))
                cr->set_source_rgb (1.0, 0.0, 0.0);
            else
                cr->set_source_rgb (c.get_red_p(), c.get_green_p(), c.get_blue_p());
            cr->move_to (x1, y1);
            cr->line_to (x2, y2);
            cr->stroke ();
        }
        cr->unset_dash ();
        cr->set_line_width (1.0);
    }

    // draw curve
    cr->set_source_rgb (c.get_red_p(), c.get_green_p(), c.get_blue_p());
    cr->move_to (double(point.at(0).x), double(point.at(0).y));
    for (int i=1; i<(int)point.size(); i++) {
        cr->line_to (double(point.at(i).x), double(point.at(i).y));
    }
    cr->stroke ();

    // draw the left colored bar
    if (leftBar) {
        // first the background
        int bWidth = getBarWidth();
        BackBuffer *bb = this;
        leftBar->setDrawRectangle(win, 1, graphY-graphH+1, bWidth-2, graphH-2);
        leftBar->expose(bb);

        // now the border
        c = style->get_dark (state);
        cr->set_source_rgb (c.get_red_p(), c.get_green_p(), c.get_blue_p());
        cr->rectangle(0.5, graphY-graphH+0.5, bWidth-1, graphH-1);
        cr->stroke();
    }

    // draw the bottom colored bar
    if (bottomBar) {
        // first the background
        int bWidth = getBarWidth();
        BackBuffer *bb = this;
        bottomBar->setDrawRectangle(win, graphX+1, graphY+CBAR_MARGIN+1, graphW-2, bWidth-2);
        bottomBar->expose(bb);

        // now the border
        c = style->get_dark (state);
        cr->set_source_rgb (c.get_red_p(), c.get_green_p(), c.get_blue_p());
        cr->rectangle(graphX+0.5, graphY+CBAR_MARGIN+0.5, graphW-1, bWidth-1 );
        cr->stroke();
    }

    // draw bullets
    if (curve.type!=DCT_Parametric) {
        c = style->get_fg (state);
        for (int i = 0; i < (int)curve.x.size(); ++i) {
            if (curve.x.at(i) == -1) continue;
            if (snapToElmt >= 1000) {
                int pt = snapToElmt-1000;
                if (i >= (pt-1) && i <= (pt+1))
                    cr->set_source_rgb(1.0, 0.0, 0.0);
                else
                    cr->set_source_rgb (c.get_red_p(), c.get_green_p(), c.get_blue_p());
            }
            else {
                if (i == handle || i == snapToElmt)
                    cr->set_source_rgb (1.0, 0.0, 0.0);
                else
                    cr->set_source_rgb (c.get_red_p(), c.get_green_p(), c.get_blue_p());
            }

            double x = double(graphX+1) + double((graphW-2) * curve.x.at(i)); // project (curve.x.at(i), 0, 1, graphW);
            double y = double(graphY-1) - double((graphH-2) * curve.y.at(i)); // project (curve.y.at(i), 0, 1, graphH);

            cr->arc (x, y, RADIUS+0.5, 0, 2*M_PI);
            cr->fill ();
        }
    }
    setDirty(false);
    queue_draw();
}

/*void MyDiagonalCurve::graphSizeRequest (Gtk::Requisition* req) {
	req->width = getGraphMinSize();
	// The real height request should take care of the presence of the vertical
	// scroll bar and its width
	req->height = sized ? getGraphMinSize() : get_allocation().get_width();
}*/

bool MyDiagonalCurve::handleEvents (GdkEvent* event) {

	CursorShape new_type = cursor_type;

	bool retval = false;
	int num = (int)curve.x.size();

	/* graphW and graphH are the size of the graph */
	calcDimensions();

	if ((graphW < 0) || (graphH < 0))
		return false;

	double minDistanceX = double(MIN_DISTANCE) / double(graphW-1);
	double minDistanceY = double(MIN_DISTANCE) / double(graphH-1);

	switch (event->type) {
	case Gdk::CONFIGURE: {
		// Happen when the the window is resized
		if (sized & (RS_Pending | RS_Force)) {
			set_size_request(-1, calcDimensions());
			sized = RS_Done;
		}
		retval = true;
		break;
	}
	case Gdk::EXPOSE:
	{
		Glib::RefPtr<Gdk::Window> win = get_window();
		if (sized & (RS_Pending | RS_Force)) {
			set_size_request(-1, calcDimensions());
		}
		sized = RS_Pending;
		// setDrawRectangle will allocate the backbuffer Surface
		if (setDrawRectangle(win, 0, 0, get_allocation().get_width(),  get_allocation().get_height())) {
			setDirty(true);
			curveIsDirty = true;
		}
		draw (lit_point);
		GdkRectangle *rectangle = &(event->expose.area);
		copySurface(win, rectangle);

		retval = true;
		break;
	}

	case Gdk::BUTTON_PRESS:
		snapToElmt = -100;
		if (curve.type!=DCT_Parametric) {
			if (event->button.button == 1) {
				std::vector<double>::iterator itx, ity;
				buttonPressed = true;
				add_modal_grab ();

				// get the pointer position
				getCursorPosition(Gdk::EventType(event->type), event->motion.is_hint!=0, int(event->button.x), int(event->button.y), Gdk::ModifierType(event->button.state));
				findClosestPoint();

				new_type = CSMove;
				if (distanceX > minDistanceX) {
					/* insert a new control point */
					if (num > 0) {
						if (clampedX > curve.x.at(closest_point))
							++closest_point;
					}
					itx = curve.x.begin();
					ity = curve.y.begin();
					for (int i=0; i<closest_point; i++) { itx++; ity++; }
					curve.x.insert (itx, 0);
					curve.y.insert (ity, 0);
					num++;

					// the graph is refreshed only if a new point is created (snaped to a pixel)
					curve.x.at(closest_point) = clampedX;
					curve.y.at(closest_point) = clampedY;

					curveIsDirty = true;
					setDirty(true);
					draw (closest_point);
					notifyListener ();
				}
				grab_point = closest_point;
				lit_point = closest_point;
				ugpX = curve.x.at(closest_point);
				ugpY = curve.y.at(closest_point);
			}
			if (buttonPressed) retval = true;
		}
		break;

	case Gdk::BUTTON_RELEASE:
		snapToElmt = -100;
		if (curve.type!=DCT_Parametric) {
			if (buttonPressed && event->button.button == 1) {
				std::vector<double>::iterator itx, ity;
				int src, dst;
				buttonPressed = false;
				/*  get the pointer position  */
				getCursorPosition(Gdk::EventType(event->type), event->motion.is_hint!=0, int(event->button.x), int(event->button.y), Gdk::ModifierType(event->button.state));
				findClosestPoint();

				remove_modal_grab ();
				int previous_lit_point = lit_point;
				/* delete inactive points: */
				itx = curve.x.begin();
				ity = curve.y.begin();
				for (src = dst = 0; src < num; ++src)
					if (curve.x.at(src) >= 0.0) {
						curve.x.at(dst) = curve.x.at(src);
						curve.y.at(dst) = curve.y.at(src);
						++dst;
						++itx;
						++ity;
					}
				if (dst < src) {
					curve.x.erase (itx, curve.x.end());
					curve.y.erase (ity, curve.y.end());
					if (curve.x.empty()) {
						curve.x.push_back (0);
						curve.y.push_back (0);
						curveIsDirty = true;
						setDirty(true);
						draw (lit_point);
					}
				}
				if (distanceX <= minDistanceX) {
					new_type = CSMove;
					lit_point = closest_point;
				}
				else {
					new_type = CSPlus;
					lit_point = -1;
				}
				if (lit_point != previous_lit_point) {
					setDirty(true);
					draw (lit_point);
				}
				grab_point = -1;
				retval = true;
				notifyListener ();
			}
		}
		break;

	case Gdk::LEAVE_NOTIFY:
		// Pointer can LEAVE even when dragging the point, so we don't modify the cursor in this case
		// The cursor will have to LEAVE another time after the drag...
		if (!buttonPressed)
			if (grab_point == -1) {
				new_type = CSArrow;
				lit_point = -1;
				pipetteR = pipetteG = pipetteB = -1.f;
				setDirty(true);
				draw (lit_point);
			}
		retval = true;
		break;

	case Gdk::MOTION_NOTIFY:
		snapToElmt = -100;
		if (curve.type == DCT_Linear || curve.type == DCT_Spline || curve.type == DCT_NURBS) {

			snapToMinDistY = snapToMinDistX = 10.;
			snapToValY = snapToValX = 0.;
			snapToElmt = -100;

			// get the pointer position
			getCursorPosition(Gdk::EventType(event->type), event->motion.is_hint!=0, int(event->button.x), int(event->button.y), Gdk::ModifierType(event->button.state));

			if (grab_point == -1) {
				// there's no point currently being moved
				int previous_lit_point = lit_point;
				findClosestPoint();
				if (cursorX<0 || cursorX>graphW || cursorY<0 || cursorY>graphH) {
					// the cursor has left the graph area
					new_type = CSArrow;
					lit_point = -1;
				}
				else if (distanceX <= minDistanceX) {
					new_type = CSMove;
					lit_point = closest_point;
				}
				else {
					new_type = CSPlus;
					lit_point = -1;
				}
				if (lit_point != previous_lit_point) {
					setDirty(true);
					draw (lit_point);
				}
			}
			else {
				// a point is being moved

				// bounds of the grabbed point
				double leftBound         = (grab_point == 0    ) ? 0. : curve.x.at(grab_point-1);
				double rightBound        = (grab_point == num-1) ? 1. : curve.x.at(grab_point+1);
				double const bottomBound = 0.;
				double const topBound    = 1.;

				double leftDeletionBound   = leftBound   - minDistanceX;
				double rightDeletionBound  = rightBound  + minDistanceX;
				double bottomDeletionBound = bottomBound - minDistanceY;
				double topDeletionBound    = topBound    + minDistanceY;

				// we memorize the previous position of the point, for optimization purpose
				double prevPosX = curve.x.at(grab_point);
				double prevPosY = curve.y.at(grab_point);

				// we memorize the previous position of the point, for optimization purpose
				ugpX += deltaX;
				ugpY += deltaY;

				// the unclamped grabbed point is brought back in the range when snapTo is active
				if (snapTo) ugpY = CLAMP(ugpY, 0.0, 1.0);

				// handling limitations along X axis
				if (ugpX >= rightDeletionBound && (grab_point > 0 && grab_point < (num-1))) {
					curve.x.at(grab_point) = -1.;
				}
				else if (ugpX <= leftDeletionBound && (grab_point > 0 && grab_point < (num-1))) {
					curve.x.at(grab_point) = -1.;
				}
				else
					// nextPosX is in bounds
					curve.x.at(grab_point) = CLAMP(ugpX, leftBound, rightBound);

				// Handling limitations along Y axis
				if (ugpY >= topDeletionBound && grab_point != 0 && grab_point != num-1) {
					curve.x.at(grab_point) = -1.;
				}
				else if (ugpY <= bottomDeletionBound && grab_point != 0 && grab_point != num-1) {
					curve.x.at(grab_point) = -1.;
				}
				else {
					// snapping point to specific values
					if (snapTo && curve.x.at(grab_point) != -1.) {
						if (grab_point > 0 && grab_point < (curve.y.size()-1)) {
							double prevX = curve.x.at(grab_point-1);
							double prevY = curve.y.at(grab_point-1);
							double nextX = curve.x.at(grab_point+1);
							double nextY = curve.y.at(grab_point+1);

							double ratio = (curve.x.at(grab_point)-prevX)/(nextX-prevX);
							double y = (nextY-prevY) * ratio + prevY;

							if (snapCoordinateY(y, ugpY)) snapToElmt = 1000+grab_point;
						}
						if (grab_point > 0) {
							int prevP = grab_point-1;
							if (snapCoordinateY(curve.y.at(prevP), ugpY)) snapToElmt = prevP;
						}
						if (grab_point < (curve.y.size()-1)) {
							int nextP = grab_point+1;
							if (snapCoordinateY(curve.y.at(nextP), ugpY)) snapToElmt = nextP;
						}
						if (snapCoordinateY(1.0,                    ugpY)) snapToElmt = -3;
						if (snapCoordinateY(curve.x.at(grab_point), ugpY)) snapToElmt = -2;
						if (snapCoordinateY(0.0,                    ugpY)) snapToElmt = -1;

						curve.y.at(grab_point) = snapToValY;
					}
					else {
						// nextPosY is in the bounds
						curve.y.at(grab_point) = CLAMP(ugpY, 0.0, 1.0);
					}
				}

				if (curve.x.at(grab_point) != prevPosX || curve.y.at(grab_point) != prevPosY) {
					// we recalculate the curve only if we have to
					curveIsDirty = true;
					setDirty(true);
					draw (lit_point);
					notifyListener ();
				}
			}
		}

		retval = true;
		break;

	default:
		break;
	}
	if (new_type != cursor_type) {
		cursor_type = new_type;
		cursorManager.setCursor(cursor_type);
	}
	return retval;
}

CursorShape MyDiagonalCurve::motionNotify(CursorShape type, double minDistanceX, double minDistanceY, int num) {
	CursorShape new_type = type;

	return new_type;
}

void MyDiagonalCurve::pipetteMouseOver (EditDataProvider *provider, int modifierKey) {
	if (!provider) {
		// occurs when leaving the preview area -> cleanup the curve editor
		pipetteR = pipetteG = pipetteB = -1.f;
		lit_point = -1;
		return;
	}

	pipetteR = provider->pipetteVal[0];
	pipetteG = provider->pipetteVal[1];
	pipetteB = provider->pipetteVal[2];
	pipetteVal = 0.f;
	if (listener)
		pipetteVal = listener->blendPipetteValues(pipetteR, pipetteG, pipetteB);
	else {
		int n = 0;
		if (pipetteR != -1.f) {
			pipetteVal += pipetteR;
			++n;
		}
		if (pipetteG != -1.f) {
			pipetteVal += pipetteG;
			++n;
		}
		if (pipetteB != -1.f) {
			pipetteVal += pipetteB;
			++n;
		}
		if (n>1)
			pipetteVal /= n;
		else if (!n)
			pipetteVal = -1.f;
	}

	/* graphW and graphH are the size of the graph */
	calcDimensions();

	if ((graphW < 0) || (graphH < 0))
		return;

	double minDistanceX = double(MIN_DISTANCE) / double(graphW-1);

	if (curve.type == DCT_Linear || curve.type == DCT_Spline || curve.type == DCT_NURBS) {
		// get the pointer position
		int px = graphX + int(float(graphW)*pipetteVal);  // WARNING: converting pipetteVal from float to int, precision loss here!
		getCursorPosition(Gdk::MOTION_NOTIFY, false, px, graphY, Gdk::ModifierType(modifierKey));

		if (grab_point == -1) {
			// there's no point currently being moved
			int previous_lit_point = lit_point;
			findClosestPoint();
			if (cursorX<0 || cursorX>graphW || cursorY<0 || cursorY>graphH) {
				// the cursor has left the graph area
				lit_point = -1;
			}
			else if (distanceX <= minDistanceX) {
				lit_point = closest_point;
			}
			else {
				lit_point = -1;
			}
			if (lit_point != previous_lit_point) {
				setDirty(true);
				draw (lit_point);
			}
		}
	}
}


void MyDiagonalCurve::pipetteButton1Pressed(EditDataProvider *provider, int modifierKey) {
	int num = (int)curve.x.size();

	/* graphW and graphH are the size of the graph */
	calcDimensions();

	double minDistanceX = double(MIN_DISTANCE) / double(graphW-1);

	if ((graphW < 0) || (graphH < 0))
		return;

	snapToElmt = -100;
	if (curve.type!=DCT_Parametric) {
		std::vector<double>::iterator itx, ity;
		buttonPressed = true;

		// get the pointer position
		int px = graphX + int(float(graphW)*pipetteVal);  // WARNING: converting pipetteVal from float to int, precision loss here!
		getCursorPosition(Gdk::BUTTON_PRESS, false, px, graphY, Gdk::ModifierType(modifierKey));
		findClosestPoint();

		if (distanceX > minDistanceX) {
			rtengine::DiagonalCurve rtCurve(getPoints(), 200);

			/* insert a new control point */
			if (num > 0) {
				if (clampedX > curve.x.at(closest_point))
					++closest_point;
			}
			itx = curve.x.begin();
			ity = curve.y.begin();
			for (int i=0; i<closest_point; i++) { itx++; ity++; }
			curve.x.insert (itx, 0);
			curve.y.insert (ity, 0);
			num++;

			// the graph is refreshed only if a new point is created (snaped to a pixel)
			curve.x.at(closest_point) = clampedX;
			curve.y.at(closest_point) = clampedY = rtCurve.getVal(pipetteVal);

			curveIsDirty = true;
			setDirty(true);
			draw (closest_point);
			notifyListener ();
		}
		grab_point = closest_point;
		lit_point = closest_point;
		ugpX = curve.x.at(closest_point);
		ugpY = curve.y.at(closest_point);
	}
}

void MyDiagonalCurve::pipetteButton1Released(EditDataProvider *provider) {
	/* graphW and graphH are the size of the graph */
	calcDimensions();

	double minDistanceX = double(MIN_DISTANCE) / double(graphW-1);
	//double minDistanceY = double(MIN_DISTANCE) / double(graphH-1);

	if ((graphW < 0) || (graphH < 0))
		return;

	snapToElmt = -100;
	if (curve.type!=DCT_Parametric) {
		std::vector<double>::iterator itx, ity;
		buttonPressed = false;
		/*  get the pointer position  */
		getCursorPosition(Gdk::EventType(Gdk::BUTTON_RELEASE), false, graphY, 0, Gdk::ModifierType(0));
		findClosestPoint();

		int previous_lit_point = lit_point;

		if (distanceX <= minDistanceX) {
			lit_point = closest_point;
		}
		else {
			lit_point = -1;
		}
		if (lit_point != previous_lit_point) {
			setDirty(true);
			draw (lit_point);
		}
		grab_point = -1;
		//notifyListener ();
	}
}

void MyDiagonalCurve::pipetteDrag(EditDataProvider *provider, int modifierKey) {
	if (curve.type==DCT_Parametric || graphW<0 || graphH<0)
		return;

	snapToMinDistY = snapToMinDistX = 10.;
	snapToValY = snapToValX = 0.;
	snapToElmt = -100;

	/* graphW and graphH are the size of the graph */
	calcDimensions();

	getCursorPosition(Gdk::MOTION_NOTIFY, false, cursorX+graphX, graphY+provider->deltaScreen.y, Gdk::ModifierType(modifierKey));

	// we memorize the previous position of the point, for optimization purpose
	double prevPosX = curve.x.at(grab_point);
	double prevPosY = curve.y.at(grab_point);

	// we memorize the previous position of the point, for optimization purpose
	ugpX += deltaX;
	ugpY += deltaY;

	// the unclamped grabbed point is brought back in the range
	ugpY = CLAMP(ugpY, 0.0, 1.0);

	// snapping point to specific values
	if (snapTo && curve.x.at(grab_point) != -1.) {
		if (grab_point > 0 && grab_point < (curve.y.size()-1)) {
			double prevX = curve.x.at(grab_point-1);
			double prevY = curve.y.at(grab_point-1);
			double nextX = curve.x.at(grab_point+1);
			double nextY = curve.y.at(grab_point+1);

			double ratio = (curve.x.at(grab_point)-prevX)/(nextX-prevX);
			double y = (nextY-prevY) * ratio + prevY;

			if (snapCoordinateY(y, ugpY)) snapToElmt = 1000+grab_point;
		}
		if (grab_point > 0) {
			int prevP = grab_point-1;
			if (snapCoordinateY(curve.y.at(prevP), ugpY)) snapToElmt = prevP;
		}
		if (grab_point < (curve.y.size()-1)) {
			int nextP = grab_point+1;
			if (snapCoordinateY(curve.y.at(nextP), ugpY)) snapToElmt = nextP;
		}
		if (snapCoordinateY(1.0,                    ugpY)) snapToElmt = -3;
		if (snapCoordinateY(curve.x.at(grab_point), ugpY)) snapToElmt = -2;
		if (snapCoordinateY(0.0,                    ugpY)) snapToElmt = -1;

		curve.y.at(grab_point) = snapToValY;
	}
	else {
		// nextPosY is in the bounds
		curve.y.at(grab_point) = ugpY;
	}

	if (curve.x.at(grab_point) != prevPosX || curve.y.at(grab_point) != prevPosY) {
		// we recalculate the curve only if we have to
		curveIsDirty = true;
		setDirty(true);
		draw (lit_point);
		notifyListener ();
	}
}

void MyDiagonalCurve::getCursorPosition(Gdk::EventType evType, bool isHint, int evX, int evY, Gdk::ModifierType modifierKey) {
	int tx, ty;
	int prevCursorX, prevCursorY;
	double incrementX = 1. / double(graphW);
	double incrementY = 1. / double(graphH);

	// getting the cursor position
	switch (evType) {
	case (Gdk::MOTION_NOTIFY) :
		if (isHint) {
			get_window()->get_pointer (tx, ty, mod_type);
		}
		else {
			tx = evX;
			ty = evY;
			mod_type = modifierKey;
		}
		break;
	case (Gdk::BUTTON_PRESS) :
	case (Gdk::BUTTON_RELEASE) :
		tx = evX;
		ty = evY;
		mod_type = modifierKey;
		break;
	default :
		// The cursor position is not available
		return;
		break;
	}

	if (grab_point != -1) {
		prevCursorX = cursorX;
		prevCursorY = cursorY;
	}
	cursorX = tx - graphX;
	cursorY = graphY - ty;

	snapTo = ST_None;

	// update deltaX/Y if the user drags a point
	if (grab_point != -1) {
		// set the dragging factor
		int control_key = mod_type & GDK_CONTROL_MASK;
		int shift_key = mod_type & GDK_SHIFT_MASK;

		// the increment get smaller if modifier key are used, and "snap to" may be enabled
		if (control_key) { incrementX *= 0.05; incrementY *= 0.05; }
		if (shift_key)   { snapTo = true; }

		deltaX = double(cursorX - prevCursorX) * incrementX;
		deltaY = double(cursorY - prevCursorY) * incrementY;
	}
	// otherwise set the position of the new point (modifier keys has no effect here)
	else {
		double tempCursorX = cursorX * incrementX;
		double tempCursorY = cursorY * incrementY;
		clampedX = CLAMP (tempCursorX, 0., 1.);  // X position of the pointer from the origin of the graph
		clampedY = CLAMP (tempCursorY, 0., 1.); // Y position of the pointer from the origin of the graph
	}

}

void MyDiagonalCurve::findClosestPoint() {
    distanceX = 10.0;  distanceY = 10.0;
    closest_point = -1;

    if (curve.type!=DCT_Parametric) {
        for (int i = 0; i < (int)curve.x.size(); i++) {
            double dX = curve.x.at(i) - clampedX;
            double dY = curve.y.at(i) - clampedY;
            double currDistX = dX < 0. ? -dX : dX; //abs (dX);
            double currDistY = dY < 0. ? -dY : dY; //abs (dY);
            if (currDistX < distanceX) {
                distanceX = currDistX;
                distanceY = currDistY;
                closest_point = i;
            }
            else if (currDistX == distanceX && currDistY < distanceY) {
                // there is more than 1 point for that X coordinate, we select the closest point to the cursor
                distanceY = currDistY;
                closest_point = i;
            }
        }
    }
}

std::vector<double> MyDiagonalCurve::getPoints () {
    std::vector<double> result;
    if (curve.type==DCT_Parametric) {
        result.push_back ((double)(DCT_Parametric));
        for (int i=0; i<(int)curve.x.size(); i++) {
            result.push_back (curve.x.at(i));
        }
    }
    else {
        // the first value gives the type of the curve
        if (curve.type==DCT_Linear)
            result.push_back (double(DCT_Linear));
        else if (curve.type==DCT_Spline)
            result.push_back (double(DCT_Spline));
        else if (curve.type==DCT_NURBS)
            result.push_back (double(DCT_NURBS));
        // then we push all the points coordinate
        for (int i=0; i<(int)curve.x.size(); i++) {
            if (curve.x.at(i)>=0) {
                result.push_back (curve.x.at(i));
                result.push_back (curve.y.at(i));
            }
        }
    }
    return result;
}

void MyDiagonalCurve::setPoints (const std::vector<double>& p) {
    int ix = 0;
    DiagonalCurveType t = (DiagonalCurveType)p[ix++];
    curve.type = t;
    if (t==DCT_Parametric) {
        curve.x.clear ();
        curve.y.clear ();
        for (size_t i=1; i<p.size(); i++)
            curve.x.push_back (p[ix++]);
    }
    else {
        curve.x.clear ();
        curve.y.clear ();
        for (size_t i=0; i<p.size()/2; i++) {
            curve.x.push_back (p[ix++]);
            curve.y.push_back (p[ix++]);
        }
        activeParam = -1;
    }
    curveIsDirty = true;
    setDirty(true);
    queue_draw ();
}

void MyDiagonalCurve::setType (DiagonalCurveType t) {

    curve.type = t;
    setDirty(true);
}

void MyDiagonalCurve::setActiveParam (int ac) {
    
    activeParam = ac;
    setDirty(true);
    queue_draw ();
}

int diagonalmchistupdateUI (void* data) {

    MyCurveIdleHelper* mcih = static_cast<MyCurveIdleHelper*>(data);

    if (mcih->destroyed) {
        if (mcih->pending == 1)
            delete mcih;
        else    
            mcih->pending--;

        return 0;
    }

    mcih->clearPixmap ();
    mcih->myCurve->queue_draw ();

    mcih->pending--;

    return 0;
}

void MyDiagonalCurve::updateBackgroundHistogram (LUTu & hist) {

    if (hist) {
        //memcpy (bghist, hist, 256*sizeof(unsigned int));
        for (int i=0; i<256; i++) bghist[i]=hist[i];
        //hist = bghist;
        bghistvalid = true;
    }
    else
        bghistvalid = false;

    mcih->pending++;
    // Can be done outside of the GUI thread
    g_idle_add (diagonalmchistupdateUI, mcih);

}

void MyDiagonalCurve::reset(const std::vector<double> &resetCurve, double identityValue) {

    if (!resetCurve.empty()) {
        setPoints(resetCurve);
        return;
    }

    switch (curve.type) {
    case DCT_Spline :
    case DCT_NURBS :
        curve.x.resize(2);
        curve.y.resize(2);
        curve.x.at(0) = 0.;
        curve.y.at(0) = 0.;
        curve.x.at(1) = 1.;
        curve.y.at(1) = 1.;
        grab_point = -1;
        lit_point = -1;
        curveIsDirty = true;
        break;
    case DCT_Parametric :
        curve.x.resize(7);
        curve.y.clear();
        // the SHCSelector values doesn't really matter for the identity curve display
        curve.x.at(0) = 0.25;
        curve.x.at(1) = 0.50;
        curve.x.at(2) = 0.75;
        curve.x.at(3) = 0.00;
        curve.x.at(4) = 0.00;
        curve.x.at(5) = 0.00;
        curve.x.at(6) = 0.00;
        grab_point = -1;  // not sure that it's necessary
        lit_point = -1;   // not sure that it's necessary
        curveIsDirty = true;
        break;
    default:
        break;
    }
    setDirty(true);
    draw(-1);
}
