#define __SP_ELLIPSE_C__

/*
 * SVG <ellipse> and related implementations
 *
 * Authors:
 *   Lauris Kaplinski <lauris@kaplinski.com>
 *   Mitsuru Oka
 *   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 "libnr/n-art-bpath.h"
#include "libnr/nr-path.h"
#include "libnr/nr-matrix-fns.h"
#include "svg/svg.h"
#include "svg/path-string.h"
#include "xml/repr.h"
#include "attributes.h"
#include "style.h"
#include "display/curve.h"
#include <glibmm/i18n.h>

#include "document.h"
#include "sp-ellipse.h"

#include "prefs-utils.h"

/* Common parent class */

#define noELLIPSE_VERBOSE

#ifndef M_PI
#define M_PI 3.14159265358979323846
#endif

#define SP_2PI (2 * M_PI)

#if 1
/* Hmmm... shouldn't this also qualify */
/* Whether it is faster or not, well, nobody knows */
#define sp_round(v,m) (((v) < 0.0) ? ((ceil((v) / (m) - 0.5)) * (m)) : ((floor((v) / (m) + 0.5)) * (m)))
#else
/* we do not use C99 round(3) function yet */
static double sp_round(double x, double y)
{
    double remain;

    g_assert(y > 0.0);

    /* return round(x/y) * y; */

    remain = fmod(x, y);

    if (remain >= 0.5*y)
        return x - remain + y;
    else
        return x - remain;
}
#endif

static void sp_genericellipse_class_init(SPGenericEllipseClass *klass);
static void sp_genericellipse_init(SPGenericEllipse *ellipse);

static void sp_genericellipse_update(SPObject *object, SPCtx *ctx, guint flags);

static void sp_genericellipse_snappoints(SPItem const *item, SnapPointsIter p);

static void sp_genericellipse_set_shape(SPShape *shape);
static void sp_genericellipse_update_patheffect (SPShape *shape, bool write);

static Inkscape::XML::Node *sp_genericellipse_write(SPObject *object, Inkscape::XML::Node *repr,
                                                    guint flags);

static gboolean sp_arc_set_elliptical_path_attribute(SPArc *arc, Inkscape::XML::Node *repr);

static SPShapeClass *ge_parent_class;

GType
sp_genericellipse_get_type(void)
{
    static GType type = 0;
    if (!type) {
        GTypeInfo info = {
            sizeof(SPGenericEllipseClass),
            NULL,   /* base_init */
            NULL,   /* base_finalize */
            (GClassInitFunc) sp_genericellipse_class_init,
            NULL,   /* class_finalize */
            NULL,   /* class_data */
            sizeof(SPGenericEllipse),
            16,   /* n_preallocs */
            (GInstanceInitFunc) sp_genericellipse_init,
            NULL,   /* value_table */
        };
        type = g_type_register_static(SP_TYPE_SHAPE, "SPGenericEllipse", &info, (GTypeFlags)0);
    }
    return type;
}

static void sp_genericellipse_class_init(SPGenericEllipseClass *klass)
{
    SPObjectClass *sp_object_class = (SPObjectClass *) klass;
    SPItemClass *item_class = (SPItemClass *) klass;
    SPShapeClass *shape_class = (SPShapeClass *) klass;

    ge_parent_class = (SPShapeClass*) g_type_class_ref(SP_TYPE_SHAPE);

    sp_object_class->update = sp_genericellipse_update;
    sp_object_class->write = sp_genericellipse_write;

    item_class->snappoints = sp_genericellipse_snappoints;

    shape_class->set_shape = sp_genericellipse_set_shape;
    shape_class->update_patheffect = sp_genericellipse_update_patheffect;
}

static void
sp_genericellipse_init(SPGenericEllipse *ellipse)
{
    ellipse->cx.unset();
    ellipse->cy.unset();
    ellipse->rx.unset();
    ellipse->ry.unset();

    ellipse->start = 0.0;
    ellipse->end = SP_2PI;
    ellipse->closed = TRUE;
}

