/* This file is part of the KDE project
   SPDX-FileCopyrightText: 2009 Thorsten Zachmann <zachmann@kde.org>
   SPDX-FileCopyrightText: 2009 Johannes Simon <johannes.simon@gmail.com>
   SPDX-FileCopyrightText: 2010, 2011 Jan Hambrecht <jaham@gmx.net>
   SPDX-FileCopyrightText: 2012 Friedrich W. H. Kossebau <kossebau@kde.org>

   SPDX-License-Identifier: LGPL-2.0-or-later
*/

#include "KoOdfWorkaround.h"

#include "KoColorBackground.h"
#include "KoPathShape.h"
#include "KoShape.h"
#include "KoShapeLoadingContext.h"
#include <KoOdfLoadingContext.h>
#include <KoStyleStack.h>
#include <KoUnit.h>
#include <KoXmlNS.h>
#include <KoXmlReader.h>

#include <QColor>
#include <QPen>

#include <FlakeDebug.h>

static bool s_workaroundPresentationPlaceholderBug = false;

void KoOdfWorkaround::fixSkew(QStringList &params, KoShapeLoadingContext &context)
{
    if (context.odfLoadingContext().generatorType() == KoOdfLoadingContext::OpenOffice) {
        debugFlake << "Work around OO bug: skewa clockwize and radians as default unit";
        QString angle = params[0].trimmed();
        if (angle.startsWith('-')) {
            angle = angle.remove(0, 1);
        } else {
            angle = angle.prepend('-');
        }
        const QChar c = angle.at(angle.length() - 1);
        if (c.isDigit()) {
            angle.append("rad");
        }
        params[0] = angle;
    }
}

void KoOdfWorkaround::fixRotate(QStringList &params, KoShapeLoadingContext &context)
{
    if (context.odfLoadingContext().generatorType() == KoOdfLoadingContext::OpenOffice) {
        debugFlake << "Work around OO bug: rotates clockwize and radians as default unit";
        QString angle = params[0].trimmed();
        if (angle.startsWith('-')) {
            angle = angle.remove(0, 1);
        } else {
            angle = angle.prepend('-');
        }
        const QChar c = angle.at(angle.length() - 1);
        if (c.isDigit()) {
            angle.append("rad");
        }
        params[0] = angle;
    }
}

void KoOdfWorkaround::fixPenWidth(QPen &pen, KoShapeLoadingContext &context)
{
    if (context.odfLoadingContext().generatorType() == KoOdfLoadingContext::OpenOffice && pen.widthF() == 0.0) {
        pen.setWidthF(0.5);
        debugFlake << "Work around OO bug with pen width 0";
    }
}

void KoOdfWorkaround::fixEnhancedPath(QString &path, const KoXmlElement &element, KoShapeLoadingContext &context)
{
    if (context.odfLoadingContext().generatorType() == KoOdfLoadingContext::OpenOffice) {
        if (path.isEmpty() && element.attributeNS(KoXmlNS::draw, "type", "") == "ellipse") {
            path = "U 10800 10800 10800 10800 0 360 Z N";
        }
    }
}

void KoOdfWorkaround::fixEnhancedPathPolarHandlePosition(QString &position, const KoXmlElement &element, KoShapeLoadingContext &context)
{
    if (context.odfLoadingContext().generatorType() == KoOdfLoadingContext::OpenOffice) {
        if (element.hasAttributeNS(KoXmlNS::draw, "handle-polar")) {
            QStringList tokens = position.simplified().split(' ');
            if (tokens.count() == 2) {
                position = tokens[1] + ' ' + tokens[0];
            }
        }
    }
}

