// SPDX-License-Identifier: GPL-2.0-or-later
/** \file
 * LPE <slice> implementation: slices a path with respect to a given line.
 */
/*
 * Authors:
 *   Maximilian Albert
 *   Johan Engelen
 *   Abhishek Sharma
 *   Jabiertxof
 *
 * Copyright (C) Johan Engelen 2007 <j.b.c.engelen@utwente.nl>
 * Copyright (C) Maximilin Albert 2008 <maximilian.albert@gmail.com>
 *
 * Released under GNU GPL v2+, read the file 'COPYING' for more information.
 */

#include "live_effects/lpe-slice.h"

#include <gtkmm.h>

#include "2geom/affine.h"
#include "2geom/path-intersection.h"
#include "display/curve.h"
#include "helper/geom.h"
#include "live_effects/parameter/satellite-reference.h"
#include "object/sp-defs.h"
#include "object/sp-lpe-item.h"
#include "object/sp-path.h"
#include "object/sp-text.h"
#include "path-chemistry.h"
#include "path/path-boolop.h"
#include "style.h"
#include "svg/path-string.h"
#include "svg/svg.h"
#include "ui/icon-names.h"
#include "xml/sp-css-attr.h"

// this is only to flatten nonzero fillrule
#include "livarot/Path.h"
#include "livarot/Shape.h"

// TODO due to internal breakage in glibmm headers, this must be last:
#include <glibmm/i18n.h>

typedef FillRule FillRuleFlatten;

namespace Inkscape {
namespace LivePathEffect {
LPESlice::LPESlice(LivePathEffectObject *lpeobject) :
    Effect(lpeobject),
    // do not change name of this parameter us used in oncommit
    lpesatellites(_("lpesatellites"), _("Items satellites"), "lpesatellites", &wr, this, false),
    allow_transforms(_("Allow Transforms"), _("Allow transforms"), "allow_transforms", &wr, this, true),
    start_point(_("Slice line start"), _("Start point of slice line"), "start_point", &wr, this, _("Adjust start point of slice line")),
    end_point(_("Slice line end"), _("End point of slice line"), "end_point", &wr, this, _("Adjust end point of slice line")),
    center_point(_("Slice line mid"), _("Center point of slice line"), "center_point", &wr, this, _("Adjust center point of slice line"))
{
    registerParameter(&lpesatellites);
    registerParameter(&allow_transforms);
    registerParameter(&start_point);
    registerParameter(&end_point);
    registerParameter(&center_point);
    show_orig_path = true;
    apply_to_clippath_and_mask = false;
    previous_center = Geom::Point(0,0);
    center_point.param_widget_is_visible(false);
    reset = false;
    center_horiz = false;
    center_vert = false;
    allow_transforms_prev = allow_transforms;
    on_remove_all = false;
    container = nullptr;
    satellitestoclipboard = true;
}

LPESlice::~LPESlice() = default;

bool
LPESlice::doOnOpen(SPLPEItem const* lpeitem) {
    bool fixed = false;
    Glib::ustring version = lpeversion.param_getSVGValue();
    if (version < "1.2") {
        std::vector<SPLPEItem *> lpeitems = getCurrrentLPEItems();
        if (lpeitems.size() >= 1) {
            sp_lpe_item_update_patheffect(lpeitems[0], false, true);
        }
        lpeversion.param_setValue("1.2", true);
        fixed = true;
        lpesatellites.write_to_SVG();
    }
    lpesatellites.start_listening();
    lpesatellites.connect_selection_changed();
    return fixed;
}

Gtk::Widget *
LPESlice::newWidget()
{
    // use manage here, because after deletion of Effect object, others might
    // still be pointing to this widget.
    Gtk::Box *vbox = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_VERTICAL));

