// SPDX-License-Identifier: GPL-2.0-or-later
/*
 * SVG <rect> implementation
 *
 * Authors:
 *   Lauris Kaplinski <lauris@kaplinski.com>
 *   bulia byak <buliabyak@users.sf.net>
 *
 * Copyright (C) 1999-2002 Lauris Kaplinski
 * Copyright (C) 2000-2001 Ximian, Inc.
 *
 * Released under GNU GPL v2+, read the file 'COPYING' for more information.
 */

#include "display/curve.h"

#include "inkscape.h"
#include "document.h"
#include "attributes.h"
#include "style.h"
#include "sp-rect.h"
#include "sp-guide.h"
#include "preferences.h"
#include "svg/svg.h"
#include "snap-candidate.h"
#include "snap-preferences.h"
#include <glibmm/i18n.h>

#define noRECT_VERBOSE

//#define OBJECT_TRACE

SPRect::SPRect() : SPShape()
    ,type(SP_GENERIC_RECT_UNDEFINED) 
{
}

SPRect::~SPRect() = default;

/*
* Ellipse and rects are the only SP object who's repr element tag name changes
* during it's lifetime. During undo and redo these changes can cause
* the SP object to become unstuck from the repr's true state.
*/
void SPRect::tag_name_changed(gchar const* oldname, gchar const* newname)
{
    const std::string typeString = newname;
    if (typeString == "svg:rect") {
        type = SP_GENERIC_RECT;
    } else if (typeString == "svg:path") {
        type = SP_GENERIC_PATH;
    }
}

void SPRect::build(SPDocument* doc, Inkscape::XML::Node* repr) {
#ifdef OBJECT_TRACE
    objectTrace( "SPRect::build" );
#endif

    SPShape::build(doc, repr);

    this->readAttr(SPAttr::X);
    this->readAttr(SPAttr::Y);
    this->readAttr(SPAttr::WIDTH);
    this->readAttr(SPAttr::HEIGHT);
    this->readAttr(SPAttr::RX);
    this->readAttr(SPAttr::RY);

#ifdef OBJECT_TRACE
    objectTrace( "SPRect::build", false );
#endif
}

void SPRect::set(SPAttr key, gchar const *value) {

#ifdef OBJECT_TRACE
    std::stringstream temp;
    temp << "SPRect::set: " << sp_attribute_name(key)  << " " << (value?value:"null");
    objectTrace( temp.str() );
#endif

    /* fixme: We need real error processing some time */

    // We must update the SVGLengths immediately or nodes may be misplaced after they are moved.
    double const w = viewport.width();
    double const h = viewport.height();
    double const em = style->font_size.computed;
    double const ex = em * 0.5;

    switch (key) {
        case SPAttr::X:
            this->x.readOrUnset(value);
            this->x.update( em, ex, w );
            this->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
            break;

        case SPAttr::Y:
            this->y.readOrUnset(value);
            this->y.update( em, ex, h );
            this->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
            break;

        case SPAttr::WIDTH:
            if (!this->width.read(value) || this->width.value < 0.0) {
            	this->width.unset();
            }
            this->width.update( em, ex, w );
            this->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
            break;

        case SPAttr::HEIGHT:
            if (!this->height.read(value) || this->height.value < 0.0) {
            	this->height.unset();
            }
            this->height.update( em, ex, h );
            this->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
            break;

        case SPAttr::RX:
            if (!this->rx.read(value) || this->rx.value <= 0.0) {
            	this->rx.unset();
            }
            this->rx.update( em, ex, w );
            this->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
            break;

        case SPAttr::RY:
            if (!this->ry.read(value) || this->ry.value <= 0.0) {
            	this->ry.unset();
            }
            this->ry.update( em, ex, h );
            this->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
            break;

        default:
            SPShape::set(key, value);
            break;
    }
#ifdef OBJECT_TRACE
    objectTrace( "SPRect::set", false );
#endif
}

