// SPDX-FileCopyrightText: 2002 Dominique Devriese <devriese@kde.org>

// SPDX-License-Identifier: GPL-2.0-or-later

#include "object_imp_factory.h"

#include "bogus_imp.h"
#include "circle_imp.h"
#include "conic_imp.h"
#include "cubic_imp.h"
#include "line_imp.h"
#include "locus_imp.h"
#include "object_imp.h"
#include "other_imp.h"
#include "point_imp.h"
#include "text_imp.h"

#include "../misc/coordinate.h"

#include <qdom.h>

const ObjectImpFactory *ObjectImpFactory::instance()
{
    static const ObjectImpFactory t;
    return &t;
}

ObjectImpFactory::ObjectImpFactory()
{
}

ObjectImpFactory::~ObjectImpFactory()
{
}

static void addXYElements(const Coordinate &c, QDomElement &parent, QDomDocument &doc)
{
    QDomElement xe = doc.createElement(QStringLiteral("x"));
    xe.appendChild(doc.createTextNode(QString::number(c.x)));
    parent.appendChild(xe);
    QDomElement ye = doc.createElement(QStringLiteral("y"));
    ye.appendChild(doc.createTextNode(QString::number(c.y)));
    parent.appendChild(ye);
}

static void addDoubleElement(const char *name, double d, QDomElement &parent, QDomDocument &doc)
{
    QDomElement e = doc.createElement(name);
    e.appendChild(doc.createTextNode(QString::number(d)));
    parent.appendChild(e);
}

static void addCoordinateElement(const char *name, const Coordinate &d, QDomElement &p, QDomDocument &doc)
{
    QDomElement e = doc.createElement(name);
    addXYElements(d, e, doc);
    p.appendChild(e);
}

