// SPDX-License-Identifier: GPL-2.0-or-later
/*
 * SVG data parser
 *
 * Authors:
 *   Lauris Kaplinski <lauris@kaplinski.com>
 *   Raph Levien <raph@acm.org>
 *
 * Copyright (C) 1999-2002 Lauris Kaplinski
 * Copyright (C) 1999 Raph Levien
 *
 * Released under GNU GPL v2+, read the file 'COPYING' for more information.
 */

#include <cstring>
#include <string>
#include <cstdlib>
#include <cstdio>
#include <glib.h>
#include <2geom/transforms.h>
#include "svg.h"
#include "preferences.h"

bool
sp_svg_transform_read(gchar const *str, Geom::Affine *transform)
{
    int idx;
    char keyword[32];
    double args[6];
    int n_args;
    size_t key_len;

    if (str == nullptr) return false;

    Geom::Affine a(Geom::identity());

    idx = 0;
    while (str[idx]) {
        /* skip initial whitespace */
        while (g_ascii_isspace (str[idx])) idx++;

        /* parse keyword */
        for (key_len = 0; key_len < sizeof (keyword); key_len++) {
            char c;

            c = str[idx];
            if (g_ascii_isalpha (c) || c == '-') {
                keyword[key_len] = str[idx++];
            } else {
                break;
            }
        }
        if (key_len >= sizeof (keyword)) return false;
        keyword[key_len] = '\0';

        /* skip whitespace */
        while (g_ascii_isspace (str[idx])) idx++;

        if (str[idx] != '(') return false;
        idx++;

        for (n_args = 0; n_args < 6; n_args++) {
            char c;
            char *end_ptr;

            /* skip whitespace */
            while (g_ascii_isspace (str[idx])) idx++;
            c = str[idx];
            if (g_ascii_isdigit (c) || c == '+' || c == '-' || c == '.') {
                if (n_args == sizeof (args) / sizeof (args[0])) return false; /* Too many args */
                args[n_args] = g_ascii_strtod (str + idx, &end_ptr);
                
                //printf("took %d chars from '%s' to make %f\n",
                //		end_ptr-(str+idx),
                //		str+idx,
                //		args[n_args]);

                idx = end_ptr - (char *) str;

                while (g_ascii_isspace (str[idx])) idx++;

                /* skip optional comma */
                if (str[idx] == ',') idx++;
            } else if (c == ')') {
                break;
            } else {
                return false;
            }
        }
        idx++;

        /* ok, have parsed keyword and args, now modify the transform */
        if (!strcmp (keyword, "matrix")) {
            if (n_args != 6) return false;
            a = (*((Geom::Affine *) &(args)[0])) * a;
        } else if (!strcmp (keyword, "translate")) {
            if (n_args == 1) {
                args[1] = 0;
            } else if (n_args != 2) {
                return false;
            }
            a = Geom::Translate(args[0], args[1]) * a;
        } else if (!strcmp (keyword, "scale")) {
            if (n_args == 1) {
                args[1] = args[0];
            } else if (n_args != 2) {
                return false;
            }
            a = Geom::Scale(args[0], args[1]) * a;
        } else if (!strcmp (keyword, "rotate")) {
            if (n_args != 1 && n_args != 3) {
                return false;
            }
            Geom::Rotate const rot(Geom::rad_from_deg(args[0]));
            if (n_args == 3) {
                a = ( Geom::Translate(-args[1], -args[2])
                      * rot
                      * Geom::Translate(args[1], args[2])
                      * Geom::Affine(a) );
            } else {
                a = rot * a;
            }
        } else if (!strcmp (keyword, "skewX")) {
            if (n_args != 1) return false;
            a = ( Geom::Affine(1, 0,
                     tan(args[0] * M_PI / 180.0), 1,
                     0, 0)
                  * a );
        } else if (!strcmp (keyword, "skewY")) {
            if (n_args != 1) return false;
            a = ( Geom::Affine(1, tan(args[0] * M_PI / 180.0),
                     0, 1,
                     0, 0)
                  * a );
        } else {
            return false; /* unknown keyword */
        }
        /* Skip trailing whitespace */
             while (g_ascii_isspace (str[idx])) idx++;
    }

    *transform = a;
    return true;
}

#define EQ(a,b) (fabs ((a) - (b)) < 1e-9)