void SPRect::update(SPCtx* ctx, unsigned int flags) {

#ifdef OBJECT_TRACE
    objectTrace( "SPRect::update", true, flags );
#endif

    if (flags & (SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG | SP_OBJECT_VIEWPORT_MODIFIED_FLAG)) {
        SPItemCtx const *ictx = reinterpret_cast<SPItemCtx const *>(ctx);

        double const w = ictx->viewport.width();
        double const h = ictx->viewport.height();
        double const em = style->font_size.computed;
        double const ex = 0.5 * em;  // fixme: get x height from pango or libnrtype.

        this->x.update(em, ex, w);
        this->y.update(em, ex, h);
        this->width.update(em, ex, w);
        this->height.update(em, ex, h);
        this->rx.update(em, ex, w);
        this->ry.update(em, ex, h);
        this->set_shape();

        flags &= ~SP_OBJECT_USER_MODIFIED_FLAG_B; // since we change the description, it's not a "just translation" anymore
    }

    SPShape::update(ctx, flags);
#ifdef OBJECT_TRACE
    objectTrace( "SPRect::update", false, flags );
#endif
}

Inkscape::XML::Node * SPRect::write(Inkscape::XML::Document *xml_doc, Inkscape::XML::Node *repr, guint flags) {

#ifdef OBJECT_TRACE
    objectTrace( "SPRect::write", true, flags );
#endif
    GenericRectType new_type = SP_GENERIC_RECT;
    if (hasPathEffectOnClipOrMaskRecursive(this)) {
        new_type = SP_GENERIC_PATH;
    }
    if ((flags & SP_OBJECT_WRITE_BUILD) && !repr) {

        switch ( new_type ) {

            case SP_GENERIC_RECT:
                repr = xml_doc->createElement("svg:rect");
                break;
            case SP_GENERIC_PATH:
                repr = xml_doc->createElement("svg:path");
                break;
            default:
                std::cerr << "SPGenericRect::write(): unknown type." << std::endl;
        }
    }
    if (type != new_type) {
        switch (new_type) {
            case SP_GENERIC_RECT:
                repr->setCodeUnsafe(g_quark_from_string("svg:rect"));
                break;
            case SP_GENERIC_PATH:
                repr->setCodeUnsafe(g_quark_from_string("svg:path"));
                repr->setAttribute("sodipodi:type", "rect");
                break;
            default:
                std::cerr << "SPGenericRect::write(): unknown type." << std::endl;
        }
        type = new_type;
    }
    repr->setAttributeSvgLength("width", this->width);
    repr->setAttributeSvgLength("height", this->height);

    if (this->rx._set) {
    	repr->setAttributeSvgLength("rx", this->rx);
    }

    if (this->ry._set) {
    	repr->setAttributeSvgLength("ry", this->ry);
    }

    repr->setAttributeSvgLength("x", this->x);
    repr->setAttributeSvgLength("y", this->y);
    // write d=
    if (type == SP_GENERIC_PATH) {
        set_rect_path_attribute(repr); // include set_shape()
    } else {
        this->set_shape(); // evaluate SPCurve
    }
    SPShape::write(xml_doc, repr, flags);

#ifdef OBJECT_TRACE
    objectTrace( "SPRect::write", false, flags );
#endif

    return repr;
}

const char* SPRect::typeName() const {
    return "rect";
}

const char* SPRect::displayName() const {
    return _("Rectangle");
}

#define C1 0.554

void SPRect::set_shape() {
    if (checkBrokenPathEffect()) {
        return;
    }
    if ((this->height.computed < 1e-18) || (this->width.computed < 1e-18)) {
    	this->setCurveInsync(nullptr);
    	this->setCurveBeforeLPE(nullptr);
        return;
    }

    SPCurve c;

    double const x = this->x.computed;
    double const y = this->y.computed;
    double const w = this->width.computed;
    double const h = this->height.computed;
    double const w2 = w / 2;
    double const h2 = h / 2;
    double const rx = std::min(( this->rx._set
                                 ? this->rx.computed
                                 : ( this->ry._set
                                     ? this->ry.computed
                                     : 0.0 ) ),
                               .5 * this->width.computed);
    double const ry = std::min(( this->ry._set
                                 ? this->ry.computed
                                 : ( this->rx._set
                                     ? this->rx.computed
                                     : 0.0 ) ),
                               .5 * this->height.computed);
    /* TODO: Handle negative rx or ry as per
     * http://www.w3.org/TR/SVG11/shapes.html#RectElementRXAttribute once Inkscape has proper error
     * handling (see http://www.w3.org/TR/SVG11/implnote.html#ErrorProcessing).
     */

    /* We don't use proper circular/elliptical arcs, but bezier curves can approximate a 90-degree
     * arc fairly well.
     */
    if ((rx > 1e-18) && (ry > 1e-18)) {
        c.moveto(x + rx, y);

        if (rx < w2) {
            c.lineto(x + w - rx, y);
        }

        c.curveto(x + w - rx * (1 - C1), y, x + w, y + ry * (1 - C1), x + w, y + ry);

        if (ry < h2) {
            c.lineto(x + w, y + h - ry);
        }

        c.curveto(x + w, y + h - ry * (1 - C1), x + w - rx * (1 - C1), y + h, x + w - rx, y + h);

        if (rx < w2) {
            c.lineto(x + rx, y + h);
        }

        c.curveto(x + rx * (1 - C1), y + h, x, y + h - ry * (1 - C1), x, y + h - ry);

        if (ry < h2) {
            c.lineto(x, y + ry);
        }

        c.curveto(x, y + ry * (1 - C1), x + rx * (1 - C1), y, x + rx, y);
    } else {
        c.moveto(x + 0.0, y + 0.0);
        c.lineto(x + w, y + 0.0);
        c.lineto(x + w, y + h);
        c.lineto(x + 0.0, y + h);
    }

    c.closepath();

    prepareShapeForLPE(&c);
}