QString ObjectImpFactory::serialize(const ObjectImp &d, QDomElement &parent, QDomDocument &doc) const
{
    if (d.inherits(IntImp::stype())) {
        parent.appendChild(doc.createTextNode(QString::number(static_cast<const IntImp &>(d).data())));
        return QStringLiteral("int");
    } else if (d.inherits(DoubleImp::stype())) {
        parent.appendChild(doc.createTextNode(QString::number(static_cast<const DoubleImp &>(d).data())));
        return QStringLiteral("double");
    } else if (d.inherits(StringImp::stype())) {
        parent.appendChild(doc.createTextNode(static_cast<const StringImp &>(d).data()));
        return QStringLiteral("string");
    } else if (d.inherits(TestResultImp::stype())) {
        assert(false);
        parent.appendChild(doc.createTextNode(static_cast<const TestResultImp &>(d).data()));
        return QStringLiteral("testresult");
    } else if (d.inherits(HierarchyImp::stype())) {
        static_cast<const HierarchyImp &>(d).data().serialize(parent, doc);
        return QStringLiteral("hierarchy");
    } else if (d.inherits(TransformationImp::stype())) {
        const Transformation &trans = static_cast<const TransformationImp &>(d).data();

        QDomElement matrixe = doc.createElement(QStringLiteral("matrix"));
        for (int i = 0; i < 3; ++i) {
            for (int j = 0; j < 3; ++j) {
                QDomElement elel = doc.createElement(QStringLiteral("element"));
                elel.setAttribute(QStringLiteral("row"), QString::number(i));
                elel.setAttribute(QStringLiteral("column"), QString::number(j));
                elel.appendChild(doc.createTextNode(QString::number(trans.data(i, j))));
                matrixe.appendChild(elel);
            };
        }
        parent.appendChild(matrixe);

        QDomElement homothetye = doc.createElement(QStringLiteral("homothetic"));
        const char *ishomothety = trans.isHomothetic() ? "true" : "false";
        homothetye.appendChild(doc.createTextNode(ishomothety));
        parent.appendChild(homothetye);

        return QStringLiteral("transformation");
    } else if (d.inherits(AbstractLineImp::stype())) {
        LineData l = static_cast<const AbstractLineImp &>(d).data();
        addCoordinateElement("a", l.a, parent, doc);
        addCoordinateElement("b", l.b, parent, doc);
        if (d.inherits(SegmentImp::stype()))
            return QStringLiteral("segment");
        else if (d.inherits(RayImp::stype()))
            return QStringLiteral("ray");
        else
            return QStringLiteral("line");
    } else if (d.inherits(PointImp::stype())) {
        addXYElements(static_cast<const PointImp &>(d).coordinate(), parent, doc);
        return QStringLiteral("point");
    } else if (d.inherits(TextImp::stype())) {
        QString text = static_cast<const TextImp &>(d).text();
        parent.appendChild(doc.createTextNode(text));
        return QStringLiteral("text");
    } else if (d.inherits(AngleImp::stype())) {
        addDoubleElement("size", static_cast<const AngleImp &>(d).size(), parent, doc);
        return QStringLiteral("angle");
    } else if (d.inherits(ArcImp::stype())) {
        const ArcImp &a = static_cast<const ArcImp &>(d);
        addCoordinateElement("center", a.center(), parent, doc);
        addDoubleElement("radius", a.radius(), parent, doc);
        addDoubleElement("startangle", a.startAngle(), parent, doc);
        addDoubleElement("angle", a.angle(), parent, doc);
        return QStringLiteral("arc");
    } else if (d.inherits(VectorImp::stype())) {
        Coordinate dir = static_cast<const VectorImp &>(d).dir();
        addXYElements(dir, parent, doc);
        return QStringLiteral("vector");
    } else if (d.inherits(LocusImp::stype())) {
        const LocusImp &locus = static_cast<const LocusImp &>(d);

        // serialize the curve..
        QDomElement curve = doc.createElement(QStringLiteral("curve"));
        const CurveImp &curveimp = *locus.curve();
        QString type = serialize(curveimp, curve, doc);
        curve.setAttribute(QStringLiteral("type"), type);
        parent.appendChild(curve);

        // serialize the hierarchy..
        QDomElement hier = doc.createElement(QStringLiteral("calculation"));
        locus.hierarchy().serialize(hier, doc);
        parent.appendChild(hier);

        return QStringLiteral("locus");
    } else if (d.inherits(CircleImp::stype())) {
        const CircleImp &c = static_cast<const CircleImp &>(d);
        addCoordinateElement("center", c.center(), parent, doc);
        addDoubleElement("radius", c.radius(), parent, doc);
        return QStringLiteral("circle");
    } else if (d.inherits(ConicImp::stype())) {
        const ConicPolarData data = static_cast<const ConicImp &>(d).polarData();
        addCoordinateElement("focus1", data.focus1, parent, doc);
        addDoubleElement("pdimen", data.pdimen, parent, doc);
        addDoubleElement("ecostheta0", data.ecostheta0, parent, doc);
        addDoubleElement("esintheta0", data.esintheta0, parent, doc);
        return QStringLiteral("conic");
    } else if (d.inherits(CubicImp::stype())) {
        const CubicCartesianData data = static_cast<const CubicImp &>(d).data();
        QDomElement coeffs = doc.createElement(QStringLiteral("coefficients"));
        addDoubleElement("a000", data.coeffs[0], coeffs, doc);
        addDoubleElement("a001", data.coeffs[1], coeffs, doc);
        addDoubleElement("a002", data.coeffs[2], coeffs, doc);
        addDoubleElement("a011", data.coeffs[3], coeffs, doc);
        addDoubleElement("a012", data.coeffs[4], coeffs, doc);
        addDoubleElement("a022", data.coeffs[5], coeffs, doc);
        addDoubleElement("a111", data.coeffs[6], coeffs, doc);
        addDoubleElement("a112", data.coeffs[7], coeffs, doc);
        addDoubleElement("a122", data.coeffs[8], coeffs, doc);
        addDoubleElement("a222", data.coeffs[9], coeffs, doc);
        parent.appendChild(coeffs);
        return QStringLiteral("cubic");
    }
    assert(false);
    return QString();
}

static Coordinate readXYElements(const QDomElement &e, bool &ok)
{
    double x, y;
    ok = true;
    QDomElement xe = e.firstChild().toElement();
    if (xe.isNull() || xe.tagName() != QLatin1String("x")) {
        ok = false;
        return Coordinate();
    } else
        x = xe.text().toDouble(&ok);

    QDomElement ye = xe.nextSibling().toElement();
    if (ye.isNull() || ye.tagName() != QLatin1String("y")) {
        ok = false;
        return Coordinate();
    } else
        y = ye.text().toDouble(&ok);

    return Coordinate(x, y);
}

static Coordinate readCoordinateElement(const QDomNode &n, bool &ok, const char *tagname)
{
    QDomElement e = n.toElement();
    if (e.isNull() || e.tagName() != tagname) {
        ok = false;
        Coordinate ret;
        return ret;
    }
    return readXYElements(e, ok);
}

static double readDoubleElement(const QDomNode &n, bool &ok, const char *tagname)
{
    QDomElement e = n.toElement();
    if (e.isNull() || e.tagName() != tagname) {
        ok = false;
        return 0.;
    };
    return e.text().toDouble(&ok);
}

