// SPDX-License-Identifier: GPL-2.0-or-later
#ifndef SEEN_GRADIENT_DRAG_H
#define SEEN_GRADIENT_DRAG_H

/*
 * On-canvas gradient dragging
 *
 * Authors:
 *   bulia byak <bulia@users.sf.net>
 *   Johan Engelen <j.b.c.engelen@ewi.utwente.nl>
 *   Jon A. Cruz <jon@joncruz.org>
 *   Tavmjong Bah <tavmjong@free.fr>
 *
 * Copyright (C) 2012 Authors
 * Copyright (C) 2007 Johan Engelen
 * Copyright (C) 2005 Authors
 *
 * Released under GNU GPL v2+, read the file 'COPYING' for more information.
 */

#include <cstddef>
#include <sigc++/sigc++.h>
#include <vector>
#include <set>
#include <gdk/gdk.h>
#include <glib.h>
#include <glibmm/ustring.h>

#include <2geom/point.h>

#include "object/sp-gradient.h" // TODO refactor enums to external .h file
#include "object/sp-mesh-array.h"
#include "display/control/canvas-item-ptr.h"

class SPKnot;

class SPDesktop;
class SPCSSAttr;
class SPLinearGradient;
class SPMeshGradient;
class SPItem;
class SPObject;
class SPRadialGradient;
class SPStop;

namespace Inkscape {
class Selection;
class CanvasItemCurve;
} // namespace Inkscape

/**
This class represents a single draggable point of a gradient. It remembers the item
which has the gradient, whether it's fill or stroke, the point type (from the
GrPointType enum), and the point number (needed if more than 2 stops are present).
*/
struct GrDraggable {
    GrDraggable(SPItem *item, GrPointType point_type, guint point_i, Inkscape::PaintTarget fill_or_stroke);
    virtual ~GrDraggable();

    SPItem *item;
    GrPointType point_type;
    gint point_i;  // the stop number of this point ( = 0 POINT_LG_BEGIN and POINT_RG_CENTER)
    Inkscape::PaintTarget fill_or_stroke;

    SPObject *getServer();

    bool mayMerge(GrDraggable *da2);

    inline int equals(GrDraggable *other) {
        return ((item == other->item) && (point_type == other->point_type) && (point_i == other->point_i) && (fill_or_stroke == other->fill_or_stroke));
    }
};

class GrDrag;

/**
This class holds together a visible on-canvas knot and a list of draggables that need to
be moved when the knot moves. Normally there's one draggable in the list, but there may
be more when draggers are snapped together.
*/
struct GrDragger {
    GrDragger(GrDrag *parent, Geom::Point p, GrDraggable *draggable);
    virtual ~GrDragger();

    GrDrag *parent;

    SPKnot *knot;

    // position of the knot, desktop coords
    Geom::Point point;
    // position of the knot before it began to drag; updated when released
    Geom::Point point_original;

    std::vector<GrDraggable *> draggables;

    void addDraggable(GrDraggable *draggable);

    void updateKnotShape();
    void updateTip();

    void select();
    void deselect();
    bool isSelected();

    /* Given one GrDraggable, these all update other draggables belonging to same GrDragger */
    void moveThisToDraggable(SPItem *item, GrPointType point_type, gint point_i, Inkscape::PaintTarget fill_or_stroke, bool write_repr);
    void moveOtherToDraggable(SPItem *item, GrPointType point_type, gint point_i, Inkscape::PaintTarget fill_or_stroke, bool write_repr);
    void updateMidstopDependencies(GrDraggable *draggable, bool write_repr);
    void updateDependencies(bool write_repr);

    /* Update handles/tensors when mesh corner moved */
    void moveMeshHandles( Geom::Point pc_old,  MeshNodeOperation op );

    /* Following are for highlighting mesh handles when corner node is selected. */
    GrDragger *getMgCorner();
    void highlightNode(SPMeshNode *node, bool highlight, Geom::Point corner_pos, int index);
    void highlightCorner(bool highlight);

    bool mayMerge(GrDragger *other);
    bool mayMerge(GrDraggable *da2);

    bool isA(GrPointType point_type);
    bool isA(SPItem *item, GrPointType point_type, Inkscape::PaintTarget fill_or_stroke);
    bool isA(SPItem *item, GrPointType point_type, gint point_i, Inkscape::PaintTarget fill_or_stroke);

    void fireDraggables(bool write_repr, bool scale_radial = false, bool merging_focus = false);

protected:
    void updateControlSizesOverload(SPKnot * knot);
    void updateControlSizes();

private:
    sigc::connection _moved_connection;
    sigc::connection _clicked_connection;
    sigc::connection _doubleclicked_connection;
    sigc::connection _mousedown_connection;
    sigc::connection _ungrabbed_connection;
};