    vbox->set_border_width(5);
    vbox->set_homogeneous(false);
    vbox->set_spacing(2);
    Gtk::Box *hbox = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_HORIZONTAL, 0));
    Gtk::Button *center_vert_button = Gtk::manage(new Gtk::Button(Glib::ustring(_("Vertical"))));
    center_vert_button->signal_clicked().connect(sigc::mem_fun(*this, &LPESlice::centerVert));
    center_vert_button->set_size_request(110, 20);
    Gtk::Button *center_horiz_button = Gtk::manage(new Gtk::Button(Glib::ustring(_("Horizontal"))));
    center_horiz_button->signal_clicked().connect(sigc::mem_fun(*this, &LPESlice::centerHoriz));
    center_horiz_button->set_size_request(110, 20);
    Gtk::Button *reset_button = Gtk::manage(new Gtk::Button(Glib::ustring(_("Reset styles"))));
    reset_button->signal_clicked().connect(sigc::mem_fun(*this, &LPESlice::resetStyles));
    reset_button->set_size_request(110, 20);

    vbox->pack_start(*hbox, true, true, 2);
    hbox->pack_start(*reset_button, false, false, 2);
    hbox->pack_start(*center_vert_button, false, false, 2);
    hbox->pack_start(*center_horiz_button, false, false, 2);
    std::vector<Parameter *>::iterator it = param_vector.begin();
    while (it != param_vector.end()) {
        if ((*it)->widget_is_visible) {
            Parameter *param = *it;
            Gtk::Widget *widg = dynamic_cast<Gtk::Widget *>(param->param_newWidget());
            Glib::ustring *tip = param->param_getTooltip();
            if (widg) {
                vbox->pack_start(*widg, true, true, 2);
                if (tip) {
                    widg->set_tooltip_markup(*tip);
                } else {
                    widg->set_tooltip_text("");
                    widg->set_has_tooltip(false);
                }
            }
        }

        ++it;
    }
    return dynamic_cast<Gtk::Widget *>(vbox);
}


void
LPESlice::centerVert(){
    center_vert = true;
    makeUndoDone(_("Center Vertical"));
}

void
LPESlice::centerHoriz(){
    center_horiz = true;
    makeUndoDone(_("Center Horizontal"));
}

bool sp_has_path_data(SPItem *item, bool originald) 
{
    auto group = cast<SPGroup>(item);
    if (group) {
        std::vector<SPObject *> childs = group->childList(true);
        for (auto &child : childs) {
            auto item = cast<SPItem>(child);
            if (sp_has_path_data(item, originald)) {
                return true;
            }
        }
    }
    auto shape = cast<SPShape>(item);
    if (shape) {
        SPCurve const *c = shape->curve();
        if (c && !c->is_empty()) {
            return true;
        }
        if (originald) {
            if (shape->hasPathEffectRecursive()) {
                SPCurve const *c = shape->curveBeforeLPE();
                if (c && !c->is_empty()) {
                    return true;
                }
            }
        }
    }
    return false;
}
/*
 * Allow changing original-d to d to "reset" temporary the LPE
 * when the slice doesn't pass through item till sp_lpe_item is crossed
 */
void
LPESlice::originalDtoD(SPShape const *shape, SPCurve *curve)
{
    SPCurve const *c = shape->curveBeforeLPE();
    if (c && !c->is_empty()) {
        curve->set_pathvector(c->get_pathvector());
    }
}

/*
 * Allow changing original-d to d to "reset" temporary the LPE
 * when the slice doesn't pass through item till sp_lpe_item is crossed
 */
void
LPESlice::originalDtoD(SPItem *item)
{
    auto group = cast<SPGroup>(item);
    if (group) {
        std::vector<SPObject *> childs = group->childList(true);
        for (auto &child : childs) {
            auto item = cast<SPItem>(child);
            originalDtoD(item);
        }
        return;
    }
    auto shape = cast<SPShape>(item);
    if (shape) {
        SPCurve const *c = shape->curveBeforeLPE();
        if (c && !c->is_empty()) {
            shape->bbox_vis_cache_is_valid = false;
            shape->bbox_geom_cache_is_valid = false;
            shape->setCurveInsync(c);
            auto str = sp_svg_write_path(c->get_pathvector());
            shape->setAttribute("d", str);
        }
    }
}

