#define __SP_RECT_C__

/*
 * 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, read the file 'COPYING' for more information
 */

#ifdef HAVE_CONFIG_H
# include "config.h"
#endif


#include <display/curve.h>
#include <libnr/nr-matrix-div.h>
#include <libnr/nr-matrix-fns.h>

#include "attributes.h"
#include "style.h"
#include "sp-rect.h"
#include <glibmm/i18n.h>
#include "xml/repr.h"

#define noRECT_VERBOSE

static void sp_rect_class_init(SPRectClass *klass);
static void sp_rect_init(SPRect *rect);

static void sp_rect_build(SPObject *object, SPDocument *document, Inkscape::XML::Node *repr);
static void sp_rect_set(SPObject *object, unsigned key, gchar const *value);
static void sp_rect_update(SPObject *object, SPCtx *ctx, guint flags);
static Inkscape::XML::Node *sp_rect_write(SPObject *object, Inkscape::XML::Node *repr, guint flags);

static gchar *sp_rect_description(SPItem *item);
static NR::Matrix sp_rect_set_transform(SPItem *item, NR::Matrix const &xform);

static void sp_rect_set_shape(SPShape *shape);

static SPShapeClass *parent_class;

GType
sp_rect_get_type(void)
{
    static GType type = 0;

    if (!type) {
        GTypeInfo info = {
            sizeof(SPRectClass),
            NULL,   /* base_init */
            NULL,   /* base_finalize */
            (GClassInitFunc) sp_rect_class_init,
            NULL,   /* class_finalize */
            NULL,   /* class_data */
            sizeof(SPRect),
            16,     /* n_preallocs */
            (GInstanceInitFunc) sp_rect_init,
            NULL,   /* value_table */
        };
        type = g_type_register_static(SP_TYPE_SHAPE, "SPRect", &info, (GTypeFlags)0);
    }
    return type;
}

static void
sp_rect_class_init(SPRectClass *klass)
{
    SPObjectClass *sp_object_class = (SPObjectClass *) klass;
    SPItemClass *item_class = (SPItemClass *) klass;
    SPShapeClass *shape_class = (SPShapeClass *) klass;

    parent_class = (SPShapeClass *)g_type_class_ref(SP_TYPE_SHAPE);

    sp_object_class->build = sp_rect_build;
    sp_object_class->write = sp_rect_write;
    sp_object_class->set = sp_rect_set;
    sp_object_class->update = sp_rect_update;

    item_class->description = sp_rect_description;
    item_class->set_transform = sp_rect_set_transform;

    shape_class->set_shape = sp_rect_set_shape;
}

static void
sp_rect_init(SPRect *rect)
{
    /* Initializing to zero is automatic */
    /* sp_svg_length_unset(&rect->x, SP_SVG_UNIT_NONE, 0.0, 0.0); */
    /* sp_svg_length_unset(&rect->y, SP_SVG_UNIT_NONE, 0.0, 0.0); */
    /* sp_svg_length_unset(&rect->width, SP_SVG_UNIT_NONE, 0.0, 0.0); */
    /* sp_svg_length_unset(&rect->height, SP_SVG_UNIT_NONE, 0.0, 0.0); */
    /* sp_svg_length_unset(&rect->rx, SP_SVG_UNIT_NONE, 0.0, 0.0); */
    /* sp_svg_length_unset(&rect->ry, SP_SVG_UNIT_NONE, 0.0, 0.0); */
}

static void
sp_rect_build(SPObject *object, SPDocument *document, Inkscape::XML::Node *repr)
{
    SPRect *rect = SP_RECT(object);

    if (((SPObjectClass *) parent_class)->build)
        ((SPObjectClass *) parent_class)->build(object, document, repr);

    sp_object_read_attr(object, "x");
    sp_object_read_attr(object, "y");
    sp_object_read_attr(object, "width");
    sp_object_read_attr(object, "height");
    sp_object_read_attr(object, "rx");
    sp_object_read_attr(object, "ry");

    Inkscape::Version const version = sp_object_get_sodipodi_version(object);

    if ( version.major == 0 && version.minor == 29 ) {
        if (rect->rx._set && rect->ry._set) {
            /* 0.29 treated 0.0 radius as missing value */
            if ((rect->rx.value != 0.0) && (rect->ry.value == 0.0)) {
                repr->setAttribute("ry", NULL);
                sp_object_read_attr(object, "ry");
            } else if ((rect->ry.value != 0.0) && (rect->rx.value == 0.0)) {
                repr->setAttribute("rx", NULL);
                sp_object_read_attr(object, "rx");
            }
        }
    }
}