static void
sp_genericellipse_update(SPObject *object, SPCtx *ctx, guint flags)
{
    if (flags & (SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG | SP_OBJECT_VIEWPORT_MODIFIED_FLAG)) {
        SPGenericEllipse *ellipse = (SPGenericEllipse *) object;
        SPStyle const *style = object->style;
        double const d = 1.0 / NR::expansion(((SPItemCtx const *) ctx)->i2vp);
        double const em = style->font_size.computed;
        double const ex = em * 0.5; // fixme: get from pango or libnrtype
        ellipse->cx.update(em, ex, d);
        ellipse->cy.update(em, ex, d);
        ellipse->rx.update(em, ex, d);
        ellipse->ry.update(em, ex, d);
        sp_shape_set_shape((SPShape *) object);
    }

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

static void
sp_genericellipse_update_patheffect(SPShape *shape, bool write)
{
    sp_genericellipse_set_shape(shape);

    if (write) {
        Inkscape::XML::Node *repr = SP_OBJECT_REPR(shape);
        if ( shape->curve != NULL ) {
            NArtBpath *abp = sp_curve_first_bpath(shape->curve);
            if (abp) {
                gchar *str = sp_svg_write_path(abp);
                repr->setAttribute("d", str);
                g_free(str);
            } else {
                repr->setAttribute("d", "");
            }
        } else {
            repr->setAttribute("d", NULL);
        }
    }

    ((SPObject *)shape)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
}


#define C1 0.552

/* fixme: Think (Lauris) */

static void sp_genericellipse_set_shape(SPShape *shape)
{
    double cx, cy, rx, ry, s, e;
    double x0, y0, x1, y1, x2, y2, x3, y3;
    double len;
    gint slice = FALSE;
    gint i;

    SPGenericEllipse *ellipse = (SPGenericEllipse *) shape;

    if ((ellipse->rx.computed < 1e-18) || (ellipse->ry.computed < 1e-18)) return;
    if (fabs(ellipse->end - ellipse->start) < 1e-9) return;

    sp_genericellipse_normalize(ellipse);

    cx = 0.0;
    cy = 0.0;
    rx = ellipse->rx.computed;
    ry = ellipse->ry.computed;

    // figure out if we have a slice, guarding against rounding errors
    len = fmod(ellipse->end - ellipse->start, SP_2PI);
    if (len < 0.0) len += SP_2PI;
    if (fabs(len) < 1e-8 || fabs(len - SP_2PI) < 1e-8) {
        slice = FALSE;
        ellipse->end = ellipse->start + SP_2PI;
    } else {
        slice = TRUE;
    }

    NR::Matrix aff = NR::Matrix(NR::scale(rx, ry));
    aff[4] = ellipse->cx.computed;
    aff[5] = ellipse->cy.computed;

    NArtBpath bpath[16];
    i = 0;
    if (ellipse->closed) {
        bpath[i].code = NR_MOVETO;
    } else {
        bpath[i].code = NR_MOVETO_OPEN;
    }
    bpath[i].x3 = cos(ellipse->start);
    bpath[i].y3 = sin(ellipse->start);
    i++;

    for (s = ellipse->start; s < ellipse->end; s += M_PI_2) {
        e = s + M_PI_2;
        if (e > ellipse->end)
            e = ellipse->end;
        len = C1 * (e - s) / M_PI_2;
        x0 = cos(s);
        y0 = sin(s);
        x1 = x0 + len * cos(s + M_PI_2);
        y1 = y0 + len * sin(s + M_PI_2);
        x3 = cos(e);
        y3 = sin(e);
        x2 = x3 + len * cos(e - M_PI_2);
        y2 = y3 + len * sin(e - M_PI_2);
#ifdef ELLIPSE_VERBOSE
        g_print("step %d s %f e %f coords %f %f %f %f %f %f\n",
                i, s, e, x1, y1, x2, y2, x3, y3);
#endif
        bpath[i].code = NR_CURVETO;
        bpath[i].x1 = x1;
        bpath[i].y1 = y1;
        bpath[i].x2 = x2;
        bpath[i].y2 = y2;
        bpath[i].x3 = x3;
        bpath[i].y3 = y3;
        i++;
    }

    if (slice && ellipse->closed) {
        bpath[i].code = NR_LINETO;
        bpath[i].x3 = 0.0;
        bpath[i].y3 = 0.0;
        i++;
        bpath[i].code = NR_LINETO;
        bpath[i].x3 = bpath[0].x3;
        bpath[i].y3 = bpath[0].y3;
        i++;
    } else if (ellipse->closed) {
        bpath[i-1].x3 = bpath[0].x3;
        bpath[i-1].y3 = bpath[0].y3;
    }

    bpath[i].code = NR_END;
    SPCurve *c = sp_curve_new_from_bpath(nr_artpath_affine(bpath, aff));
    g_assert(c != NULL);

    sp_shape_perform_path_effect(c, SP_SHAPE (ellipse));
    sp_shape_set_curve_insync((SPShape *) ellipse, c, TRUE);
    sp_curve_unref(c);
}

static void sp_genericellipse_snappoints(SPItem const *item, SnapPointsIter p)
{
    SPGenericEllipse const *ge = SP_GENERICELLIPSE(item);

    NR::Matrix const i2d = sp_item_i2d_affine(item);

    /* Add the centre */
    *p = NR::Point(ge->cx.computed, ge->cy.computed) * i2d;

    // TODO: add the ends of radii
}

void
sp_genericellipse_normalize(SPGenericEllipse *ellipse)
{
    ellipse->start = fmod(ellipse->start, SP_2PI);
    ellipse->end = fmod(ellipse->end, SP_2PI);

    if (ellipse->start < 0.0)
        ellipse->start += SP_2PI;
    double diff = ellipse->start - ellipse->end;
    if (diff >= 0.0)
        ellipse->end += diff - fmod(diff, SP_2PI) + SP_2PI;

    /* Now we keep: 0 <= start < end <= 2*PI */
}

static Inkscape::XML::Node *sp_genericellipse_write(SPObject *object, Inkscape::XML::Node *repr, guint flags)
{
    SPGenericEllipse *ellipse = SP_GENERICELLIPSE(object);

    if (flags & SP_OBJECT_WRITE_EXT) {
        if ((flags & SP_OBJECT_WRITE_BUILD) && !repr) {
            Inkscape::XML::Document *xml_doc = sp_document_repr_doc(SP_OBJECT_DOCUMENT(object));
            repr = xml_doc->createElement("svg:path");
        }

        sp_repr_set_svg_double(repr, "sodipodi:cx", ellipse->cx.computed);
        sp_repr_set_svg_double(repr, "sodipodi:cy", ellipse->cy.computed);
        sp_repr_set_svg_double(repr, "sodipodi:rx", ellipse->rx.computed);
        sp_repr_set_svg_double(repr, "sodipodi:ry", ellipse->ry.computed);

        if (SP_IS_ARC(ellipse))
            sp_arc_set_elliptical_path_attribute(SP_ARC(object), SP_OBJECT_REPR(object));
    }

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

    return repr;
}

/* SVG <ellipse> element */

static void sp_ellipse_class_init(SPEllipseClass *klass);
static void sp_ellipse_init(SPEllipse *ellipse);

static void sp_ellipse_build(SPObject *object, SPDocument *document, Inkscape::XML::Node *repr);
static Inkscape::XML::Node *sp_ellipse_write(SPObject *object, Inkscape::XML::Node *repr, guint flags);
static void sp_ellipse_set(SPObject *object, unsigned int key, gchar const *value);
static gchar *sp_ellipse_description(SPItem *item);

static SPGenericEllipseClass *ellipse_parent_class;

GType
sp_ellipse_get_type(void)
{
    static GType type = 0;
    if (!type) {
        GTypeInfo info = {
            sizeof(SPEllipseClass),
            NULL,   /* base_init */
            NULL,   /* base_finalize */
            (GClassInitFunc) sp_ellipse_class_init,
            NULL,   /* class_finalize */
            NULL,   /* class_data */
            sizeof(SPEllipse),
            16,   /* n_preallocs */
            (GInstanceInitFunc) sp_ellipse_init,
            NULL,   /* value_table */
        };
        type = g_type_register_static(SP_TYPE_GENERICELLIPSE, "SPEllipse", &info, (GTypeFlags)0);
    }
    return type;
}

static void sp_ellipse_class_init(SPEllipseClass *klass)
{
    SPObjectClass *sp_object_class = (SPObjectClass *) klass;
    SPItemClass *item_class = (SPItemClass *) klass;

    ellipse_parent_class = (SPGenericEllipseClass*) g_type_class_ref(SP_TYPE_GENERICELLIPSE);

    sp_object_class->build = sp_ellipse_build;
    sp_object_class->write = sp_ellipse_write;
    sp_object_class->set = sp_ellipse_set;

    item_class->description = sp_ellipse_description;
}

static void
sp_ellipse_init(SPEllipse */*ellipse*/)
{
    /* Nothing special */
}

static void
sp_ellipse_build(SPObject *object, SPDocument *document, Inkscape::XML::Node *repr)
{
    if (((SPObjectClass *) ellipse_parent_class)->build)
        (* ((SPObjectClass *) ellipse_parent_class)->build) (object, document, repr);

    sp_object_read_attr(object, "cx");
    sp_object_read_attr(object, "cy");
    sp_object_read_attr(object, "rx");
    sp_object_read_attr(object, "ry");
}

static Inkscape::XML::Node *
sp_ellipse_write(SPObject *object, Inkscape::XML::Node *repr, guint flags)
{
    SPGenericEllipse *ellipse;

    ellipse = SP_GENERICELLIPSE(object);

    if ((flags & SP_OBJECT_WRITE_BUILD) && !repr) {
        Inkscape::XML::Document *xml_doc = sp_document_repr_doc(SP_OBJECT_DOCUMENT(object));
        repr = xml_doc->createElement("svg:ellipse");
    }

    sp_repr_set_svg_double(repr, "cx", ellipse->cx.computed);
    sp_repr_set_svg_double(repr, "cy", ellipse->cy.computed);
    sp_repr_set_svg_double(repr, "rx", ellipse->rx.computed);
    sp_repr_set_svg_double(repr, "ry", ellipse->ry.computed);

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

    return repr;
}

static void
sp_ellipse_set(SPObject *object, unsigned int key, gchar const *value)
{
    SPGenericEllipse *ellipse;

    ellipse = SP_GENERICELLIPSE(object);

    switch (key) {
        case SP_ATTR_CX:
            ellipse->cx.readOrUnset(value);
            object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
            break;
        case SP_ATTR_CY:
            ellipse->cy.readOrUnset(value);
            object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
            break;
        case SP_ATTR_RX:
            if (!ellipse->rx.read(value) || (ellipse->rx.value <= 0.0)) {
                ellipse->rx.unset();
            }
            object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
            break;
        case SP_ATTR_RY:
            if (!ellipse->ry.read(value) || (ellipse->ry.value <= 0.0)) {
                ellipse->ry.unset();
            }
            object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
            break;
        default:
            if (((SPObjectClass *) ellipse_parent_class)->set)
                ((SPObjectClass *) ellipse_parent_class)->set(object, key, value);
            break;
    }
}

static gchar *sp_ellipse_description(SPItem */*item*/)
{
    return g_strdup(_("<b>Ellipse</b>"));
}


void
sp_ellipse_position_set(SPEllipse *ellipse, gdouble x, gdouble y, gdouble rx, gdouble ry)
{
    SPGenericEllipse *ge;

    g_return_if_fail(ellipse != NULL);
    g_return_if_fail(SP_IS_ELLIPSE(ellipse));

    ge = SP_GENERICELLIPSE(ellipse);

    ge->cx.computed = x;
    ge->cy.computed = y;
    ge->rx.computed = rx;
    ge->ry.computed = ry;

    ((SPObject *)ge)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
}

/* SVG <circle> element */

static void sp_circle_class_init(SPCircleClass *klass);
static void sp_circle_init(SPCircle *circle);

static void sp_circle_build(SPObject *object, SPDocument *document, Inkscape::XML::Node *repr);
static Inkscape::XML::Node *sp_circle_write(SPObject *object, Inkscape::XML::Node *repr, guint flags);
static void sp_circle_set(SPObject *object, unsigned int key, gchar const *value);
static gchar *sp_circle_description(SPItem *item);

static SPGenericEllipseClass *circle_parent_class;

GType
sp_circle_get_type(void)
{
    static GType type = 0;
    if (!type) {
        GTypeInfo info = {
            sizeof(SPCircleClass),
            NULL,   /* base_init */
            NULL,   /* base_finalize */
            (GClassInitFunc) sp_circle_class_init,
            NULL,   /* class_finalize */
            NULL,   /* class_data */
            sizeof(SPCircle),
            16,   /* n_preallocs */
            (GInstanceInitFunc) sp_circle_init,
            NULL,   /* value_table */
        };
        type = g_type_register_static(SP_TYPE_GENERICELLIPSE, "SPCircle", &info, (GTypeFlags)0);
    }
    return type;
}

static void
sp_circle_class_init(SPCircleClass *klass)
{
    SPObjectClass *sp_object_class = (SPObjectClass *) klass;
    SPItemClass *item_class = (SPItemClass *) klass;

    circle_parent_class = (SPGenericEllipseClass*) g_type_class_ref(SP_TYPE_GENERICELLIPSE);

    sp_object_class->build = sp_circle_build;
    sp_object_class->write = sp_circle_write;
    sp_object_class->set = sp_circle_set;

    item_class->description = sp_circle_description;
}

static void
sp_circle_init(SPCircle */*circle*/)
{
    /* Nothing special */
}

static void
sp_circle_build(SPObject *object, SPDocument *document, Inkscape::XML::Node *repr)
{
    if (((SPObjectClass *) circle_parent_class)->build)
        (* ((SPObjectClass *) circle_parent_class)->build)(object, document, repr);

    sp_object_read_attr(object, "cx");
    sp_object_read_attr(object, "cy");
    sp_object_read_attr(object, "r");
}

static Inkscape::XML::Node *
sp_circle_write(SPObject *object, Inkscape::XML::Node *repr, guint flags)
{
    SPGenericEllipse *ellipse;

    ellipse = SP_GENERICELLIPSE(object);

    if ((flags & SP_OBJECT_WRITE_BUILD) && !repr) {
        Inkscape::XML::Document *xml_doc = sp_document_repr_doc(SP_OBJECT_DOCUMENT(object));
        repr = xml_doc->createElement("svg:circle");
    }

    sp_repr_set_svg_double(repr, "cx", ellipse->cx.computed);
    sp_repr_set_svg_double(repr, "cy", ellipse->cy.computed);
    sp_repr_set_svg_double(repr, "r", ellipse->rx.computed);

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

    return repr;
}

static void
sp_circle_set(SPObject *object, unsigned int key, gchar const *value)
{
    SPGenericEllipse *ge;

    ge = SP_GENERICELLIPSE(object);

    switch (key) {
        case SP_ATTR_CX:
            ge->cx.readOrUnset(value);
            object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
            break;
        case SP_ATTR_CY:
            ge->cy.readOrUnset(value);
            object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
            break;
        case SP_ATTR_R:
            if (!ge->rx.read(value) || ge->rx.value <= 0.0) {
                ge->rx.unset();
            }
            ge->ry = ge->rx;
            object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
            break;
        default:
            if (((SPObjectClass *) circle_parent_class)->set)
                ((SPObjectClass *) circle_parent_class)->set(object, key, value);
            break;
    }
}

static gchar *sp_circle_description(SPItem */*item*/)
{
    return g_strdup(_("<b>Circle</b>"));
}

/* <path sodipodi:type="arc"> element */

static void sp_arc_class_init(SPArcClass *klass);
static void sp_arc_init(SPArc *arc);

static void sp_arc_build(SPObject *object, SPDocument *document, Inkscape::XML::Node *repr);
static Inkscape::XML::Node *sp_arc_write(SPObject *object, Inkscape::XML::Node *repr, guint flags);
static void sp_arc_set(SPObject *object, unsigned int key, gchar const *value);
static void sp_arc_modified(SPObject *object, guint flags);

static gchar *sp_arc_description(SPItem *item);

static SPGenericEllipseClass *arc_parent_class;

GType
sp_arc_get_type(void)
{
    static GType type = 0;
    if (!type) {
        GTypeInfo info = {
            sizeof(SPArcClass),
            NULL,   /* base_init */
            NULL,   /* base_finalize */
            (GClassInitFunc) sp_arc_class_init,
            NULL,   /* class_finalize */
            NULL,   /* class_data */
            sizeof(SPArc),
            16,   /* n_preallocs */
            (GInstanceInitFunc) sp_arc_init,
            NULL,   /* value_table */
        };
        type = g_type_register_static(SP_TYPE_GENERICELLIPSE, "SPArc", &info, (GTypeFlags)0);
    }
    return type;
}

static void
sp_arc_class_init(SPArcClass *klass)
{
    SPObjectClass *sp_object_class = (SPObjectClass *) klass;
    SPItemClass *item_class = (SPItemClass *) klass;

    arc_parent_class = (SPGenericEllipseClass*) g_type_class_ref(SP_TYPE_GENERICELLIPSE);

    sp_object_class->build = sp_arc_build;
    sp_object_class->write = sp_arc_write;
    sp_object_class->set = sp_arc_set;
    sp_object_class->modified = sp_arc_modified;

    item_class->description = sp_arc_description;
}

static void
sp_arc_init(SPArc */*arc*/)
{
    /* Nothing special */
}

static void
sp_arc_build(SPObject *object, SPDocument *document, Inkscape::XML::Node *repr)
{
    if (((SPObjectClass *) arc_parent_class)->build)
        (* ((SPObjectClass *) arc_parent_class)->build) (object, document, repr);

    Inkscape::Version version = sp_object_get_sodipodi_version(object);

    sp_object_read_attr(object, "sodipodi:cx");
    sp_object_read_attr(object, "sodipodi:cy");
    sp_object_read_attr(object, "sodipodi:rx");
    sp_object_read_attr(object, "sodipodi:ry");

    sp_object_read_attr(object, "sodipodi:start");
    sp_object_read_attr(object, "sodipodi:end");
    sp_object_read_attr(object, "sodipodi:open");
}

/*
 * sp_arc_set_elliptical_path_attribute:
 *
 * Convert center to endpoint parameterization and set it to repr.
 *
 * See SVG 1.0 Specification W3C Recommendation
 * ``F.6 Ellptical arc implementation notes'' for more detail.
 */
static gboolean
sp_arc_set_elliptical_path_attribute(SPArc *arc, Inkscape::XML::Node *repr)
{
    SPGenericEllipse *ge = SP_GENERICELLIPSE(arc);

    Inkscape::SVG::PathString str;

    NR::Point p1 = sp_arc_get_xy(arc, ge->start);
    NR::Point p2 = sp_arc_get_xy(arc, ge->end);
    double rx = ge->rx.computed;
    double ry = ge->ry.computed;

    str.moveTo(p1);

    double dt = fmod(ge->end - ge->start, SP_2PI);
    if (fabs(dt) < 1e-6) {
        NR::Point ph = sp_arc_get_xy(arc, (ge->start + ge->end) / 2.0);
        str.arcTo(rx, ry, 0, true, true, ph)
           .arcTo(rx, ry, 0, true, true, p2)
           .closePath();
    } else {
        bool fa = (fabs(dt) > M_PI);
        bool fs = (dt > 0);
        str.arcTo(rx, ry, 0, fa, fs, p2);
        if (ge->closed) {
            NR::Point center = NR::Point(ge->cx.computed, ge->cy.computed);
            str.lineTo(center).closePath();
        }
    }

    repr->setAttribute("d", str.c_str());
    return true;
}

static Inkscape::XML::Node *
sp_arc_write(SPObject *object, Inkscape::XML::Node *repr, guint flags)
{
    SPGenericEllipse *ge = SP_GENERICELLIPSE(object);
    SPArc *arc = SP_ARC(object);

    if ((flags & SP_OBJECT_WRITE_BUILD) && !repr) {
        Inkscape::XML::Document *xml_doc = sp_document_repr_doc(SP_OBJECT_DOCUMENT(object));
        repr = xml_doc->createElement("svg:path");
    }

    if (flags & SP_OBJECT_WRITE_EXT) {
        repr->setAttribute("sodipodi:type", "arc");
        sp_repr_set_svg_double(repr, "sodipodi:cx", ge->cx.computed);
        sp_repr_set_svg_double(repr, "sodipodi:cy", ge->cy.computed);
        sp_repr_set_svg_double(repr, "sodipodi:rx", ge->rx.computed);
        sp_repr_set_svg_double(repr, "sodipodi:ry", ge->ry.computed);

        // write start and end only if they are non-trivial; otherwise remove
        gdouble len = fmod(ge->end - ge->start, SP_2PI);
        if (len < 0.0) len += SP_2PI;
        if (!(fabs(len) < 1e-8 || fabs(len - SP_2PI) < 1e-8)) {
            sp_repr_set_svg_double(repr, "sodipodi:start", ge->start);
            sp_repr_set_svg_double(repr, "sodipodi:end", ge->end);
            repr->setAttribute("sodipodi:open", (!ge->closed) ? "true" : NULL);
        } else {
            repr->setAttribute("sodipodi:end", NULL);
            repr->setAttribute("sodipodi:start", NULL);
            repr->setAttribute("sodipodi:open", NULL);
        }
    }

    // write d=
    sp_arc_set_elliptical_path_attribute(arc, repr);

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

    return repr;
}

static void
sp_arc_set(SPObject *object, unsigned int key, gchar const *value)
{
    SPGenericEllipse *ge = SP_GENERICELLIPSE(object);

    switch (key) {
        case SP_ATTR_SODIPODI_CX:
            ge->cx.readOrUnset(value);
            object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
            break;
        case SP_ATTR_SODIPODI_CY:
            ge->cy.readOrUnset(value);
            object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
            break;
        case SP_ATTR_SODIPODI_RX:
            if (!ge->rx.read(value) || ge->rx.computed <= 0.0) {
                ge->rx.unset();
            }
            object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
            break;
        case SP_ATTR_SODIPODI_RY:
            if (!ge->ry.read(value) || ge->ry.computed <= 0.0) {
                ge->ry.unset();
            }
            object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
            break;
        case SP_ATTR_SODIPODI_START:
            if (value) {
                sp_svg_number_read_d(value, &ge->start);
            } else {
                ge->start = 0;
            }
            object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
            break;
        case SP_ATTR_SODIPODI_END:
            if (value) {
                sp_svg_number_read_d(value, &ge->end);
            } else {
                ge->end = 2 * M_PI;
            }
            object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
            break;
        case SP_ATTR_SODIPODI_OPEN:
            ge->closed = (!value);
            object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
            break;
        default:
            if (((SPObjectClass *) arc_parent_class)->set)
                ((SPObjectClass *) arc_parent_class)->set(object, key, value);
            break;
    }
}

static void
sp_arc_modified(SPObject *object, guint flags)
{
    if (flags & SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG | SP_OBJECT_VIEWPORT_MODIFIED_FLAG) {
        sp_shape_set_shape((SPShape *) object);
    }

    if (((SPObjectClass *) arc_parent_class)->modified)
        ((SPObjectClass *) arc_parent_class)->modified(object, flags);
}

static gchar *sp_arc_description(SPItem *item)
{
    SPGenericEllipse *ge = SP_GENERICELLIPSE(item);

    gdouble len = fmod(ge->end - ge->start, SP_2PI);
    if (len < 0.0) len += SP_2PI;
    if (!(fabs(len) < 1e-8 || fabs(len - SP_2PI) < 1e-8)) {
        if (ge->closed) {
            return g_strdup(_("<b>Segment</b>"));
        } else {
            return g_strdup(_("<b>Arc</b>"));
        }
    } else {
        return g_strdup(_("<b>Ellipse</b>"));
    }
}

void
sp_arc_position_set(SPArc *arc, gdouble x, gdouble y, gdouble rx, gdouble ry)
{
    g_return_if_fail(arc != NULL);
    g_return_if_fail(SP_IS_ARC(arc));

    SPGenericEllipse *ge = SP_GENERICELLIPSE(arc);

    ge->cx.computed = x;
    ge->cy.computed = y;
    ge->rx.computed = rx;
    ge->ry.computed = ry;
    if (prefs_get_double_attribute("tools.shapes.arc", "start", 0.0) != 0)
        ge->start = prefs_get_double_attribute("tools.shapes.arc", "start", 0.0);
    if (prefs_get_double_attribute("tools.shapes.arc", "end", 0.0) != 0)
        ge->end = prefs_get_double_attribute("tools.shapes.arc", "end", 0.0);
    if (!prefs_get_string_attribute("tools.shapes.arc", "open"))
        ge->closed = 1;
    else
        ge->closed = 0;

    ((SPObject *)arc)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
}

NR::Point sp_arc_get_xy(SPArc *arc, gdouble arg)
{
    SPGenericEllipse *ge = SP_GENERICELLIPSE(arc);

    return NR::Point(ge->rx.computed * cos(arg) + ge->cx.computed,
                     ge->ry.computed * sin(arg) + ge->cy.computed);
}


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