void 
LPESlice::doAfterEffect (SPLPEItem const* lpeitem, SPCurve *curve)
{
    Glib::ustring version = lpeversion.param_getSVGValue();
    // this avoid regenerate fake satellites un undo after open a legacy LPE
    if (!is_load && version < "1.2") {
        return;
    }
    SPDocument *document = getSPDoc();
    if (!document) {
        return;
    }
    
    if (document->isSeeking()) {
        return;
    }
    bool is_applied_on = false; 
    if (is_applied) {
        is_applied_on = true;
        is_applied = false;
    }
    bool write = false;
    bool active = !lpesatellites.data().size() || is_load;
    for (auto const &lpereference : lpesatellites.data()) {
        if (lpereference && lpereference->isAttached() && lpereference.get()->getObject() != nullptr) {
            active = true;
        }
    }
    if (!active && !is_load) {
        lpesatellites.clear();
        return;
    }

    LPESlice *nextslice = dynamic_cast<LPESlice *>(sp_lpe_item->getNextLPE(this));
    if (is_visible && (!nextslice || !nextslice->is_visible)) {   
        LPESlice *prevslice = dynamic_cast<LPESlice *>(sp_lpe_item->getPrevLPE(this));
        if (boundingbox_X.isSingular() || boundingbox_Y.isSingular()) {
            for (auto & iter : lpesatellites.data()) {
                SPObject *elemref;
                if (iter && iter->isAttached() && (elemref = iter->getObject())) {
                    if (auto *splpeitem = cast<SPLPEItem>(elemref)) {
                        splpeitem->setHidden(true);
                    }
                }
            }
            return;
        }    
        //ungroup
        if (!is_load && container && container != sp_lpe_item->parent && container != sp_lpe_item->parent->parent) {
            processObjects(LPE_UPDATE);
        } else if (!is_load && container && container != sp_lpe_item->parent) { // group
            processObjects(LPE_UPDATE);
        }
        std::vector<std::pair<Geom::Line, size_t> > slicer = getSplitLines();
        if (!slicer.size()) {
            return;
        }
        container = lpeitem->parent;
        objindex = 0;
        legacy = false;
        bool creation = write;
        split(sp_lpe_item, curve, slicer, 0, creation);
        bool connected = lpesatellites.is_connected();
        if (lpesatellites.data().size() && (creation || !connected)) {
            lpesatellites.write_to_SVG();
            lpesatellites.start_listening();
            lpesatellites.update_satellites();
        }
        lpesatellites.setUpdating(true);
        bool maindata = sp_has_path_data(sp_lpe_item, true);
        for (auto & iter : lpesatellites.data()) {
            SPObject *elemref;
            if (iter && iter->isAttached() && (elemref = iter->getObject())) {
                auto splpeitem = cast<SPLPEItem>(elemref);
                if (splpeitem || lpeitem->isHidden()) {
                    if (!maindata || lpeitem->isHidden()) {
                        splpeitem->setHidden(true);
                    }
                    sp_lpe_item_update_patheffect(splpeitem, false, false);
                }
            }
        }
        if (!maindata) {
            if (!curve) { // group
                originalDtoD(sp_lpe_item);
            } else {
                originalDtoD(getCurrentShape(), curve);
            }
            lpesatellites.setUpdating(false);
            return; 
        }
        reset = false;
        if (is_applied_on && prevslice) {
            sp_lpe_item_update_patheffect(sp_lpe_item, false, false);
            for (auto const &link : prevslice->lpesatellites.data()) {
                if (link && link->isAttached()) {
                    auto spgrp = cast<SPGroup>(link->getObject());
                    auto spit = cast<SPShape>(link->getObject());
                    Glib::ustring transform = "";
                    Glib::ustring patheffects = "";
                    Geom::OptRect _gbbox = Geom::OptRect();
                    if (spgrp) {
                        if (spgrp->getAttribute("transform")) {
                            transform = spgrp->getAttribute("transform");
                        }
                        if (spgrp->getAttribute("inkscape:path-effect")) {
                            patheffects =  spgrp->getAttribute("inkscape:path-effect");
                        }
                        spgrp->setAttribute("transform", nullptr);   
                        spgrp->setAttribute("inkscape:path-effect", nullptr); 
                        _gbbox = spgrp->geometricBounds();
                    }
                    if (spit || spgrp) {
                        for (auto const &link2 : lpesatellites.data()) {
                            if (link2 && link2->isAttached()) {
                                auto spgrp2 = cast<SPGroup>(link2->getObject());
                                auto spit2 = cast<SPShape>(link2->getObject());
                                if (spit && spit2) {
                                    Geom::OptRect _bbox = spit->curveForEdit()->get_pathvector().boundsFast();
                                    Geom::OptRect _bbox2 = spit2->curveForEdit()->get_pathvector().boundsFast();
                                    if (_bbox && _bbox2) {
                                        (*_bbox).expandBy(1);
                                        if ((*_bbox).contains(*_bbox2)) {
                                            spit2->setAttribute("transform", spit->getAttribute("transform"));
                                            spit2->setAttribute("inkscape:path-effect", spit->getAttribute("inkscape:path-effect"));
                                            spit2->setAttribute("style", spit->getAttribute("style"));
                                        }
                                    }
                                } else if (spgrp && spgrp2) {  
                                    Geom::OptRect _gbbox2 = spgrp2->geometricBounds();
                                    if (_gbbox && _gbbox2) {
                                        (*_gbbox).expandBy(1);
                                        if ((*_gbbox).contains(*_gbbox2)) {
                                            spgrp2->setAttribute("transform", transform);
                                            spgrp2->setAttribute("inkscape:path-effect", patheffects);
                                            cloneStyle(spgrp, spgrp2);
                                        }
                                    }
                                }
                            }
                        }
                        if (spgrp) {
                            spgrp->setAttribute("transform", transform);
                            spgrp->setAttribute("inkscape:path-effect", patheffects);
                        }
                    }
                }
            }
            
        }
    } else {
        for (auto const &itemrf : lpesatellites.data()) {
            if (itemrf && itemrf->isAttached()) {
                auto splpeitem = cast<SPLPEItem>(itemrf->getObject());
                if (splpeitem) {
                    splpeitem->setHidden(true);
                    sp_lpe_item_update_patheffect(splpeitem, false, false);
                }
            }
        }
    }
    lpesatellites.setUpdating(false);
}