QColor KoOdfWorkaround::fixMissingFillColor(const KoXmlElement &element, KoShapeLoadingContext &context)
{
    // Default to an invalid color
    QColor color;

    if (element.prefix() == "chart") {
        KoStyleStack &styleStack = context.odfLoadingContext().styleStack();
        styleStack.save();

        bool hasStyle = element.hasAttributeNS(KoXmlNS::chart, "style-name");
        if (hasStyle) {
            context.odfLoadingContext().fillStyleStack(element, KoXmlNS::chart, "style-name", "chart");
            styleStack.setTypeProperties("graphic");
        }

        if (context.odfLoadingContext().generatorType() == KoOdfLoadingContext::OpenOffice) {
            if (hasStyle && !styleStack.hasProperty(KoXmlNS::draw, "fill") && styleStack.hasProperty(KoXmlNS::draw, "fill-color")) {
                color = QColor(styleStack.property(KoXmlNS::draw, "fill-color"));
            } else if (!hasStyle || (!styleStack.hasProperty(KoXmlNS::draw, "fill") && !styleStack.hasProperty(KoXmlNS::draw, "fill-color"))) {
                KoXmlElement plotAreaElement = element.parentNode().toElement();
                KoXmlElement chartElement = plotAreaElement.parentNode().toElement();

                if (element.tagName() == "wall") {
                    if (chartElement.hasAttributeNS(KoXmlNS::chart, "class")) {
                        QString chartType = chartElement.attributeNS(KoXmlNS::chart, "class");
                        // TODO: Check what default backgrounds for surface, stock and gantt charts are
                        if (chartType == "chart:line" || chartType == "chart:area" || chartType == "chart:bar" || chartType == "chart:scatter")
                            color = QColor(0xe0e0e0);
                    }
                } else if (element.tagName() == "series") {
                    if (chartElement.hasAttributeNS(KoXmlNS::chart, "class")) {
                        QString chartType = chartElement.attributeNS(KoXmlNS::chart, "class");
                        // TODO: Check what default backgrounds for surface, stock and gantt charts are
                        if (chartType == "chart:area" || chartType == "chart:bar")
                            color = QColor(0x99ccff);
                    }
                } else if (element.tagName() == "chart")
                    color = QColor(0xffffff);
            }
        }

        styleStack.restore();
    }

    return color;
}

bool KoOdfWorkaround::fixMissingStroke(QPen &pen, const KoXmlElement &element, KoShapeLoadingContext &context, const KoShape *shape)
{
    bool fixed = false;

    if (context.odfLoadingContext().generatorType() == KoOdfLoadingContext::OpenOffice) {
        KoStyleStack &styleStack = context.odfLoadingContext().styleStack();
        if (element.prefix() == "chart") {
            styleStack.save();

            bool hasStyle = element.hasAttributeNS(KoXmlNS::chart, "style-name");
            if (hasStyle) {
                context.odfLoadingContext().fillStyleStack(element, KoXmlNS::chart, "style-name", "chart");
                styleStack.setTypeProperties("graphic");
            }

            if (hasStyle && styleStack.hasProperty(KoXmlNS::draw, "stroke") && !styleStack.hasProperty(KoXmlNS::svg, "stroke-color")) {
                fixed = true;
                pen.setColor(Qt::black);
            } else if (!hasStyle) {
                KoXmlElement plotAreaElement = element.parentNode().toElement();
                KoXmlElement chartElement = plotAreaElement.parentNode().toElement();

                if (element.tagName() == "series") {
                    QString chartType = chartElement.attributeNS(KoXmlNS::chart, "class");
                    if (!chartType.isEmpty()) {
                        // TODO: Check what default backgrounds for surface, stock and gantt charts are
                        if (chartType == "chart:line" || chartType == "chart:scatter") {
                            fixed = true;
                            pen = QPen(0x99ccff);
                        }
                    }
                } else if (element.tagName() == "legend") {
                    fixed = true;
                    pen = QPen(Qt::black);
                }
            }
            styleStack.restore();
        } else {
            const KoPathShape *pathShape = dynamic_cast<const KoPathShape *>(shape);
            if (pathShape) {
                const QString strokeColor(styleStack.property(KoXmlNS::svg, "stroke-color"));
                if (strokeColor.isEmpty()) {
                    pen.setColor(Qt::black);
                } else {
                    pen.setColor(strokeColor);
                }
                fixed = true;
            }
        }
    }

    return fixed;
}

bool KoOdfWorkaround::fixMissingStyle_DisplayLabel(const KoXmlElement &element, KoShapeLoadingContext &context)
{
    Q_UNUSED(element);
    // If no axis style is specified, OpenOffice.org hides the axis' data labels
    if (context.odfLoadingContext().generatorType() == KoOdfLoadingContext::OpenOffice)
        return false;

    // In all other cases, they're visible
    return true;
}

void KoOdfWorkaround::setFixPresentationPlaceholder(bool fix, KoShapeLoadingContext &context)
{
    KoOdfLoadingContext::GeneratorType type(context.odfLoadingContext().generatorType());
    if (type == KoOdfLoadingContext::OpenOffice || type == KoOdfLoadingContext::MicrosoftOffice) {
        s_workaroundPresentationPlaceholderBug = fix;
    }
}