static void
sp_rect_set(SPObject *object, unsigned key, gchar const *value)
{
    SPRect *rect = SP_RECT(object);

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

    switch (key) {
        case SP_ATTR_X:
            rect->x.readOrUnset(value);
            object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
            break;
        case SP_ATTR_Y:
            rect->y.readOrUnset(value);
            object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
            break;
        case SP_ATTR_WIDTH:
            if (!rect->width.read(value) || rect->width.value < 0.0) {
                rect->width.unset();
            }
            object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
            break;
        case SP_ATTR_HEIGHT:
            if (!rect->height.read(value) || rect->height.value < 0.0) {
                rect->height.unset();
            }
            object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
            break;
        case SP_ATTR_RX:
            if (!rect->rx.read(value) || rect->rx.value < 0.0) {
                rect->rx.unset();
            }
            object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
            break;
        case SP_ATTR_RY:
            if (!rect->ry.read(value) || rect->ry.value < 0.0) {
                rect->ry.unset();
            }
            object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
            break;
        default:
            if (((SPObjectClass *) parent_class)->set)
                ((SPObjectClass *) parent_class)->set(object, key, value);
            break;
    }
}

static void
sp_rect_update(SPObject *object, SPCtx *ctx, guint flags)
{
    if (flags & (SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG | SP_OBJECT_VIEWPORT_MODIFIED_FLAG)) {
        SPRect *rect = (SPRect *) object;
        SPStyle *style = object->style;
        SPItemCtx const *ictx = (SPItemCtx const *) ctx;
        double const d = NR::expansion(ictx->i2vp);
        double const w = (ictx->vp.x1 - ictx->vp.x0) / d;
        double const h = (ictx->vp.y1 - ictx->vp.y0) / d;
        double const em = style->font_size.computed;
        double const ex = 0.5 * em;  // fixme: get x height from pango or libnrtype.
        rect->x.update(em, ex, w);
        rect->y.update(em, ex, h);
        rect->width.update(em, ex, w);
        rect->height.update(em, ex, h);
        rect->rx.update(em, ex, w);
        rect->ry.update(em, ex, h);
        sp_shape_set_shape((SPShape *) object);
        flags &= ~SP_OBJECT_USER_MODIFIED_FLAG_B; // since we change the description, it's not a "just translation" anymore
    }

    if (((SPObjectClass *) parent_class)->update)
        ((SPObjectClass *) parent_class)->update(object, ctx, flags);
}

static Inkscape::XML::Node *
sp_rect_write(SPObject *object, Inkscape::XML::Node *repr, guint flags)
{
    SPRect *rect = SP_RECT(object);

    if ((flags & SP_OBJECT_WRITE_BUILD) && !repr) {
        repr = sp_repr_new("svg:rect");
    }

    sp_repr_set_svg_double(repr, "width", rect->width.computed);
    sp_repr_set_svg_double(repr, "height", rect->height.computed);
    if (rect->rx._set) sp_repr_set_svg_double(repr, "rx", rect->rx.computed);
    if (rect->ry._set) sp_repr_set_svg_double(repr, "ry", rect->ry.computed);
    sp_repr_set_svg_double(repr, "x", rect->x.computed);
    sp_repr_set_svg_double(repr, "y", rect->y.computed);

    if (((SPObjectClass *) parent_class)->write)
        ((SPObjectClass *) parent_class)->write(object, repr, flags);

    return repr;
}

static gchar *
sp_rect_description(SPItem *item)
{
    g_return_val_if_fail(SP_IS_RECT(item), NULL);

    return g_strdup(_("<b>Rectangle</b>"));
}

#define C1 0.554