bool
LPESlice::split(SPItem* item, SPCurve *curve, std::vector<std::pair<Geom::Line, size_t> > slicer, size_t splitindex, bool &creation) {
    bool splited = false;
    size_t nsplits = slicer.size();
    SPDocument *document = getSPDoc();
    if (!document) {
        return splited;
    }
    
    SPObject *elemref = nullptr;
    if (!is_load && container != sp_lpe_item->parent) {
        lpesatellites.read_from_SVG();
        return splited;
    }
    if (objindex < lpesatellites.data().size() && lpesatellites.data()[objindex]) {
        elemref = lpesatellites.data()[objindex]->getObject();
    }
    bool prevreset = reset;
    
    if (!elemref && item->getId()) {
        
        Glib::ustring elemref_id = Glib::ustring("slice-");
        elemref_id += Glib::ustring::format(slicer[splitindex].second);
        elemref_id += "-";
        Glib::ustring clean_id = item->getId();
        //First check is to allow effects on "satellites"
        auto lpeitem = cast<SPLPEItem>(item);
        if (!lpeitem) {
            return splited;
        }
        if (!lpeitem->hasPathEffectOfType(SLICE) && clean_id.find("slice-") != Glib::ustring::npos) {
            clean_id = clean_id.replace(0,6,"");
            elemref_id += clean_id;
        } else {
            elemref_id += clean_id;
        }
        creation = true;
        if (is_load && (elemref = document->getObjectById(elemref_id))) {
            legacy = true;
            lpesatellites.link(elemref, objindex);
        } else {
            reset = true;
            Inkscape::XML::Node *phantom = createPathBase(item);
            if (!container) {
                return splited;
            }
            elemref = container->appendChildRepr(phantom);
            Inkscape::GC::release(phantom);
            lpesatellites.link(elemref, objindex);
        }
    }
    auto other = cast<SPItem>(elemref);
    if (other) {
        objindex++;
        other->setHidden(false);
        if (nsplits) {
            cloneD(item, other, false);
            reset = prevreset;
            splited = splititem(item, curve, slicer[splitindex], true);
            splititem(other, nullptr, slicer[splitindex], false);
            if (!splited) {
                other->setHidden(true);
            }
            splitindex++;
            if (nsplits > splitindex) {
                auto splpeother = cast<SPLPEItem>(other);
                auto splpeitem = cast<SPLPEItem>(item);
                if (item == sp_lpe_item || !splpeitem->hasPathEffectOfType(SLICE)) {
                    split(item, curve, slicer, splitindex, creation);
                    if (other == sp_lpe_item || !splpeother->hasPathEffectOfType(SLICE)) {
                        split(other, nullptr, slicer, splitindex, creation);
                    }
                }
            }
        }
    }
    return splited;
}

std::vector<std::pair<Geom::Line, size_t> >
LPESlice::getSplitLines() {
    std::vector<std::pair<Geom::Line, size_t> > splitlines;
    std::vector<SPLPEItem *> lpeitems = getCurrrentLPEItems();
    if (lpeitems.size() >= 1) {
        sp_lpe_item = lpeitems[0];
    } else {
        return splitlines;
    }
    LPESlice *prevslice = dynamic_cast<LPESlice *>(sp_lpe_item->getPrevLPE(this));
    if (prevslice) {
        splitlines = prevslice->getSplitLines();
    }
    Geom::Line line_separation((Geom::Point)start_point, (Geom::Point)end_point);
    size_t index = sp_lpe_item->getLPEIndex(this);
    std::pair<Geom::Line, size_t> slice = std::make_pair(line_separation, index);
    splitlines.push_back(slice);
    return splitlines;
}