bool SPRect::set_rect_path_attribute(Inkscape::XML::Node *repr)
{
    // Make sure our pathvector is up to date.
    this->set_shape();

    if (_curve) {
        repr->setAttribute("d", sp_svg_write_path(_curve->get_pathvector()));
    } else {
        repr->removeAttribute("d");
    }

    return true;
}

void SPRect::modified(guint flags)
{
    if (flags & (SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG | SP_OBJECT_VIEWPORT_MODIFIED_FLAG)) {
        this->set_shape();
    }

    SPShape::modified(flags);
}

/* fixme: Think (Lauris) */

void SPRect::setPosition(gdouble x, gdouble y, gdouble width, gdouble height) {
    this->x = x;
    this->y = y;
    this->width = width;
    this->height = height;

    this->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
}

void SPRect::setRx(bool set, gdouble value) {
    this->rx._set = set;

    if (set) {
    	this->rx = value;
    }

    this->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
}

void SPRect::setRy(bool set, gdouble value) {
    this->ry._set = set;

    if (set) {
    	this->ry = value;
    }

    this->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
}

void SPRect::update_patheffect(bool write) {
    if (type != SP_GENERIC_PATH && hasPathEffectOnClipOrMaskRecursive(this)) {
        SPRect::write(document->getReprDoc(), getRepr(), SP_OBJECT_MODIFIED_FLAG);
    }
    SPShape::update_patheffect(write);
}

Geom::Affine SPRect::set_transform(Geom::Affine const& xform) {
    if (pathEffectsEnabled() && !optimizeTransforms()) {
        return xform;
    }
    /* Calculate rect start in parent coords. */
    Geom::Point pos(Geom::Point(this->x.computed, this->y.computed) * xform);

    /* This function takes care of translation and scaling, we return whatever parts we can't
       handle. */
    Geom::Affine ret(Geom::Affine(xform).withoutTranslation());
    gdouble const sw = hypot(ret[0], ret[1]);
    gdouble const sh = hypot(ret[2], ret[3]);

    if (sw > 1e-9) {
        ret[0] /= sw;
        ret[1] /= sw;
    } else {
        ret[0] = 1.0;
        ret[1] = 0.0;
    }

    if (sh > 1e-9) {
        ret[2] /= sh;
        ret[3] /= sh;
    } else {
        ret[2] = 0.0;
        ret[3] = 1.0;
    }

    /* Preserve units */
    this->width.scale( sw );
    this->height.scale( sh );

    if (this->rx._set) {
    	this->rx.scale( sw );
    }

    if (this->ry._set) {
    	this->ry.scale( sh );
    }

    /* Find start in item coords */
    pos = pos * ret.inverse();
    this->x = pos[Geom::X];
    this->y = pos[Geom::Y];

    this->set_shape();

    // Adjust stroke width
    this->adjust_stroke(sqrt(fabs(sw * sh)));

    // Adjust pattern fill
    this->adjust_pattern(xform * ret.inverse());

    // Adjust gradient fill
    this->adjust_gradient(xform * ret.inverse());

    return ret;
}


/**
Returns the ratio in which the vector from p0 to p1 is stretched by transform
 */