/**
This is the root class of the gradient dragging machinery. It holds lists of GrDraggers
and of lines (simple canvas items). It also remembers one of the draggers as selected.
*/
class GrDrag {
public: // FIXME: make more of this private!

    GrDrag(SPDesktop *desktop);
    virtual ~GrDrag();

    bool isNonEmpty() {return !draggers.empty();}
    bool hasSelection() {return !selected.empty();}
    guint numSelected() {return selected.size();}
    guint numDraggers() {return draggers.size();}

    guint singleSelectedDraggerNumDraggables() {
        return (selected.empty()? 0 : (*(selected.begin()))->draggables.size() );
    }

    guint singleSelectedDraggerSingleDraggableType() {
        return (selected.empty() ? 0 : ((*(selected.begin()))->draggables[0]->point_type)); 
    }

    // especially the selection must be private, fix gradient-context to remove direct access to it
    std::set<GrDragger *> selected; // list of GrDragger*
    void setSelected(GrDragger *dragger, bool add_to_selection = false, bool override = true);
    void setDeselected(GrDragger *dragger);
    void deselectAll();
    void selectAll();
    void selectByCoords(std::vector<Geom::Point> coords);
    void selectByStop(SPStop *stop,  bool add_to_selection = true, bool override = true);
    void selectRect(Geom::Rect const &r);

    bool dropColor(SPItem *item, gchar const *c, Geom::Point p);

    SPStop *addStopNearPoint(SPItem *item, Geom::Point mouse_p, double tolerance);

    void deleteSelected(bool just_one = false);

    guint32 getColor();

    bool keep_selection;

    GrDragger *getDraggerFor(GrDraggable *d);
    GrDragger *getDraggerFor(SPItem *item, GrPointType point_type, gint point_i, Inkscape::PaintTarget fill_or_stroke);

    void grabKnot(GrDragger *dragger, gint x, gint y, guint32 etime);
    void grabKnot(SPItem *item, GrPointType point_type, gint point_i, Inkscape::PaintTarget fill_or_stroke, gint x, gint y, guint32 etime);

    bool local_change;

    SPDesktop *desktop;

    // lists of edges of selection bboxes, to snap draggers to
    std::vector<double> hor_levels;
    std::vector<double> vert_levels;

    std::vector<GrDragger *> draggers;

    struct ItemCurve
    {
        SPItem *item;
        CanvasItemPtr<Inkscape::CanvasItemCurve> curve;
        bool is_fill = true; // Fill or stroke, used by meshes.
        int corner0 = -1; // For meshes
        int corner1 = -1; // For meshes
    };
    std::vector<ItemCurve> item_curves;

    void updateDraggers();
    void refreshDraggers();
    void updateLines();
    void updateLevels();

    bool mouseOver();

    void selected_move_nowrite(double x, double y, bool scale_radial);
    void selected_move(double x, double y, bool write_repr = true, bool scale_radial = false);
    void selected_move_screen(double x, double y);

    bool key_press_handler(GdkEvent *event);

    GrDragger *select_next();
    GrDragger *select_prev();

    void selected_reverse_vector();

private:
    void deselect_all();

    void addLine( SPItem *item, Geom::Point p1, Geom::Point p2, Inkscape::PaintTarget fill_or_stroke);
    void addCurve(SPItem *item, Geom::Point p0, Geom::Point p1, Geom::Point p2, Geom::Point p3,
                  int corner0, int corner1, int handle0, int handle1, Inkscape::PaintTarget fill_or_stroke);

    GrDragger *addDragger(GrDraggable *draggable);

    void addDraggersRadial(SPRadialGradient *rg, SPItem *item, Inkscape::PaintTarget fill_or_stroke);
    void addDraggersLinear(SPLinearGradient *lg, SPItem *item, Inkscape::PaintTarget fill_or_stroke);
    void addDraggersMesh(  SPMeshGradient   *mg, SPItem *item, Inkscape::PaintTarget fill_or_stroke);
    void refreshDraggersMesh(SPMeshGradient *mg, SPItem *item, Inkscape::PaintTarget fill_or_stroke);

    bool styleSet( const SPCSSAttr *css, bool switch_style);

    Glib::ustring makeStopSafeColor( gchar const *str, bool &isNull );

    Inkscape::Selection *selection;
    sigc::connection sel_changed_connection;
    sigc::connection sel_modified_connection;

    sigc::connection style_set_connection;
    sigc::connection style_query_connection;
};

#endif // SEEN_GRADIENT_DRAG_H
/*
  Local Variables:
  mode:c++
  c-file-style:"stroustrup"
  c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
  indent-tabs-mode:nil
  fill-column:99
  End:
*/
// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