Inkscape::XML::Node *
LPESlice::createPathBase(SPObject *elemref) {
    SPDocument *document = getSPDoc();
    if (!document) {
        return nullptr;
    }
    Inkscape::XML::Document *xml_doc = getSPDoc()->getReprDoc();
    Inkscape::XML::Node *prev = elemref->getRepr();
    auto group = cast<SPGroup>(elemref);
    if (group) {
        Inkscape::XML::Node *container = xml_doc->createElement("svg:g");
        container->setAttribute("transform", prev->attribute("transform"));
        container->setAttribute("mask", prev->attribute("mask"));
        container->setAttribute("clip-path", prev->attribute("clip-path"));
        std::vector<SPItem*> const item_list = group->item_list();
        Inkscape::XML::Node *previous = nullptr;
        for (auto sub_item : item_list) {
            Inkscape::XML::Node *resultnode = createPathBase(sub_item);
            container->addChild(resultnode, previous);
            previous = resultnode;
        }
        return container;
    }
    Inkscape::XML::Node *resultnode = xml_doc->createElement("svg:path");
    resultnode->setAttribute("transform", prev->attribute("transform"));
    resultnode->setAttribute("mask", prev->attribute("mask"));
    resultnode->setAttribute("clip-path", prev->attribute("clip-path"));
    return resultnode;
}

void
LPESlice::cloneD(SPObject *orig, SPObject *dest, bool is_original)
{
    if (!is_original && !g_strcmp0(sp_lpe_item->getId(), orig->getId())) {
        is_original = true;
    }
    SPDocument *document = getSPDoc();
    if (!document) {
        return;
    }
    auto originalitem = cast<SPItem>(orig);
    if ( is<SPGroup>(orig) && is<SPGroup>(dest) && cast<SPGroup>(orig)->getItemCount() == cast<SPGroup>(dest)->getItemCount() ) {
        if (reset) {
            cloneStyle(orig, dest);
        }
        if (!allow_transforms) {
            auto str = sp_svg_transform_write(originalitem->transform);
            dest->setAttributeOrRemoveIfEmpty("transform", str);
        }
        std::vector< SPObject * > childs = orig->childList(true);
        size_t index = 0;
        for (auto &child : childs) {
            SPObject *dest_child = dest->nthChild(index);
            cloneD(child, dest_child, is_original);
            index++;
        }
        return;
    }

    auto shape = cast<SPShape>(orig);
    auto path = cast<SPPath>(dest);
    if (path && shape) {
        SPCurve const *c = shape->curve();
        if (c && !c->is_empty()) {
            auto str = sp_svg_write_path(c->get_pathvector());
            if (path->hasPathEffectRecursive()) {
                sp_lpe_item_enable_path_effects(path, false);
                dest->setAttribute("inkscape:original-d", str);
                sp_lpe_item_enable_path_effects(path, true);
                dest->setAttribute("d", str);
            } else {
                dest->setAttribute("d", str);
            }
            if (!allow_transforms) {
                auto str = sp_svg_transform_write(originalitem->transform);
                dest->setAttributeOrRemoveIfEmpty("transform", str);
            }
            if (reset) {
                cloneStyle(orig, dest);
            }
        }
    }
}

static fill_typ GetFillTyp(SPItem *item)
{
    SPCSSAttr *css = sp_repr_css_attr(item->getRepr(), "style");
    gchar const *val = sp_repr_css_property(css, "fill-rule", nullptr);
    if (val && strcmp(val, "nonzero") == 0) {
        return fill_nonZero;
    } else if (val && strcmp(val, "evenodd") == 0) {
        return fill_oddEven;
    } else {
        return fill_nonZero;
    }
}