gdouble SPRect::vectorStretch(Geom::Point p0, Geom::Point p1, Geom::Affine xform) {
    if (p0 == p1) {
        return 0;
    }

    return (Geom::distance(p0 * xform, p1 * xform) / Geom::distance(p0, p1));
}

void SPRect::setVisibleRx(gdouble rx) {
    if (rx == 0) {
        this->rx.unset();
    } else {
    	this->rx = rx / SPRect::vectorStretch(
            Geom::Point(this->x.computed + 1, this->y.computed),
            Geom::Point(this->x.computed, this->y.computed),
            this->i2doc_affine());
    }

    this->updateRepr();
}

void SPRect::setVisibleRy(gdouble ry) {
    if (ry == 0) {
    	this->ry.unset();
    } else {
    	this->ry = ry / SPRect::vectorStretch(
            Geom::Point(this->x.computed, this->y.computed + 1),
            Geom::Point(this->x.computed, this->y.computed),
            this->i2doc_affine());
    }

    this->updateRepr();
}

gdouble SPRect::getVisibleRx() const {
    if (!this->rx._set) {
        return 0;
    }

    return this->rx.computed * SPRect::vectorStretch(
        Geom::Point(this->x.computed + 1, this->y.computed),
        Geom::Point(this->x.computed, this->y.computed),
        this->i2doc_affine());
}

gdouble SPRect::getVisibleRy() const {
    if (!this->ry._set) {
        return 0;
    }

    return this->ry.computed * SPRect::vectorStretch(
        Geom::Point(this->x.computed, this->y.computed + 1),
        Geom::Point(this->x.computed, this->y.computed),
        this->i2doc_affine());
}

Geom::Rect SPRect::getRect() const {
    Geom::Point p0 = Geom::Point(this->x.computed, this->y.computed);
    Geom::Point p2 = Geom::Point(this->x.computed + this->width.computed, this->y.computed + this->height.computed);

    return Geom::Rect(p0, p2);
}

void SPRect::compensateRxRy(Geom::Affine xform) {
    if (this->rx.computed == 0 && this->ry.computed == 0) {
        return; // nothing to compensate
    }

    // test unit vectors to find out compensation:
    Geom::Point c(this->x.computed, this->y.computed);
    Geom::Point cx = c + Geom::Point(1, 0);
    Geom::Point cy = c + Geom::Point(0, 1);

    // apply previous transform if any
    c *= this->transform;
    cx *= this->transform;
    cy *= this->transform;

    // find out stretches that we need to compensate
    gdouble eX = SPRect::vectorStretch(cx, c, xform);
    gdouble eY = SPRect::vectorStretch(cy, c, xform);

    // If only one of the radii is set, set both radii so they have the same visible length
    // This is needed because if we just set them the same length in SVG, they might end up unequal because of transform
    if ((this->rx._set && !this->ry._set) || (this->ry._set && !this->rx._set)) {
        gdouble r = MAX(this->rx.computed, this->ry.computed);
        this->rx = r / eX;
        this->ry = r / eY;
    } else {
    	this->rx = this->rx.computed / eX;
    	this->ry = this->ry.computed / eY;
    }

    // Note that a radius may end up larger than half-side if the rect is scaled down;
    // that's ok because this preserves the intended radii in case the rect is enlarged again,
    // and set_shape will take care of trimming too large radii when generating d=
}

void SPRect::setVisibleWidth(gdouble width) {
    this->width = width / SPRect::vectorStretch(
        Geom::Point(this->x.computed + 1, this->y.computed),
        Geom::Point(this->x.computed, this->y.computed),
        this->i2doc_affine());

    this->updateRepr();
}

void SPRect::setVisibleHeight(gdouble height) {
    this->height = height / SPRect::vectorStretch(
        Geom::Point(this->x.computed, this->y.computed + 1),
        Geom::Point(this->x.computed, this->y.computed),
        this->i2doc_affine());

	this->updateRepr();
}

gdouble SPRect::getVisibleWidth() const {
    if (!this->width._set) {
        return 0;
    }

    return this->width.computed * SPRect::vectorStretch(
        Geom::Point(this->x.computed + 1, this->y.computed),
        Geom::Point(this->x.computed, this->y.computed),
        this->i2doc_affine());
}

gdouble SPRect::getVisibleHeight() const {
    if (!this->height._set) {
        return 0;
    }

    return this->height.computed * SPRect::vectorStretch(
        Geom::Point(this->x.computed, this->y.computed + 1),
        Geom::Point(this->x.computed, this->y.computed),
        this->i2doc_affine());
}

