// SPDX-License-Identifier: GPL-2.0-or-later
/*
 * Simple bezier bpath CanvasItem for inkscape
 *
 * Authors:
 *   Lauris Kaplinski <lauris@ximian.com>
 *   Jon A. Cruz <jon@joncruz.org>
 *
 * Copyright (C) 2001 Lauris Kaplinski and Ximian, Inc.
 *
 * Released under GNU GPL v2+, read the file 'COPYING' for more information.
 *
 */

#include <sstream>
#include <cstring>
#include "desktop.h"

#include "color.h"
#include "display/sp-canvas-group.h"
#include "display/sp-canvas-util.h"
#include "display/canvas-bpath.h"
#include "display/curve.h"
#include "display/cairo-utils.h"
#include "helper/geom.h"
#include "display/sp-canvas.h"

static void sp_canvas_bpath_destroy(SPCanvasItem *object);

static void sp_canvas_bpath_update (SPCanvasItem *item, Geom::Affine const &affine, unsigned int flags);
static void sp_canvas_bpath_render (SPCanvasItem *item, SPCanvasBuf *buf);
static double sp_canvas_bpath_point (SPCanvasItem *item, Geom::Point p, SPCanvasItem **actual_item);

G_DEFINE_TYPE(SPCanvasBPath, sp_canvas_bpath, SP_TYPE_CANVAS_ITEM);

static void sp_canvas_bpath_class_init(SPCanvasBPathClass *klass)
{
    SPCanvasItemClass *item_class = (SPCanvasItemClass *) klass;

    item_class->destroy = sp_canvas_bpath_destroy;
    item_class->update = sp_canvas_bpath_update;
    item_class->render = sp_canvas_bpath_render;
    item_class->point = sp_canvas_bpath_point;
}

static void
sp_canvas_bpath_init (SPCanvasBPath * bpath)
{
    bpath->fill_rgba = 0x00000000;
    bpath->fill_rule = SP_WIND_RULE_EVENODD;

    bpath->stroke_rgba = 0x00000000;
    bpath->stroke_width = 1.0;
    bpath->stroke_linejoin = SP_STROKE_LINEJOIN_MITER;
    bpath->stroke_linecap = SP_STROKE_LINECAP_BUTT;
    bpath->stroke_miterlimit = 11.0;
    bpath->phantom_line = false;
}

static void sp_canvas_bpath_destroy(SPCanvasItem *object)
{
    SPCanvasBPath *cbp = SP_CANVAS_BPATH (object);

    if (cbp->curve) {
        cbp->curve = cbp->curve->unref();
    }

    if (SP_CANVAS_ITEM_CLASS(sp_canvas_bpath_parent_class)->destroy)
        (* SP_CANVAS_ITEM_CLASS(sp_canvas_bpath_parent_class)->destroy) (object);
}

static void sp_canvas_bpath_update(SPCanvasItem *item, Geom::Affine const &affine, unsigned int flags)
{
    SPCanvasBPath *cbp = SP_CANVAS_BPATH(item);

    item->canvas->requestRedraw((int)item->x1 - 1, (int)item->y1 - 1, (int)item->x2 + 1 , (int)item->y2 + 1);

    if (reinterpret_cast<SPCanvasItemClass *>(sp_canvas_bpath_parent_class)->update) {
        reinterpret_cast<SPCanvasItemClass *>(sp_canvas_bpath_parent_class)->update(item, affine, flags);
    }

    sp_canvas_item_reset_bounds (item);

    if (!cbp->curve) return;

    cbp->affine = affine;

    Geom::OptRect bbox = bounds_exact_transformed(cbp->curve->get_pathvector(), affine);

    if (bbox) {
        item->x1 = (int)floor(bbox->min()[Geom::X]) - 1;
        item->y1 = (int)floor(bbox->min()[Geom::Y]) - 1;
        item->x2 = (int)ceil(bbox->max()[Geom::X]) + 1;
        item->y2 = (int)ceil(bbox->max()[Geom::Y]) + 1;
    } else {
        item->x1 = 0;
        item->y1 = 0;
        item->x2 = 0;
        item->y2 = 0;
    }
    item->canvas->requestRedraw((int)item->x1, (int)item->y1, (int)item->x2, (int)item->y2);
}