bool
LPESlice::splititem(SPItem* item, SPCurve * curve, std::pair<Geom::Line, size_t> slicer, bool toggle, bool is_original, Geom::Affine tpass, bool top) 
{
    bool splited = false;
    if (!is_original && !g_strcmp0(sp_lpe_item->getId(), item->getId())) {
        is_original = true;
    }
    Geom::Line line_separation = slicer.first;
    Geom::Point s = line_separation.initialPoint();
    Geom::Point e = line_separation.finalPoint();
    Geom::Point center = Geom::middle_point(s, e);
    auto group = cast<SPGroup>(item);
    if (group) {
        Geom::Affine t = group->transform * tpass;
        if (top) {
            t = Geom::identity();
        }
        std::vector<SPObject *> childs = group->childList(true);
        for (auto &child : childs) {
            auto dest_child = cast<SPItem>(child);
            // groups not need update curve
            splited = splititem(dest_child, nullptr, slicer, toggle, is_original, t, false) ? true : splited;
        }
        if (!is_original) { 
            sp_lpe_item_update_patheffect(group, false, false);
        }
        return splited;
    }
    auto shape = cast<SPShape>(item);
    auto path = cast<SPPath>(item);
    if (shape) {
        SPCurve const *c;
        c = shape->curve();
        if (c) {
            Geom::PathVector original_pathv = pathv_to_linear_and_cubic_beziers(c->get_pathvector());
            sp_flatten(original_pathv, GetFillTyp(shape));
            Geom::PathVector path_out;
            Geom::Affine t = shape->transform * tpass;
            if (!is<SPGroup>(sp_lpe_item)) {
                t = Geom::identity();
            }
            for (auto & path_it : original_pathv) {
                path_it *= t;
                if (path_it.empty()) {
                    continue;
                }
                Geom::PathVector tmp_pathvector;
                double time_start = 0.0;
                int position = 0;
                bool end_open = false;
                if (path_it.closed()) {
                    const Geom::Curve &closingline = path_it.back_closed();
                    if (!are_near(closingline.initialPoint(), closingline.finalPoint())) {
                        end_open = true;
                    }
                }
                Geom::Path original = path_it;
                if (end_open && path_it.closed()) {
                    original.close(false);
                    original.appendNew<Geom::LineSegment>( original.initialPoint() );
                    original.close(true);
                }
                double dir = line_separation.angle();
                Geom::Ray ray = line_separation.ray(0);
                double diagonal = Geom::distance(Geom::Point(boundingbox_X.min(),boundingbox_Y.min()),Geom::Point(boundingbox_X.max(),boundingbox_Y.max()));
                Geom::Rect bbox(Geom::Point(boundingbox_X.min(),boundingbox_Y.min()),Geom::Point(boundingbox_X.max(),boundingbox_Y.max()));
                double size_divider = Geom::distance(center, bbox) + diagonal;
                s = Geom::Point::polar(dir,size_divider) + center;
                e = Geom::Point::polar(dir + Geom::rad_from_deg(180),size_divider) + center;
                Geom::Path divider = Geom::Path(s);
                divider.appendNew<Geom::LineSegment>(e);
                std::vector<double> crossed;
                if (Geom::are_near(s,e)) {
                    continue;
                }
                Geom::Crossings cs = crossings(original, divider);
                for(auto & c : cs) {
                    crossed.push_back(c.ta);
                }
                double angle = Geom::deg_from_rad(ray.angle());
                bool toggleside = !(angle > 0 && angle < 180);
                std::sort(crossed.begin(), crossed.end());
                for (double time_end : crossed) {
                    if (time_start != time_end && time_end - time_start > Geom::EPSILON) {
                        Geom::Path portion = original.portion(time_start, time_end);
                        if (!portion.empty()) {
                            Geom::Point middle = portion.pointAt((double)portion.size()/2.0);
                            position = Geom::sgn(Geom::cross(e - s, middle - s));
                            if (toggleside) {
                                position *= -1;
                            }
                            if (toggle) {
                                position *= -1;
                            }
                            if (position == 1) {
                                tmp_pathvector.push_back(portion);
                            }
                            portion.clear();
                        }
                    }
                    time_start = time_end;
                }
                position = Geom::sgn(Geom::cross(e - s, original.finalPoint() - s));
                if (toggleside) {
                    position *= -1;
                }
                if (toggle) {
                    position *= -1;
                }
                if (cs.size()!=0 && (position == 1)) {
                    if (time_start != original.size() && original.size() - time_start > Geom::EPSILON) {
                        Geom::Path portion = original.portion(time_start, original.size());
                        if (!portion.empty()) {
                            if (!original.closed()) {
                                tmp_pathvector.push_back(portion);
                            } else {
                                if (cs.size() > 1 && tmp_pathvector.size() > 0 && tmp_pathvector[0].size() > 0 ) {
                                    tmp_pathvector[0] = tmp_pathvector[0].reversed();
                                    portion = portion.reversed();
                                    portion.setInitial(tmp_pathvector[0].finalPoint());
                                    tmp_pathvector[0].append(portion);
                                    tmp_pathvector[0] = tmp_pathvector[0].reversed();
                                } else {
                                    tmp_pathvector.push_back(portion);
                                }
                            }
                            portion.clear();
                        }
                    }
                }
                if (cs.size() > 0 && original.closed()) {
                    for (auto &path : tmp_pathvector) {
                        if (!path.closed()) {
                            path.close();
                        }
                    }
                }
                if (cs.size() == 0 && position == 1) {
                    splited = false;
                    tmp_pathvector.push_back(original);
                } else {
                    splited = true;
                }
                tmp_pathvector *= t.inverse();
                path_out.insert(path_out.end(), tmp_pathvector.begin(), tmp_pathvector.end());
                
                tmp_pathvector.clear();
            }
            if (curve && is_original) {
                curve->set_pathvector(path_out);
            }
            if (shape->curve()) {
                shape->bbox_vis_cache_is_valid = false;
                shape->bbox_geom_cache_is_valid = false;
                shape->setCurveInsync(SPCurve(path_out));
                auto str = sp_svg_write_path(path_out);
                if (!is_original && shape->hasPathEffectRecursive()) {
                    sp_lpe_item_enable_path_effects(shape, false);
                    if (path) {
                        shape->setAttribute("inkscape:original-d", str);
                    } else {
                        shape->setAttribute("d", str);
                    }
                    sp_lpe_item_enable_path_effects(shape, true);
                } else {
                    shape->setAttribute("d", str);
                }
            }
        }
    }
    return splited;
}