bool KoOdfWorkaround::fixPresentationPlaceholder()
{
    return s_workaroundPresentationPlaceholderBug;
}

void KoOdfWorkaround::fixPresentationPlaceholder(KoShape *shape)
{
    if (s_workaroundPresentationPlaceholderBug && !shape->hasAdditionalAttribute("presentation:placeholder")) {
        shape->setAdditionalAttribute("presentation:placeholder", "true");
    }
}

QSharedPointer<KoColorBackground> KoOdfWorkaround::fixBackgroundColor(const KoShape *shape, KoShapeLoadingContext &context)
{
    QSharedPointer<KoColorBackground> colorBackground;
    KoOdfLoadingContext &odfContext = context.odfLoadingContext();
    if (odfContext.generatorType() == KoOdfLoadingContext::OpenOffice) {
        const KoPathShape *pathShape = dynamic_cast<const KoPathShape *>(shape);
        // check shape type
        if (pathShape) {
            KoStyleStack &styleStack = odfContext.styleStack();
            const QString color(styleStack.property(KoXmlNS::draw, "fill-color"));
            if (color.isEmpty()) {
                colorBackground = QSharedPointer<KoColorBackground>(new KoColorBackground(QColor(153, 204, 255)));
            } else {
                colorBackground = QSharedPointer<KoColorBackground>(new KoColorBackground(color));
            }
        }
    }
    return colorBackground;
}

void KoOdfWorkaround::fixGluePointPosition(QString &positionString, KoShapeLoadingContext &context)
{
    KoOdfLoadingContext::GeneratorType type(context.odfLoadingContext().generatorType());
    if (type == KoOdfLoadingContext::OpenOffice && !positionString.endsWith('%')) {
        const qreal pos = KoUnit::parseValue(positionString);
        positionString = QString("%1%%").arg(KoUnit::toMillimeter(pos));
    }
}

void KoOdfWorkaround::fixMissingFillRule(Qt::FillRule &fillRule, KoShapeLoadingContext &context)
{
    if ((context.odfLoadingContext().generatorType() == KoOdfLoadingContext::OpenOffice)) {
        fillRule = Qt::OddEvenFill;
    }
}

bool KoOdfWorkaround::fixAutoGrow(KoTextShapeDataBase::ResizeMethod method, KoShapeLoadingContext &context)
{
    bool fix = false;
    if (context.odfLoadingContext().generatorType() == KoOdfLoadingContext::OpenOffice) {
        if (method == KoTextShapeDataBase::AutoGrowWidth || method == KoTextShapeDataBase::AutoGrowHeight
            || method == KoTextShapeDataBase::AutoGrowWidthAndHeight) {
            fix = true;
        }
    }
    return fix;
}

bool KoOdfWorkaround::fixEllipse(const QString &kind, KoShapeLoadingContext &context)
{
    bool radiusGiven = false;
    if (context.odfLoadingContext().generatorType() == KoOdfLoadingContext::OpenOffice) {
        if (kind == "section" || kind == "arc") {
            radiusGiven = true;
        }
    }
    return radiusGiven;
}

void KoOdfWorkaround::fixBadFormulaHiddenForStyleCellProtect(QString &value)
{
    if (value.endsWith(QLatin1String("Formula.hidden"))) {
        const int length = value.length();
        value[length - 14] = QLatin1Char('f');
        value[length - 7] = QLatin1Char('-');
    }
}

void KoOdfWorkaround::fixBadDateForTextTime(QString &value)
{
    if (value.startsWith(QLatin1String("0-00-00T"))) {
        value.remove(0, 8);
    }
}

void KoOdfWorkaround::fixClipRectOffsetValuesString(QString &offsetValuesString)
{
    if (!offsetValuesString.contains(QLatin1Char(','))) {
        // assumes no spaces existing between values and units
        offsetValuesString = offsetValuesString.simplified().replace(QLatin1Char(' '), QLatin1Char(','));
    }
}

QString KoOdfWorkaround::fixTableTemplateName(const KoXmlElement &e)
{
    return e.attributeNS(KoXmlNS::text, "style-name", QString());
}

QString KoOdfWorkaround::fixTableTemplateCellStyleName(const KoXmlElement &e)
{
    return e.attributeNS(KoXmlNS::text, "style-name", QString());
}

