// SPDX-License-Identifier: GPL-2.0-or-later
/*
 * Authors:
 *   Jabier Arraiza Cenoz
 *
 * Copyright (C) Jabier Arraiza Cenoz 2014 <jabier.arraiza@marker.es>
 * Released under GNU GPL v2+, read the file 'COPYING' for more information.
 */

#include <gtkmm.h>
#include "live_effects/lpe-show_handles.h"
#include <2geom/sbasis-to-bezier.h>
#include <2geom/svg-path-parser.h>
#include "helper/geom.h"
#include "desktop-style.h"
#include "display/curve.h"
#include "svg/svg.h"

#include "object/sp-shape.h"
#include "style.h"

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

namespace Inkscape {
namespace LivePathEffect {

LPEShowHandles::LPEShowHandles(LivePathEffectObject *lpeobject)
    : Effect(lpeobject),
      nodes(_("Show nodes"), _("Show nodes"), "nodes", &wr, this, true),
      handles(_("Show handles"), _("Show handles"), "handles", &wr, this, true),
      original_path(_("Show path"), _("Show path"), "original_path", &wr, this, true),
      show_center_node(_("Show center of node"), _("Show center of node"), "show_center_node", &wr, this, false),
      original_d(_("Show original"), _("Show original"), "original_d", &wr, this, false),
      scale_nodes_and_handles(_("Scale nodes and handles"), _("Scale nodes and handles"), "scale_nodes_and_handles", &wr, this, 10)
{
    registerParameter(&nodes);
    registerParameter(&handles);
    registerParameter(&original_path);
    registerParameter(&show_center_node);
    registerParameter(&original_d);
    registerParameter(&scale_nodes_and_handles);
    scale_nodes_and_handles.param_set_range(0, 500.);
    scale_nodes_and_handles.param_set_increments(1, 1);
    scale_nodes_and_handles.param_set_digits(2);
    stroke_width = 1.0;
}

/**
 * Sets default styles to element
 * this permanently remove.some styles of the element
 */

void LPEShowHandles::doOnApply(SPLPEItem const* lpeitem)
{
    SPLPEItem* item = const_cast<SPLPEItem*>(lpeitem);
    SPCSSAttr *css = sp_repr_css_attr_new ();
    sp_repr_css_set_property (css, "stroke", "black");
    sp_repr_css_set_property (css, "stroke-width", "1");
    sp_repr_css_set_property (css, "stroke-linecap", "butt");
    sp_repr_css_set_property(css, "fill", "none");

    sp_desktop_apply_css_recursive(item, css, true);
    sp_repr_css_attr_unref (css);
}

void LPEShowHandles::doBeforeEffect (SPLPEItem const* lpeitem)
{
    stroke_width = lpeitem->style->stroke_width.computed;
}

Geom::PathVector LPEShowHandles::doEffect_path (Geom::PathVector const & path_in)
{
    Geom::PathVector path_out;
    Geom::PathVector original_pathv = pathv_to_linear_and_cubic_beziers(path_in);
    if(original_path) {
        for (const auto & i : path_in) {
            path_out.push_back(i);
        }
    }
    if(!outline_path.empty()) {
        outline_path.clear();
    }
    if (original_d) {
        if (auto const shape_curve = current_shape->curveForEdit()) {
            Geom::PathVector original_curve = shape_curve->get_pathvector();
            if(original_path) {
                for (const auto & i : original_curve) {
                    path_out.push_back(i);
                }
            }
            original_pathv.insert(original_pathv.end(), original_curve.begin(), original_curve.end());
        }
        generateHelperPath(original_pathv);
    } else {
        generateHelperPath(original_pathv);
    }
    for (const auto & i : outline_path) {
        path_out.push_back(i);
    }

    return path_out;
}

void
LPEShowHandles::generateHelperPath(Geom::PathVector result)
{
    if(!handles && !nodes) {
        return;
    }

    Geom::CubicBezier const *cubic = nullptr;
    for (auto & path_it : result) {
        //Si está vacío...
        if (path_it.empty()) {
            continue;
        }
        //Itreadores
        Geom::Path::iterator curve_it1 = path_it.begin(); // incoming curve
        Geom::Path::iterator curve_it2 = ++(path_it.begin()); // outgoing curve
        Geom::Path::iterator curve_endit = path_it.end_default(); // this determines when the loop has to stop

        if (path_it.closed()) {
            // if the path is closed, maybe we have to stop a bit earlier because the
            // closing line segment has zerolength.
            Geom::Curve const &closingline = path_it.back_closed(); // the closing line segment is always of type
            // Geom::LineSegment.
            if (are_near(closingline.initialPoint(), closingline.finalPoint())) {
                // closingline.isDegenerate() did not work, because it only checks for
                // *exact* zero length, which goes wrong for relative coordinates and
                // rounding errors...
                // the closing line segment has zero-length. So stop before that one!
                curve_endit = path_it.end_open();
            }
        }
        if(nodes) {
            Geom::NodeType nodetype = Geom::NODE_CUSP;
            if(path_it.closed()) {
                nodetype = Geom::get_nodetype(path_it.finalCurve(), *curve_it1);
            } 
            drawNode(curve_it1->initialPoint(), nodetype);
        }
        while (curve_it1 != curve_endit) {
            cubic = dynamic_cast<Geom::CubicBezier const *>(&*curve_it1);
            if (cubic) {
                if(handles) {
                    if(!are_near((*cubic)[0],(*cubic)[1])) {
                        drawHandle((*cubic)[1]);
                        drawHandleLine((*cubic)[0],(*cubic)[1]);
                    }
                    if(!are_near((*cubic)[3],(*cubic)[2])) {
                        drawHandle((*cubic)[2]);
                        drawHandleLine((*cubic)[3],(*cubic)[2]);
                    }
                }
            }
            if(nodes && (curve_it2 != curve_endit || !path_it.closed())) {
                Geom::NodeType nodetype = Geom::get_nodetype(*curve_it1, *curve_it2);
                drawNode(curve_it1->finalPoint(), nodetype);
            }
            ++curve_it1;
            if(curve_it2 != curve_endit) {
                ++curve_it2;
            }
        }
    }
}

void
LPEShowHandles::drawNode(Geom::Point p, Geom::NodeType nodetype)
{
    if(stroke_width * scale_nodes_and_handles > 0.0) {
        Geom::Rotate rotate = Geom::Rotate(0);
        if ( nodetype == Geom::NODE_CUSP) {
            rotate = Geom::Rotate::from_degrees(45);
        }
        double diameter = stroke_width * scale_nodes_and_handles;
        char const * svgd;
        if (show_center_node) {
            svgd = "M 0.05,0 A 0.05,0.05 0 0 1 0,0.05 0.05,0.05 0 0 1 -0.05,0 0.05,0.05 0 0 1 0,-0.05 0.05,0.05 0 0 1 0.05,0 Z M -0.5,-0.5 0.5,-0.5 0.5,0.5 -0.5,0.5 Z";
        } else {
            svgd = "M -0.5,-0.5 0.5,-0.5 0.5,0.5 -0.5,0.5 Z";
        }
        Geom::PathVector pathv = sp_svg_read_pathv(svgd);
        pathv *= rotate * Geom::Scale(diameter) * Geom::Translate(p);
        outline_path.push_back(pathv[0]);
        if (show_center_node) {
            outline_path.push_back(pathv[1]);
        }
    }
}

void
LPEShowHandles::drawHandle(Geom::Point p)
{
    if(stroke_width * scale_nodes_and_handles > 0.0) {
        double diameter = stroke_width * scale_nodes_and_handles;
        char const * svgd;
        svgd = "M 0.7,0.35 A 0.35,0.35 0 0 1 0.35,0.7 0.35,0.35 0 0 1 0,0.35 0.35,0.35 0 0 1 0.35,0 0.35,0.35 0 0 1 0.7,0.35 Z";
        Geom::PathVector pathv = sp_svg_read_pathv(svgd);
        pathv *= Geom::Scale (diameter) * Geom::Translate(p - Geom::Point(diameter * 0.35,diameter * 0.35));
        outline_path.push_back(pathv[0]);
    }
}


void
LPEShowHandles::drawHandleLine(Geom::Point p,Geom::Point p2)
{
    Geom::Path path;
    double diameter = stroke_width * scale_nodes_and_handles;
    if(diameter > 0.0 && Geom::distance(p,p2) > (diameter * 0.35)) {
        Geom::Ray ray2(p, p2);
        p2 =  p2 - Geom::Point::polar(ray2.angle(),(diameter * 0.35));
    }
    path.start( p );
    path.appendNew<Geom::LineSegment>( p2 );
    outline_path.push_back(path);
}

}; //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 :