void
LPESlice::doBeforeEffect (SPLPEItem const* lpeitem)
{
    SPDocument *document = getSPDoc();
    if (!document) {
        return;
    }
    if (!lpesatellites.data().size()) {
        lpesatellites.read_from_SVG();
        if (lpesatellites.data().size()) {
            lpesatellites.update_satellites();
        }
    }
    using namespace Geom;
    original_bbox(lpeitem, false, true);
    Point point_a(boundingbox_X.max(), boundingbox_Y.min());
    Point point_b(boundingbox_X.max(), boundingbox_Y.max());
    Point point_c(boundingbox_X.middle(), boundingbox_Y.middle());
    if (center_vert) {
        double dista = std::abs(end_point[Geom::Y] - boundingbox_Y.max());
        double distb = std::abs(start_point[Geom::Y] - boundingbox_Y.max());
        previous_center = Geom::Point(Geom::infinity(), g_random_double_range(0, 1000));
        end_point.param_setValue(
            Geom::Point(center_point[Geom::X], dista <= distb ? boundingbox_Y.min() : boundingbox_Y.max()), true);
        start_point.param_setValue(
            Geom::Point(center_point[Geom::X], dista > distb ? boundingbox_Y.min() : boundingbox_Y.max()), true);
        center_point.param_setValue(Geom::middle_point((Geom::Point)start_point, (Geom::Point)end_point), true);
        //force update
        center_vert = false;
    } else if (center_horiz) {
        double dista = std::abs(end_point[Geom::X] - boundingbox_X.max());
        double distb = std::abs(start_point[Geom::X] - boundingbox_X.max());
        previous_center = Geom::Point(Geom::infinity(), g_random_double_range(0, 1000));
        end_point.param_setValue(
            Geom::Point(dista <= distb ? boundingbox_X.min() : boundingbox_X.max(), center_point[Geom::Y]), true);
        start_point.param_setValue(
            Geom::Point(dista > distb ? boundingbox_X.min() : boundingbox_X.max(), center_point[Geom::Y]), true);
        center_point.param_setValue(Geom::middle_point((Geom::Point)start_point, (Geom::Point)end_point), true);
        //force update
        center_horiz = false;
    } else {
        if ((Geom::Point)start_point == (Geom::Point)end_point) {
            start_point.param_setValue(point_a, true);
            end_point.param_setValue(point_b, true);
            previous_center = Geom::middle_point((Geom::Point)start_point, (Geom::Point)end_point);
            center_point.param_setValue(previous_center, true);
            return;
        }
        if (!are_near(previous_center, (Geom::Point)center_point, 0.001)) {
            Geom::Point trans = center_point - Geom::middle_point((Geom::Point)start_point, (Geom::Point)end_point);
            start_point.param_setValue(start_point + trans, true);
            end_point.param_setValue(end_point + trans, true);
        }
        center_point.param_setValue(Geom::middle_point((Geom::Point)start_point, (Geom::Point)end_point), true);
        previous_center = Geom::middle_point((Geom::Point)start_point, (Geom::Point)end_point);
    }
    if (allow_transforms_prev != allow_transforms) {
        LPESlice *nextslice = dynamic_cast<LPESlice *>(sp_lpe_item->getNextLPE(this));
        while (nextslice) {
            if (nextslice->allow_transforms != allow_transforms) {
                nextslice->allow_transforms_prev = allow_transforms;
                nextslice->allow_transforms.param_setValue(allow_transforms);
            }
            nextslice = dynamic_cast<LPESlice *>(sp_lpe_item->getNextLPE(nextslice));
        }
        LPESlice *prevslice = dynamic_cast<LPESlice *>(sp_lpe_item->getPrevLPE(this));
        while (prevslice) {
            if (prevslice->allow_transforms != allow_transforms) {
                prevslice->allow_transforms_prev = allow_transforms;
                prevslice->allow_transforms.param_setValue(allow_transforms);
            }
            prevslice = dynamic_cast<LPESlice *>(sp_lpe_item->getNextLPE(prevslice));
        }
    }
    allow_transforms_prev = allow_transforms;
}