static const struct {
    const char *oldPath;
    const char *newPath;
} markerPathMapping[] = {
    // Arrow
    {"m10 0-10 30h20z", "M10 0l-10 30h20z"},
    // Square
    {"m0 0h10v10h-10", "M0 0h10v10h-10z"},
    // Small Arrow
    {"m1321 3493h-1321l702-3493z", "M1321 3493h-1321l702-3493z"},
    // Dimension Lines
    {"M0 0h278 278 280v36 36 38h-278-278-280v-36-36z", "m0 0h278 278 280v36 36 38h-278-278-280v-36-36z"},
    // Double Arrow
    {"m737 1131h394l-564-1131-567 1131h398l-398 787h1131z", "M737 1131h394l-564-1131-567 1131h398l-398 787h1131z"},
    // Rounded short Arrow
    {"m1009 1050-449-1008-22-30-29-12-34 12-21 26-449 1012-5 13v8l5 21 12 21 17 13 21 4h903l21-4 21-13 9-21 4-21v-8z",
     "M1009 1050l-449-1008-22-30-29-12-34 12-21 26-449 1012-5 13v8l5 21 12 21 17 13 21 4h903l21-4 21-13 9-21 4-21v-8z"},
    // Symmetric Arrow
    {"m564 0-564 902h1131z", "M564 0l-564 902h1131z"},
    // Line Arrow
    {"m0 2108v17 17l12 42 30 34 38 21 43 4 29-8 30-21 25-26 13-34 343-1532 339 1520 13 42 29 34 39 21 42 4 42-12 34-30 21-42v-39-12l-4 "
     "4-440-1998-9-42-25-39-38-25-43-8-42 8-38 25-26 39-8 42z",
     "M0 2108v17 17l12 42 30 34 38 21 43 4 29-8 30-21 25-26 13-34 343-1532 339 1520 13 42 29 34 39 21 42 4 42-12 34-30 21-42v-39-12l-4 "
     "4-440-1998-9-42-25-39-38-25-43-8-42 8-38 25-26 39-8 42z"},
    // Rounded large Arrow
    {"m1127 2120-449-2006-9-42-25-39-38-25-38-8-43 8-38 25-25 39-9 42-449 2006v13l-4 9 9 42 25 38 38 25 42 9h903l42-9 38-25 26-38 8-42v-9z",
     "M1127 2120l-449-2006-9-42-25-39-38-25-38-8-43 8-38 25-25 39-9 42-449 2006v13l-4 9 9 42 25 38 38 25 42 9h903l42-9 38-25 26-38 8-42v-9z"},
    // Circle
    {"m462 1118-102-29-102-51-93-72-72-93-51-102-29-102-13-105 13-102 29-106 51-102 72-89 93-72 102-50 102-34 106-9 101 9 106 34 98 50 93 72 72 89 51 102 29 "
     "106 13 102-13 105-29 102-51 102-72 93-93 72-98 51-106 29-101 13z",
     "M462 1118l-102-29-102-51-93-72-72-93-51-102-29-102-13-105 13-102 29-106 51-102 72-89 93-72 102-50 102-34 106-9 101 9 106 34 98 50 93 72 72 89 51 102 29 "
     "106 13 102-13 105-29 102-51 102-72 93-93 72-98 51-106 29-101 13z"},
    // Square 45
    {"m0 564 564 567 567-567-567-564z", "M0 564l564 567 567-567-567-564z"},
    // Arrow concave
    {"m1013 1491 118 89-567-1580-564 1580 114-85 136-68 148-46 161-17 161 13 153 46z",
     "M1013 1491l118 89-567-1580-564 1580 114-85 136-68 148-46 161-17 161 13 153 46z"},
    // Short line Arrow
    {"m1500 0 1500 2789v211h-114l-1286-2392v2392h-200v-2392l-1286 2392h-114v-211z",
     "M1500 0l1500 2789v211h-114l-1286-2392v2392h-200v-2392l-1286 2392h-114v-211z"},
    // Triangle unfilled
    {"m1500 0 1500 3000h-3000zm1500-2553-1176 2353h2353z", "M1500 0l1500 3000h-3000zM1500 447l-1176 2353h2353z"},
    // Diamond unfilled
    {"m1500 0 1500 3000-1500 3000-1500-3000zm1500-2553-1276 2553 1276 2553 1276-2553z",
     "M1500 0l1500 3000-1500 3000-1500-3000zM1500 447l-1276 2553 1276 2553 1276-2553z"},
    // Diamond
    {"m1500 0 1500 3000-1500 3000-1500-3000z", "M1500 0l1500 3000-1500 3000-1500-3000z"},
    // Circle unfilled
    {"m1500 3000c-276 0-511-63-750-201s-411-310-549-549-201-474-201-750 63-511 201-750 310-411 549-549 474-201 750-201 511 63 750 201 411 310 549 549 201 474 "
     "201 750-63 511-201 750-310 411-549 549-474 201-750 201zm0-200c-239 0-443-55-650-174s-356-269-476-476-174-411-174-650 55-443 174-650 269-356 "
     "476-476c207-119 411-174 650-174s443 55 650 174c207 120 356 269 476 476s174 411 174 650-55 443-174 650-269 356-476 476c-207 119-411 174-650 174z",
     "M1500 3000c-276 0-511-63-750-201s-411-310-549-549-201-474-201-750 63-511 201-750 310-411 549-549 474-201 750-201 511 63 750 201 411 310 549 549 201 474 "
     "201 750-63 511-201 750-310 411-549 549-474 201-750 201zM1500 2800c-239 0-443-55-650-174s-356-269-476-476-174-411-174-650 55-443 174-650 269-356 "
     "476-476c207-119 411-174 650-174s443 55 650 174c207 120 356 269 476 476s174 411 174 650-55 443-174 650-269 356-476 476c-207 119-411 174-650 174z"},
    // Square 45 unfilled
    {"m1500 3000-1500-1500 1500-1500 1500 1500zm-1500 1215-1215-1215 1215-1215 1215 1215z",
     "M1500 3000l-1500-1500 1500-1500 1500 1500zM1500 2715l-1215-1215 1215-1215 1215 1215z"},
    // Square unfilled
    {"m0 0h300v300h-300zm20-280h260v260h-260z", "M0 0h300v300h-300zM20 20h260v260h-260z"},
    // Half Circle unfilled
    {"m14971 0c21 229 29 423 29 653 0 690-79 1328-244 1943-165 614-416 1206-761 1804-345 597-733 1110-1183 1560-451 450-964 837-1562 1182-598 345-1190 "
     "596-1806 760-600 161-1223 240-1894 "
     "244v600h-100v-600c-671-4-1294-83-1894-244-616-164-1208-415-1806-760-598-345-1111-732-1562-1182-450-450-838-963-1183-1560-345-598-596-1190-761-1804-165-"
     "615-244-1253-244-1943 0-230 8-424 29-653l298 26 299 26c-18 211-26 390-26 601 0 635 72 1222 224 1787 151 566 383 1110 700 1659 318 550 674 1022 1088 1437 "
     "415 414 888 769 1438 1087 550 317 1095 548 1661 700 566 151 1154 223 1789 223s1223-72 1789-223c566-152 1111-383 1661-700 550-318 1023-673 1438-1087 "
     "414-415 770-887 1088-1437 317-549 549-1093 700-1659 152-565 224-1152 224-1787 0-211-8-390-26-601l299-26z",
     "M14971 0c21 229 29 423 29 653 0 690-79 1328-244 1943-165 614-416 1206-761 1804-345 597-733 1110-1183 1560-451 450-964 837-1562 1182s-1190 596-1806 "
     "760c-600 161-1223 240-1894 "
     "244v600h-100v-600c-671-4-1294-83-1894-244-616-164-1208-415-1806-760s-1111-732-1562-1182c-450-450-838-963-1183-1560-345-598-596-1190-761-1804-165-615-244-"
     "1253-244-1943 0-230 8-424 29-653l298 26 299 26c-18 211-26 390-26 601 0 635 72 1222 224 1787 151 566 383 1110 700 1659 318 550 674 1022 1088 1437 415 414 "
     "888 769 1438 1087 550 317 1095 548 1661 700 566 151 1154 223 1789 223s1223-72 1789-223c566-152 1111-383 1661-700 550-318 1023-673 1438-1087 414-415 "
     "770-887 1088-1437 317-549 549-1093 700-1659 152-565 224-1152 224-1787 0-211-8-390-26-601l299-26z"}};
static const int markerPathMappingSize = sizeof(markerPathMapping) / sizeof(markerPathMapping[0]);

void KoOdfWorkaround::fixMarkerPath(QString &path)
{
    for (int i = 0; i < markerPathMappingSize; ++i) {
        if (path == QLatin1String(markerPathMapping[i].oldPath)) {
            path = QLatin1String(markerPathMapping[i].newPath);
            break;
        }
    }
}