gchar *
sp_svg_transform_write(Geom::Affine const &transform)
{
    Inkscape::Preferences *prefs = Inkscape::Preferences::get();

    double e = 0.000001 * transform.descrim();
    int prec = prefs->getInt("/options/svgoutput/numericprecision", 8);
    int min_exp = prefs->getInt("/options/svgoutput/minimumexponent", -8);

    // Special case: when all fields of the affine are zero,
    // the optimized transformation is scale(0)
    if (transform[0] == 0 && transform[1] == 0 && transform[2] == 0 &&
        transform[3] == 0 && transform[4] == 0 && transform[5] == 0)
    {
        return g_strdup("scale(0)");
    }

    // FIXME legacy C code!
    // the function sp_svg_number_write_de is stopping me from using a proper C++ string

    gchar c[256]; // string buffer
    unsigned p = 0; // position in the buffer

    if (transform.isIdentity()) {
        // We are more or less identity, so no transform attribute needed:
        return nullptr;
    } else if (transform.isScale()) {
        // We are more or less a uniform scale
        strcpy (c + p, "scale(");
        p += 6;
        p += sp_svg_number_write_de( c + p, sizeof(c) - p, transform[0], prec, min_exp );
        if (Geom::are_near(transform[0], transform[3], e)) {
            c[p++] = ')';
            c[p] = '\000';
        } else {
            c[p++] = ',';
            p += sp_svg_number_write_de( c + p, sizeof(c) - p, transform[3], prec, min_exp );
            c[p++] = ')';
            c[p] = '\000';
        }
    } else if (transform.isTranslation()) {
        // We are more or less a pure translation
        strcpy (c + p, "translate(");
        p += 10;
        p += sp_svg_number_write_de( c + p, sizeof(c) - p, transform[4], prec, min_exp );
        if (Geom::are_near(transform[5], 0.0, e)) {
            c[p++] = ')';
            c[p] = '\000';
        } else {
            c[p++] = ',';
            p += sp_svg_number_write_de( c + p, sizeof(c) - p, transform[5], prec, min_exp );
            c[p++] = ')';
            c[p] = '\000';
        }
    } else if (transform.isRotation()) {
        // We are more or less a pure rotation
        strcpy(c + p, "rotate(");
        p += 7;

        double angle = std::atan2(transform[1], transform[0]) * (180 / M_PI);
        p += sp_svg_number_write_de(c + p, sizeof(c) - p, angle, prec, min_exp);

        c[p++] = ')';
        c[p] = '\000';
    } else if (transform.withoutTranslation().isRotation()) {
        // Solution found by Johan Engelen
        // Refer to the matrix in svg-affine-test.h

        // We are a rotation about a special axis
        strcpy(c + p, "rotate(");
        p += 7;

        double angle = std::atan2(transform[1], transform[0]) * (180 / M_PI);
        p += sp_svg_number_write_de(c + p, sizeof(c) - p, angle, prec, min_exp);
        c[p++] = ',';

        Geom::Affine const& m = transform;
        double tx = (m[2]*m[5]+m[4]-m[4]*m[3]) / (1-m[3]-m[0]+m[0]*m[3]-m[2]*m[1]);
        p += sp_svg_number_write_de(c + p, sizeof(c) - p, tx, prec, min_exp);

        c[p++] = ',';

        double ty = (m[1]*tx + m[5]) / (1 - m[3]);
        p += sp_svg_number_write_de(c + p, sizeof(c) - p, ty, prec, min_exp);

        c[p++] = ')';
        c[p] = '\000';
    } else if (transform.isHShear()) {
        // We are more or less a pure skewX
        strcpy(c + p, "skewX(");
        p += 6;

        double angle = atan(transform[2]) * (180 / M_PI);
        p += sp_svg_number_write_de(c + p, sizeof(c) - p, angle, prec, min_exp);

        c[p++] = ')';
        c[p] = '\000';
    } else if (transform.isVShear()) {
        // We are more or less a pure skewY
        strcpy(c + p, "skewY(");
        p += 6;

        double angle = atan(transform[1]) * (180 / M_PI);
        p += sp_svg_number_write_de(c + p, sizeof(c) - p, angle, prec, min_exp);

        c[p++] = ')';
        c[p] = '\000';
    } else {
        strcpy (c + p, "matrix(");
        p += 7;
        p += sp_svg_number_write_de( c + p, sizeof(c) - p, transform[0], prec, min_exp );
        c[p++] = ',';
        p += sp_svg_number_write_de( c + p, sizeof(c) - p, transform[1], prec, min_exp );
        c[p++] = ',';
        p += sp_svg_number_write_de( c + p, sizeof(c) - p, transform[2], prec, min_exp );
        c[p++] = ',';
        p += sp_svg_number_write_de( c + p, sizeof(c) - p, transform[3], prec, min_exp );
        c[p++] = ',';
        p += sp_svg_number_write_de( c + p, sizeof(c) - p, transform[4], prec, min_exp );
        c[p++] = ',';
        p += sp_svg_number_write_de( c + p, sizeof(c) - p, transform[5], prec, min_exp );
        c[p++] = ')';
        c[p] = '\000';
    }

    assert(p <= sizeof(c));
    return g_strdup(c);
}


gchar *
sp_svg_transform_write(Geom::Affine const *transform)
{
    return sp_svg_transform_write(*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:fileencoding=utf-8:textwidth=99 :