ObjectImp *ObjectImpFactory::deserialize(const QString &type, const QDomElement &parent, QString &error) const
{
#define KIG_GENERIC_PARSE_ERROR                                                                                                                                \
    {                                                                                                                                                          \
        error = i18n("An error was encountered at line %1 in file %2.", __LINE__, __FILE__);                                                                   \
        return nullptr;                                                                                                                                        \
    }

    bool ok = true;
    if (type == QLatin1String("int")) {
        int ret = parent.text().toInt(&ok);
        if (!ok)
            KIG_GENERIC_PARSE_ERROR;
        return new IntImp(ret);
    } else if (type == QLatin1String("double")) {
        double ret = parent.text().toDouble(&ok);
        if (!ok)
            KIG_GENERIC_PARSE_ERROR;
        return new DoubleImp(ret);
    } else if (type == QLatin1String("string")) {
        return new StringImp(parent.text());
    } else if (type == QLatin1String("testresult")) {
        // should never get here, at least for new save files!
        return new TestResultImp(true, parent.text());
    } else if (type == QLatin1String("hierarchy")) {
        ObjectHierarchy *hier = ObjectHierarchy::buildSafeObjectHierarchy(parent, error);
        if (!hier)
            return nullptr;
        HierarchyImp *imp = new HierarchyImp(*hier);
        delete hier;
        return imp;
    } else if (type == QLatin1String("transformation")) {
        double data[3][3];
        bool homothetic = false;
        for (QDomElement childe = parent.firstChild().toElement(); !childe.isNull(); childe = childe.nextSibling().toElement()) {
            if (childe.tagName() == QLatin1String("matrix")) {
                for (QDomElement elel = childe.firstChild().toElement(); !elel.isNull(); elel = elel.nextSibling().toElement()) {
                    if (elel.tagName() != QLatin1String("element"))
                        KIG_GENERIC_PARSE_ERROR;
                    bool ok = true;
                    int row = elel.attribute(QStringLiteral("row")).toInt(&ok);
                    if (!ok)
                        KIG_GENERIC_PARSE_ERROR;
                    int column = elel.attribute(QStringLiteral("column")).toInt(&ok);
                    if (!ok)
                        KIG_GENERIC_PARSE_ERROR;
                    data[row][column] = elel.text().toDouble(&ok);
                    if (!ok)
                        KIG_GENERIC_PARSE_ERROR;
                };
            } else if (childe.tagName() == QLatin1String("homothetic")) {
                homothetic = childe.text() == QLatin1String("true");
            } else
                continue;
        };
        Transformation trans(data, homothetic);
        return new TransformationImp(trans);
    } else if (type == QLatin1String("point")) {
        Coordinate ret = readXYElements(parent, ok);
        if (!ok)
            KIG_GENERIC_PARSE_ERROR;
        return new PointImp(ret);
    } else if (type == QLatin1String("line") || type == QLatin1String("segment") || type == QLatin1String("ray")) {
        QDomNode n = parent.firstChild();
        Coordinate a = readCoordinateElement(n, ok, "a");
        if (!ok)
            KIG_GENERIC_PARSE_ERROR;
        n = n.nextSibling();
        Coordinate b = readCoordinateElement(n, ok, "b");
        if (!ok)
            KIG_GENERIC_PARSE_ERROR;
        if (type == QLatin1String("line"))
            return new LineImp(a, b);
        else if (type == QLatin1String("segment"))
            return new SegmentImp(a, b);
        else
            return new RayImp(a, b);
    } else if (type == QLatin1String("angle")) {
        double size = readDoubleElement(parent.firstChild(), ok, "size");
        if (!ok)
            KIG_GENERIC_PARSE_ERROR;

        // TODO figure out how to know if this should be marked as a right angle
        return new AngleImp(Coordinate(), 0, size, false);
    } else if (type == QLatin1String("arc")) {
        QDomNode n = parent.firstChild();
        Coordinate center = readCoordinateElement(n, ok, "center");
        if (!ok)
            KIG_GENERIC_PARSE_ERROR;
        n = n.nextSibling();
        double radius = readDoubleElement(n, ok, "radius");
        if (!ok)
            KIG_GENERIC_PARSE_ERROR;
        n = n.nextSibling();
        double startangle = readDoubleElement(n, ok, "startangle");
        if (!ok)
            KIG_GENERIC_PARSE_ERROR;
        n = n.nextSibling();
        double angle = readDoubleElement(n, ok, "angle");
        if (!ok)
            KIG_GENERIC_PARSE_ERROR;
        return new ArcImp(center, radius, startangle, angle);
    } else if (type == QLatin1String("vector")) {
        Coordinate dir = readXYElements(parent, ok);
        if (!ok)
            KIG_GENERIC_PARSE_ERROR;
        return new VectorImp(Coordinate(), dir);
    } else if (type == QLatin1String("locus")) {
        QDomElement curvee = parent.firstChild().toElement();
        if (curvee.isNull() || curvee.tagName() != QLatin1String("curve"))
            KIG_GENERIC_PARSE_ERROR;
        QString type = curvee.attribute(QStringLiteral("type"));
        ObjectImp *oi = deserialize(type, curvee, error);
        if (!oi || !oi->inherits(CurveImp::stype()))
            KIG_GENERIC_PARSE_ERROR;
        // CurveImp* curvei = static_cast<CurveImp*>( oi );

        QDomElement hiere = curvee.nextSibling().toElement();
        if (hiere.isNull() || hiere.tagName() != QLatin1String("calculation"))
            KIG_GENERIC_PARSE_ERROR;
        assert(false); // TODO
        //    return new LocusImp( curvei, hier );
    } else if (type == QLatin1String("circle")) {
        QDomNode n = parent.firstChild();
        Coordinate center = readCoordinateElement(n, ok, "center");
        if (!ok)
            KIG_GENERIC_PARSE_ERROR;

        n = n.nextSibling();
        double radius = readDoubleElement(n, ok, "radius");
        if (!ok)
            KIG_GENERIC_PARSE_ERROR;

        return new CircleImp(center, radius);
    } else if (type == QLatin1String("conic")) {
        QDomNode n = parent.firstChild();
        Coordinate focus1 = readCoordinateElement(n, ok, "focus1");
        if (!ok)
            KIG_GENERIC_PARSE_ERROR;

        n = n.nextSibling();
        double pdimen = readDoubleElement(n, ok, "pdimen");
        if (!ok)
            KIG_GENERIC_PARSE_ERROR;

        n = n.nextSibling();
        double ecostheta0 = readDoubleElement(n, ok, "ecostheta0");
        if (!ok)
            KIG_GENERIC_PARSE_ERROR;

        n = n.nextSibling();
        double esintheta0 = readDoubleElement(n, ok, "esintheta0");
        if (!ok)
            KIG_GENERIC_PARSE_ERROR;

        return new ConicImpPolar(ConicPolarData(focus1, pdimen, ecostheta0, esintheta0));
    } else if (type == QLatin1String("cubic")) {
        QDomElement coeffse = parent.firstChild().toElement();
        if (coeffse.isNull() || coeffse.tagName() != QLatin1String("coefficients"))
            KIG_GENERIC_PARSE_ERROR;

        QDomNode n = coeffse.firstChild();
        double a000 = readDoubleElement(n, ok, "a000");
        if (!ok)
            KIG_GENERIC_PARSE_ERROR;

        n = n.nextSibling();
        double a001 = readDoubleElement(n, ok, "a001");
        if (!ok)
            KIG_GENERIC_PARSE_ERROR;

        n = n.nextSibling();
        double a002 = readDoubleElement(n, ok, "a002");
        if (!ok)
            KIG_GENERIC_PARSE_ERROR;

        n = n.nextSibling();
        double a011 = readDoubleElement(n, ok, "a011");
        if (!ok)
            KIG_GENERIC_PARSE_ERROR;

        n = n.nextSibling();
        double a012 = readDoubleElement(n, ok, "a012");
        if (!ok)
            KIG_GENERIC_PARSE_ERROR;

        n = n.nextSibling();
        double a022 = readDoubleElement(n, ok, "a022");
        if (!ok)
            KIG_GENERIC_PARSE_ERROR;

        n = n.nextSibling();
        double a111 = readDoubleElement(n, ok, "a111");
        if (!ok)
            KIG_GENERIC_PARSE_ERROR;

        n = n.nextSibling();
        double a112 = readDoubleElement(n, ok, "a112");
        if (!ok)
            KIG_GENERIC_PARSE_ERROR;

        n = n.nextSibling();
        double a122 = readDoubleElement(n, ok, "a112");
        if (!ok)
            KIG_GENERIC_PARSE_ERROR;

        n = n.nextSibling();
        double a222 = readDoubleElement(n, ok, "a222");
        if (!ok)
            KIG_GENERIC_PARSE_ERROR;

        return new CubicImp(CubicCartesianData(a000, a001, a002, a011, a012, a022, a111, a112, a122, a222));
    }

    error = i18n(
        "This Kig file uses an object of type \"%1\", "
        "which this Kig version does not support."
        "Perhaps you have compiled Kig without support "
        "for this object type,"
        "or perhaps you are using an older Kig version.",
        type);
    return nullptr;
}