void LPESlice::cloneStyle(SPObject *orig, SPObject *dest)
{
    for (auto iter : orig->style->properties()) {
        if (iter->style_src != SPStyleSrc::UNSET) {
            auto key = iter->id();
            if (key != SPAttr::FONT && key != SPAttr::D && key != SPAttr::MARKER) {
                const gchar *attr = orig->getAttribute(iter->name().c_str());
                if (attr) {
                    dest->setAttribute(iter->name(), attr);
                }
            }
        }
    }
    dest->setAttribute("style", orig->getAttribute("style"));
}

void
LPESlice::resetStyles(){
    std::vector<SPLPEItem *> lpeitems = getCurrrentLPEItems();
    if (lpeitems.size() == 1) {
        sp_lpe_item = lpeitems[0];
        LPESlice *nextslice = dynamic_cast<LPESlice *>(sp_lpe_item->getNextLPE(this));
        while (nextslice) {
            nextslice->reset = true;
            nextslice = dynamic_cast<LPESlice *>(sp_lpe_item->getNextLPE(nextslice));
        }
        reset = true;
        sp_lpe_item_update_patheffect(sp_lpe_item, false, false);
    }
}

void
LPESlice::doOnVisibilityToggled(SPLPEItem const* /*lpeitem*/)
{
    if (!is_visible) {
        for (auto const &itemrf : lpesatellites.data()) {
            if (itemrf && itemrf->isAttached()) {
                auto splpeitem = cast<SPLPEItem>(itemrf->getObject());
                if (splpeitem) {
                    splpeitem->setHidden(true);
                    sp_lpe_item_update_patheffect(splpeitem, false, false);
                }
            }
        }
    }
}
void
LPESlice::doOnRemove(SPLPEItem const* lpeitem)
{
    if (keep_paths) {
        processObjects(LPE_TO_OBJECTS);
        return;
    }
    processObjects(LPE_ERASE);
}

void
LPESlice::doOnApply (SPLPEItem const* lpeitem)
{
    using namespace Geom;
    original_bbox(lpeitem, false, true);
    LPESlice *prevslice = dynamic_cast<LPESlice *>(sp_lpe_item->getPrevLPE(this));
    if (prevslice) {
        allow_transforms_prev = prevslice->allow_transforms;
        allow_transforms.param_setValue(prevslice->allow_transforms);
    }
    Point point_a(boundingbox_X.middle(), boundingbox_Y.min());
    Point point_b(boundingbox_X.middle(), boundingbox_Y.max());
    Point point_c(boundingbox_X.middle(), boundingbox_Y.middle());
    start_point.param_setValue(point_a, true);
    start_point.param_update_default(point_a);
    end_point.param_setValue(point_b, true);
    end_point.param_update_default(point_b);
    center_point.param_setValue(point_c, true);
    end_point.param_update_default(point_c);
    previous_center = center_point;
    lpeversion.param_setValue("1.2", true);
    sp_lpe_item_update_patheffect(sp_lpe_item, false, false, true);
}


Geom::PathVector
LPESlice::doEffect_path (Geom::PathVector const & path_in)
{
    return path_in;
}

void
LPESlice::addCanvasIndicators(SPLPEItem const */*lpeitem*/, std::vector<Geom::PathVector> &hp_vec)
{
    using namespace Geom;
    hp_vec.clear();
    Geom::Path path;
    Geom::Point s = start_point;
    Geom::Point e = end_point;
    path.start( s );
    path.appendNew<Geom::LineSegment>( e );
    Geom::PathVector helper;
    helper.push_back(path);
    hp_vec.push_back(helper);
}

} //namespace LivePathEffect
} /* namespace Inkscape */

/*
  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 :