static void
sp_rect_set_shape(SPShape *shape)
{
    SPRect *rect = (SPRect *) shape;

    if ((rect->height.computed < 1e-18) || (rect->width.computed < 1e-18)) return;

    SPCurve *c = sp_curve_new();

    double const x = rect->x.computed;
    double const y = rect->y.computed;
    double const w = rect->width.computed;
    double const h = rect->height.computed;
    double const w2 = w / 2;
    double const h2 = h / 2;
    double const rx = std::min(( rect->rx._set
                                 ? rect->rx.computed
                                 : ( rect->ry._set
                                     ? rect->ry.computed
                                     : 0.0 ) ),
                               .5 * rect->width.computed);
    double const ry = std::min(( rect->ry._set
                                 ? rect->ry.computed
                                 : ( rect->rx._set
                                     ? rect->rx.computed
                                     : 0.0 ) ),
                               .5 * rect->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)) {
        sp_curve_moveto(c, x + rx, y);
        if (rx < w2) sp_curve_lineto(c, x + w - rx, y);
        sp_curve_curveto(c, x + w - rx * (1 - C1), y,     x + w, y + ry * (1 - C1),       x + w, y + ry);
        if (ry < h2) sp_curve_lineto(c, x + w, y + h - ry);
        sp_curve_curveto(c, x + w, y + h - ry * (1 - C1),     x + w - rx * (1 - C1), y + h,       x + w - rx, y + h);
        if (rx < w2) sp_curve_lineto(c, x + rx, y + h);
        sp_curve_curveto(c, x + rx * (1 - C1), y + h,     x, y + h - ry * (1 - C1),       x, y + h - ry);
        if (ry < h2) sp_curve_lineto(c, x, y + ry);
        sp_curve_curveto(c, x, y + ry * (1 - C1),     x + rx * (1 - C1), y,       x + rx, y);
    } else {
        sp_curve_moveto(c, x + 0.0, y + 0.0);
        sp_curve_lineto(c, x + w, y + 0.0);
        sp_curve_lineto(c, x + w, y + h);
        sp_curve_lineto(c, x + 0.0, y + h);
        sp_curve_lineto(c, x + 0.0, y + 0.0);
    }

    sp_curve_closepath_current(c);
    sp_shape_set_curve_insync(SP_SHAPE(rect), c, TRUE);
    sp_curve_unref(c);
}

/* fixme: Think (Lauris) */

void
sp_rect_position_set(SPRect *rect, gdouble x, gdouble y, gdouble width, gdouble height)
{
    g_return_if_fail(rect != NULL);
    g_return_if_fail(SP_IS_RECT(rect));

    rect->x.computed = x;
    rect->y.computed = y;
    rect->width.computed = width;
    rect->height.computed = height;

    SP_OBJECT(rect)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
}

void
sp_rect_set_rx(SPRect *rect, gboolean set, gdouble value)
{
    g_return_if_fail(rect != NULL);
    g_return_if_fail(SP_IS_RECT(rect));

    rect->rx._set = set;
    if (set) rect->rx.computed = value;

    SP_OBJECT(rect)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
}

void
sp_rect_set_ry(SPRect *rect, gboolean set, gdouble value)
{
    g_return_if_fail(rect != NULL);
    g_return_if_fail(SP_IS_RECT(rect));

    rect->ry._set = set;
    if (set) rect->ry.computed = value;

    SP_OBJECT(rect)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
}

/*
 * Initially we'll do:
 * Transform x, y, set x, y, clear translation
 */

/* fixme: Use preferred units somehow (Lauris) */
/* fixme: Alternately preserve whatever units there are (lauris) */

static NR::Matrix
sp_rect_set_transform(SPItem *item, NR::Matrix const &xform)
{
    SPRect *rect = SP_RECT(item);

    /* Calculate rect start in parent coords. */
    NR::Point pos( NR::Point(rect->x.computed, rect->y.computed) * xform );

    /* This function takes care of translation and scaling, we return whatever parts we can't
       handle. */
    NR::Matrix ret(NR::transform(xform));
    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;
    }

    /* fixme: Would be nice to preserve units here */
    rect->width = rect->width.computed * sw;
    rect->height = rect->height.computed * sh;
    if (rect->rx._set) {
        rect->rx = rect->rx.computed * sw;
    }
    if (rect->ry._set) {
        rect->ry = rect->ry.computed * sh;
    }

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

    sp_rect_set_shape(rect);

    // Adjust stroke width
    sp_item_adjust_stroke(item, sqrt(fabs(sw * sh)));

    // Adjust pattern fill
    sp_item_adjust_pattern(item, xform / ret);

    // Adjust gradient fill
    sp_item_adjust_gradient(item, xform / ret);

    item->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG);

    return ret;
}


/**
Returns the ratio in which the vector from p0 to p1 is stretched by transform
 */