void SPRect::snappoints(std::vector<Inkscape::SnapCandidatePoint> &p, Inkscape::SnapPreferences const *snapprefs) const {
    /* This method overrides sp_shape_snappoints, which is the default for any shape. The default method
    returns all eight points along the path of a rounded rectangle, but not the real corners. Snapping
    the startpoint and endpoint of each rounded corner is not very useful and really confusing. Instead
    we could snap either the real corners, or not snap at all. Bulia Byak opted to snap the real corners,
    but it should be noted that this might be confusing in some cases with relatively large radii. With
    small radii though the user will easily understand which point is snapping. */

    Geom::Affine const i2dt (this->i2dt_affine ());

    Geom::Point p0 = Geom::Point(this->x.computed, this->y.computed) * i2dt;
    Geom::Point p1 = Geom::Point(this->x.computed, this->y.computed + this->height.computed) * i2dt;
    Geom::Point p2 = Geom::Point(this->x.computed + this->width.computed, this->y.computed + this->height.computed) * i2dt;
    Geom::Point p3 = Geom::Point(this->x.computed + this->width.computed, this->y.computed) * i2dt;

    if (snapprefs->isTargetSnappable(Inkscape::SNAPTARGET_RECT_CORNER)) {
        p.emplace_back(p0, Inkscape::SNAPSOURCE_RECT_CORNER, Inkscape::SNAPTARGET_RECT_CORNER);
        p.emplace_back(p1, Inkscape::SNAPSOURCE_RECT_CORNER, Inkscape::SNAPTARGET_RECT_CORNER);
        p.emplace_back(p2, Inkscape::SNAPSOURCE_RECT_CORNER, Inkscape::SNAPTARGET_RECT_CORNER);
        p.emplace_back(p3, Inkscape::SNAPSOURCE_RECT_CORNER, Inkscape::SNAPTARGET_RECT_CORNER);
    }

    if (snapprefs->isTargetSnappable(Inkscape::SNAPTARGET_LINE_MIDPOINT)) {
        p.emplace_back((p0 + p1)/2, Inkscape::SNAPSOURCE_LINE_MIDPOINT, Inkscape::SNAPTARGET_LINE_MIDPOINT);
        p.emplace_back((p1 + p2)/2, Inkscape::SNAPSOURCE_LINE_MIDPOINT, Inkscape::SNAPTARGET_LINE_MIDPOINT);
        p.emplace_back((p2 + p3)/2, Inkscape::SNAPSOURCE_LINE_MIDPOINT, Inkscape::SNAPTARGET_LINE_MIDPOINT);
        p.emplace_back((p3 + p0)/2, Inkscape::SNAPSOURCE_LINE_MIDPOINT, Inkscape::SNAPTARGET_LINE_MIDPOINT);
    }

    if (snapprefs->isTargetSnappable(Inkscape::SNAPTARGET_OBJECT_MIDPOINT)) {
        p.emplace_back((p0 + p2)/2, Inkscape::SNAPSOURCE_OBJECT_MIDPOINT, Inkscape::SNAPTARGET_OBJECT_MIDPOINT);
    }
}

void SPRect::convert_to_guides() const {
    Inkscape::Preferences *prefs = Inkscape::Preferences::get();

    if (!prefs->getBool("/tools/shapes/rect/convertguides", true)) {
    	// Use bounding box instead of edges
    	SPShape::convert_to_guides();
        return;
    }

    std::list<std::pair<Geom::Point, Geom::Point> > pts;

    Geom::Affine const i2dt(this->i2dt_affine());

    Geom::Point A1(Geom::Point(this->x.computed, this->y.computed) * i2dt);
    Geom::Point A2(Geom::Point(this->x.computed, this->y.computed + this->height.computed) * i2dt);
    Geom::Point A3(Geom::Point(this->x.computed + this->width.computed, this->y.computed + this->height.computed) * i2dt);
    Geom::Point A4(Geom::Point(this->x.computed + this->width.computed, this->y.computed) * i2dt);

    pts.emplace_back(A1, A2);
    pts.emplace_back(A2, A3);
    pts.emplace_back(A3, A4);
    pts.emplace_back(A4, A1);

    sp_guide_pt_pairs_to_guides(this->document, pts);
}

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