static void
sp_canvas_bpath_render (SPCanvasItem *item, SPCanvasBuf *buf)
{
    SPCanvasBPath *cbp = SP_CANVAS_BPATH (item);

    Geom::Rect area = buf->rect;

    if ( !cbp->curve  || 
         ((cbp->stroke_rgba & 0xff) == 0 && (cbp->fill_rgba & 0xff) == 0 ) || 
         cbp->curve->get_segment_count() < 1)
        return;

    if (!buf->ct)
        return;

    bool dofill = ((cbp->fill_rgba & 0xff) != 0);
    bool dostroke = ((cbp->stroke_rgba & 0xff) != 0);

    cairo_set_tolerance(buf->ct, 0.5);
    cairo_new_path(buf->ct);

    feed_pathvector_to_cairo (buf->ct, cbp->curve->get_pathvector(), cbp->affine, area,
        /* optimized_stroke = */ !dofill, 1);

    if (dofill) {
        // RGB / BGR
        ink_cairo_set_source_rgba32(buf->ct, cbp->fill_rgba);
        cairo_set_fill_rule(buf->ct, cbp->fill_rule == SP_WIND_RULE_EVENODD? CAIRO_FILL_RULE_EVEN_ODD
                            : CAIRO_FILL_RULE_WINDING);
        cairo_fill_preserve(buf->ct);
    }

    if (dostroke && cbp->phantom_line) {
        ink_cairo_set_source_rgba32(buf->ct, 0xffffff4d);
        cairo_set_line_width(buf->ct, 2);
        if (cbp->dashes[0] != 0 && cbp->dashes[1] != 0) {
            cairo_set_dash (buf->ct, cbp->dashes, 2, 0);
        }
        cairo_stroke(buf->ct);
        cairo_set_tolerance(buf->ct, 0.5);
        cairo_new_path(buf->ct);
        feed_pathvector_to_cairo (buf->ct, cbp->curve->get_pathvector(), cbp->affine, area,
        /* optimized_stroke = */ !dofill, 1);
        ink_cairo_set_source_rgba32(buf->ct, cbp->stroke_rgba);
        cairo_set_line_width(buf->ct, 1);
        cairo_stroke(buf->ct);
    } else if (dostroke) {
        ink_cairo_set_source_rgba32(buf->ct, cbp->stroke_rgba);
        cairo_set_line_width(buf->ct, 1);
        if (cbp->dashes[0] != 0 && cbp->dashes[1] != 0) {
            cairo_set_dash (buf->ct, cbp->dashes, 2, 0);
        }
        cairo_stroke(buf->ct);
    } else {
        cairo_new_path(buf->ct);
    }
}

static double
sp_canvas_bpath_point (SPCanvasItem *item, Geom::Point p, SPCanvasItem **actual_item)
{
    SPCanvasBPath *cbp = SP_CANVAS_BPATH (item);

    if ( !cbp->curve  || 
         ((cbp->stroke_rgba & 0xff) == 0 && (cbp->fill_rgba & 0xff) == 0 ) || 
         cbp->curve->get_segment_count() < 1)
        return Geom::infinity();

    double width = 0.5;
    Geom::Rect viewbox = item->canvas->getViewbox();
    viewbox.expandBy (width);
    double dist = Geom::infinity();
    pathv_matrix_point_bbox_wind_distance(cbp->curve->get_pathvector(), cbp->affine, p, nullptr, nullptr, &dist, 0.5, &viewbox);

    if (dist <= 1.0) {
        *actual_item = item;
    }

    return dist;
}

SPCanvasItem *
sp_canvas_bpath_new (SPCanvasGroup *parent, SPCurve *curve, bool phantom_line)
{
    g_return_val_if_fail (parent != nullptr, NULL);
    g_return_val_if_fail (SP_IS_CANVAS_GROUP (parent), NULL);

    SPCanvasItem *item = sp_canvas_item_new (parent, SP_TYPE_CANVAS_BPATH, nullptr);

    sp_canvas_bpath_set_bpath (SP_CANVAS_BPATH (item), curve, phantom_line);

    return item;
}

void
sp_canvas_bpath_set_bpath (SPCanvasBPath *cbp, SPCurve *curve, bool phantom_line)
{
    g_return_if_fail (cbp != nullptr);
    g_return_if_fail (SP_IS_CANVAS_BPATH (cbp));

    cbp->phantom_line = phantom_line;
    if (cbp->curve) {
        cbp->curve = cbp->curve->unref();
    }

    if (curve) {
        cbp->curve = curve->ref();
    }

    sp_canvas_item_request_update (SP_CANVAS_ITEM (cbp));
}

void
sp_canvas_bpath_set_fill (SPCanvasBPath *cbp, guint32 rgba, SPWindRule rule)
{
    g_return_if_fail (cbp != nullptr);
    g_return_if_fail (SP_IS_CANVAS_BPATH (cbp));

    cbp->fill_rgba = rgba;
    cbp->fill_rule = rule;

    sp_canvas_item_request_update (SP_CANVAS_ITEM (cbp));
}

void
sp_canvas_bpath_set_stroke (SPCanvasBPath *cbp, guint32 rgba, gdouble width, SPStrokeJoinType join, SPStrokeCapType cap, double dash, double gap)
{
    g_return_if_fail (cbp != nullptr);
    g_return_if_fail (SP_IS_CANVAS_BPATH (cbp));

    cbp->stroke_rgba = rgba;
    cbp->stroke_width = MAX (width, 0.1);
    cbp->stroke_linejoin = join;
    cbp->stroke_linecap = cap;
    cbp->dashes[0] = dash;
    cbp->dashes[1] = gap;

    sp_canvas_item_request_update (SP_CANVAS_ITEM (cbp));
}

/*
  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:fileencoding=utf-8:textwidth=99 :