static gdouble
vector_stretch(NR::Point p0, NR::Point p1, NR::Matrix xform)
{
    if (p0 == p1)
        return 0;
    return (NR::distance(p0 * xform, p1 * xform) / NR::distance(p0, p1));
}

void
sp_rect_set_visible_rx(SPRect *rect, gdouble rx)
{
    if (rx == 0) {
        rect->rx.computed = 0;
        rect->rx._set = false;
    } else {
        rect->rx.computed = rx / vector_stretch(
            NR::Point(rect->x.computed + 1, rect->y.computed),
            NR::Point(rect->x.computed, rect->y.computed),
            SP_ITEM(rect)->transform);
        rect->rx._set = true;
    }
    SP_OBJECT(rect)->updateRepr();
}

void
sp_rect_set_visible_ry(SPRect *rect, gdouble ry)
{
    if (ry == 0) {
        rect->ry.computed = 0;
        rect->ry._set = false;
    } else {
        rect->ry.computed = ry / vector_stretch(
            NR::Point(rect->x.computed, rect->y.computed + 1),
            NR::Point(rect->x.computed, rect->y.computed),
            SP_ITEM(rect)->transform);
        rect->ry._set = true;
    }
    SP_OBJECT(rect)->updateRepr();
}

gdouble
sp_rect_get_visible_rx(SPRect *rect)
{
    if (!rect->rx._set)
        return 0;
    return rect->rx.computed * vector_stretch(
        NR::Point(rect->x.computed + 1, rect->y.computed),
        NR::Point(rect->x.computed, rect->y.computed),
        SP_ITEM(rect)->transform);
}

gdouble
sp_rect_get_visible_ry(SPRect *rect)
{
    if (!rect->ry._set)
        return 0;
    return rect->ry.computed * vector_stretch(
        NR::Point(rect->x.computed, rect->y.computed + 1),
        NR::Point(rect->x.computed, rect->y.computed),
        SP_ITEM(rect)->transform);
}

void
sp_rect_compensate_rxry(SPRect *rect, NR::Matrix xform)
{
    if (rect->rx.computed == 0 && rect->ry.computed == 0)
        return; // nothing to compensate

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

    // apply previous transform if any
    c *= SP_ITEM(rect)->transform;
    cx *= SP_ITEM(rect)->transform;
    cy *= SP_ITEM(rect)->transform;

    // find out stretches that we need to compensate
    gdouble eX = vector_stretch(cx, c, xform);
    gdouble eY = vector_stretch(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 ((rect->rx._set && !rect->ry._set) || (rect->ry._set && !rect->rx._set)) {
        gdouble r = MAX(rect->rx.computed, rect->ry.computed);
        rect->rx.computed = r / eX;
        rect->ry.computed = r / eY;
    } else {
        rect->rx.computed = rect->rx.computed / eX;
        rect->ry.computed = rect->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=

    rect->rx._set = rect->ry._set = true;
}

void
sp_rect_set_visible_width(SPRect *rect, gdouble width)
{
    rect->width.computed = width / vector_stretch(
        NR::Point(rect->x.computed + 1, rect->y.computed),
        NR::Point(rect->x.computed, rect->y.computed),
        SP_ITEM(rect)->transform);
    rect->width._set = true;
    SP_OBJECT(rect)->updateRepr();
}

void
sp_rect_set_visible_height(SPRect *rect, gdouble height)
{
    rect->height.computed = height / vector_stretch(
        NR::Point(rect->x.computed, rect->y.computed + 1),
        NR::Point(rect->x.computed, rect->y.computed),
        SP_ITEM(rect)->transform);
    rect->height._set = true;
    SP_OBJECT(rect)->updateRepr();
}

gdouble
sp_rect_get_visible_width(SPRect *rect)
{
    if (!rect->width._set)
        return 0;
    return rect->width.computed * vector_stretch(
        NR::Point(rect->x.computed + 1, rect->y.computed),
        NR::Point(rect->x.computed, rect->y.computed),
        SP_ITEM(rect)->transform);
}

gdouble
sp_rect_get_visible_height(SPRect *rect)
{
    if (!rect->height._set)
        return 0;
    return rect->height.computed * vector_stretch(
        NR::Point(rect->x.computed, rect->y.computed + 1),
        NR::Point(rect->x.computed, rect->y.computed),
        SP_ITEM(rect)->transform);
}

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