/* This file is part of the Calligra project, made within the KDE community.

   SPDX-FileCopyrightText: 2012 Friedrich W. H. Kossebau <kossebau@kde.org>

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

#include "XFigOdgWriter.h"

// filter
#include "XFigDocument.h"
// Calligra
#include <KoDocumentInfo.h>
#include <KoOdf.h>
#include <KoStore.h>
#include <KoXmlNS.h>
#include <KoXmlWriter.h>

// keep in sync with XFigPageSizeType
static const struct PageSize {
    const char *width;
    const char *height;
} pageSizeTable[29] = {
    {"8.5in", "11in"}, // letter
    {"8.5in", "14in"}, // legal
    {"11in", "17in"}, // tabloid

    {"8.5in", "11in"}, // A
    {"11in", "17in"}, // B
    {"17in", "22in"}, // C
    {"22in", "34in"}, // D
    {"34in", "44in"}, // E

    {"37mm", "52mm"}, // A9
    {"52mm", "74mm"}, // A8
    {"74mm", "105mm"}, // A7
    {"105mm", "148mm"}, // A6
    {"148mm", "297mm"}, // A5
    {"210mm", "297mm"}, // A4
    {"297mm", "420mm"}, // A3
    {"420mm", "594mm"}, // A2
    {"594mm", "841mm"}, // A1
    {"841mm", "1189mm"}, // A0

    {"32mm", "45mm"}, // B10
    {"45mm", "64mm"}, // B9
    {"64mm", "91mm"}, // B8
    {"91mm", "128mm"}, // B7
    {"128mm", "182mm"}, // B6
    {"182mm", "257mm"}, // B5
    {"257mm", "364mm"}, // B4
    {"364mm", "515mm"}, // B3
    {"515mm", "728mm"}, // B2
    {"728mm", "1030mm"}, // B1
    {"1030mm", "1456mm"} // B0
};

// Metric conversion
// 1 point = 1/72 inches
static const double inchtoPtFactor = 72;
// TODO: check that support for both inch and metric system works

static inline double ptUnit(double x)
{
    return x * inchtoPtFactor;
}

static inline double odfCornerRadius(qint32 xfigRadius)
{
    return ptUnit(static_cast<double>(xfigRadius) / 80);
}

static inline double odfLineThickness(qint32 xfigLineThickness)
{
    return ptUnit(static_cast<double>(xfigLineThickness) / 80);
}

double XFigOdgWriter::odfLength(qint32 length) const
{
    return ptUnit(static_cast<double>(length) / m_Document->resolution());
}

double XFigOdgWriter::odfLength(double length) const
{
    return ptUnit(length / m_Document->resolution());
}

double XFigOdgWriter::odfXCoord(qint32 x) const
{
    return ptUnit(static_cast<double>(x) / m_Document->resolution());
}

double XFigOdgWriter::odfYCoord(qint32 y) const
{
    return ptUnit(static_cast<double>(y) / m_Document->resolution());
}

XFigOdgWriter::XFigOdgWriter(KoStore *outputStore)
    : m_CLocale(QLocale::c())
    , m_OdfWriteStore(outputStore)
    , m_OutputStore(outputStore)
    , m_PageCount(0)
{
    m_CLocale.setNumberOptions(QLocale::OmitGroupSeparator);
    m_ManifestWriter = m_OdfWriteStore.manifestWriter(KoOdf::mimeType(KoOdf::Graphics));
}

XFigOdgWriter::~XFigOdgWriter()
{
    m_OdfWriteStore.closeManifestWriter();

    delete m_OutputStore;
}

bool XFigOdgWriter::write(XFigDocument *document)
{
    m_Document = document;

    storePixelImageFiles();

    // Create content.xml
    storeContentXml();

    // Create the styles.xml file
    m_StyleCollector.saveOdfStylesDotXml(m_OutputStore, m_ManifestWriter);

    // Create meta.xml
    storeMetaXml();

    return true;
}

void XFigOdgWriter::storePixelImageFiles()
{
    // TODO: store pixel files linked from XFigPictureBoxObject
#if 0
    // TODO: as mManifestWriter needs full rel path, perhaps drop enterDirectory/leaveDirectory
    mOutputStore->enterDirectory( QLatin1String("Pictures") );

    foreach( const XFigPixelImage* image, mDocument->pixelImages() )
    {
        mOutputStore->open( fileName );
        mOutputStore->write( *image );
        mOutputStore->close();

        mManifestWriter->addManifestEntry( filePath, mediaType );
    }

    mOutputStore->leaveDirectory();
#endif
}

void XFigOdgWriter::storeMetaXml()
{
    KoDocumentInfo documentInfo;
    documentInfo.setOriginalGenerator(QLatin1String("Calligra XFig filter"));
    documentInfo.setAboutInfo(QLatin1String("comments"), m_Document->comment());

    const QString documentInfoFilePath = QLatin1String("meta.xml");

    m_OutputStore->open(documentInfoFilePath);
    documentInfo.saveOasis(m_OutputStore);
    m_OutputStore->close();

    // TODO: "text/xml" could be a static string
    m_ManifestWriter->addManifestEntry(documentInfoFilePath, QLatin1String("text/xml"));
}

void XFigOdgWriter::storeContentXml()
{
    KoXmlWriter *contentWriter = m_OdfWriteStore.contentWriter();
    m_BodyWriter = m_OdfWriteStore.bodyWriter();

    m_BodyWriter->startElement("office:body");
    m_BodyWriter->startElement(KoOdf::bodyContentElement(KoOdf::Graphics, true));

    writeMasterPage();

    foreach (const XFigPage *page, m_Document->pages())
        writePage(page);

    m_BodyWriter->endElement(); // office:drawing
    m_BodyWriter->endElement(); // office:body
    m_BodyWriter->endDocument();

    m_StyleCollector.saveOdfStyles(KoGenStyles::DocumentAutomaticStyles, contentWriter);

    m_OdfWriteStore.closeContentWriter();

    // TODO: mOdfWriteStore.closeContentWriter() should do that, or? also "text/xml" could be a static string
    m_ManifestWriter->addManifestEntry(QLatin1String("content.xml"), QLatin1String("text/xml"));
}

void XFigOdgWriter::writeMasterPage()
{
    KoGenStyle masterPageStyle(KoGenStyle::MasterPageStyle);

    KoGenStyle masterPageLayoutStyle(KoGenStyle::PageLayoutStyle);
    masterPageLayoutStyle.setAutoStyleInStylesDotXml(true);

    if (m_Document->pageSizeType() != XFigPageSizeUnknown) {
        const PageSize &pageSize = pageSizeTable[m_Document->pageSizeType() - 1];

        // defaults to portrait in case orientation is unknown
        const bool isLandscape = (m_Document->pageOrientation() == XFigPageLandscape);
        masterPageLayoutStyle.addProperty(QLatin1String("fo:page-width"), isLandscape ? pageSize.height : pageSize.width);
        masterPageLayoutStyle.addProperty(QLatin1String("fo:page-height"), isLandscape ? pageSize.width : pageSize.height);
    }

    const QString masterPageLayoutStyleName = m_StyleCollector.insert(masterPageLayoutStyle, QLatin1String("masterPageLayoutStyle"));

    masterPageStyle.addAttribute(QLatin1String("style:page-layout-name"), masterPageLayoutStyleName);

    KoGenStyle drawingMasterPageStyle(KoGenStyle::DrawingPageStyle, "drawing-page");
    drawingMasterPageStyle.setAutoStyleInStylesDotXml(true);

    drawingMasterPageStyle.addProperty(QLatin1String("draw:fill"), "none");

    const QString drawingMasterPageStyleName = m_StyleCollector.insert(drawingMasterPageStyle, QLatin1String("drawingMasterPageStyle"));

    masterPageStyle.addAttribute(QLatin1String("draw:style-name"), drawingMasterPageStyleName);

    m_MasterPageStyleName = m_StyleCollector.insert(masterPageStyle, QLatin1String("masterPageStyle"));
}

void XFigOdgWriter::writePage(const XFigPage *page)
{
    m_BodyWriter->startElement("draw:page");

    m_BodyWriter->addAttribute("xml:id", QLatin1String("page") + QString::number(m_PageCount++));
    m_BodyWriter->addAttribute("draw:master-page-name", m_MasterPageStyleName);

    // objects
    foreach (const XFigAbstractObject *object, page->objects()) {
        writeObject(object);
    }

    m_BodyWriter->endElement(); // draw:page
}

void XFigOdgWriter::writeObject(const XFigAbstractObject *object)
{
    const XFigAbstractObject::TypeId typeId = object->typeId();

    if (typeId == XFigAbstractObject::EllipseId)
        writeEllipseObject(static_cast<const XFigEllipseObject *>(object));
    else if (typeId == XFigAbstractObject::PolylineId)
        writePolylineObject(static_cast<const XFigPolylineObject *>(object));
    else if (typeId == XFigAbstractObject::PolygonId)
        writePolygonObject(static_cast<const XFigPolygonObject *>(object));
    else if (typeId == XFigAbstractObject::BoxId)
        writeBoxObject(static_cast<const XFigBoxObject *>(object));
    else if (typeId == XFigAbstractObject::PictureBoxId)
        writePictureBoxObject(static_cast<const XFigPictureBoxObject *>(object));
    else if (typeId == XFigAbstractObject::SplineId)
        writeSplineObject(static_cast<const XFigSplineObject *>(object));
    else if (typeId == XFigAbstractObject::ArcId)
        writeArcObject(static_cast<const XFigArcObject *>(object));
    else if (typeId == XFigAbstractObject::TextId)
        writeTextObject(static_cast<const XFigTextObject *>(object));
    else if (typeId == XFigAbstractObject::CompoundId)
        writeCompoundObject(static_cast<const XFigCompoundObject *>(object));
}

void XFigOdgWriter::writeCompoundObject(const XFigCompoundObject *groupObject)
{
    // for now compounds are not written as groups, as in OpenDocument groups
    // are kind of sublayers with an automatically assigned z-index
    // while in XFig objects from different compounds can be intertwined in the z-order
    // losing the grouping seems the less tragic solution over wrong order of objects
    // TODO: find if a compound really needs to be dissolved, or if it does not overlap with others
    //     mBodyWriter->startElement("draw:g");

    //     writeComment(groupObject);

    foreach (const XFigAbstractObject *object, groupObject->objects()) {
        writeObject(object);
    }

    //     mBodyWriter->endElement(); //draw:g
}

void XFigOdgWriter::writeEllipseObject(const XFigEllipseObject *ellipseObject)
{
    m_BodyWriter->startElement("draw:ellipse");

    writeZIndex(ellipseObject);

    const XFigPoint centerPoint = ellipseObject->centerPoint();
    m_BodyWriter->addAttribute("svg:cx", "0pt");
    m_BodyWriter->addAttribute("svg:cy", "0pt");
    m_BodyWriter->addAttributePt("svg:rx", odfLength(ellipseObject->xRadius()));
    m_BodyWriter->addAttributePt("svg:ry", odfLength(ellipseObject->yRadius()));

    const QString transformationString = QLatin1String("rotate(") + m_CLocale.toString(ellipseObject->xAxisAngle()) + QLatin1String(")translate(")
        + m_CLocale.toString(odfXCoord(centerPoint.x())) + QLatin1String("pt ") + m_CLocale.toString(odfYCoord(centerPoint.y())) + QLatin1String("pt)");
    m_BodyWriter->addAttribute("draw:transform", transformationString);

    KoGenStyle ellipseStyle(KoGenStyle::GraphicAutoStyle, "graphic");
    writeStroke(ellipseStyle, ellipseObject);
    writeFill(ellipseStyle, ellipseObject, ellipseObject->lineColorId());
    const QString ellipseStyleName = m_StyleCollector.insert(ellipseStyle, QLatin1String("ellipseStyle"));
    m_BodyWriter->addAttribute("draw:style-name", ellipseStyleName);

    writeComment(ellipseObject);

    m_BodyWriter->endElement(); // draw:ellipse
}

void XFigOdgWriter::writePolylineObject(const XFigPolylineObject *polylineObject)
{
    m_BodyWriter->startElement("draw:polyline");

    writeZIndex(polylineObject);

    writePoints(polylineObject->points());

    KoGenStyle polylineStyle(KoGenStyle::GraphicAutoStyle, "graphic");
    writeStroke(polylineStyle, polylineObject);
    writeFill(polylineStyle, polylineObject, polylineObject->lineColorId());
    writeJoinType(polylineStyle, polylineObject->joinType());
    writeCapType(polylineStyle, polylineObject);
    writeArrow(polylineStyle, polylineObject->backwardArrow(), LineStart);
    writeArrow(polylineStyle, polylineObject->forwardArrow(), LineEnd);
    const QString polylineStyleName = m_StyleCollector.insert(polylineStyle, QLatin1String("polylineStyle"));
    m_BodyWriter->addAttribute("draw:style-name", polylineStyleName);

    writeComment(polylineObject);

    m_BodyWriter->endElement(); // draw:polyline
}

void XFigOdgWriter::writePolygonObject(const XFigPolygonObject *polygonObject)
{
    m_BodyWriter->startElement("draw:polygon");

    writeZIndex(polygonObject);

    writePoints(polygonObject->points());

    KoGenStyle polygonStyle(KoGenStyle::GraphicAutoStyle, "graphic");
    writeStroke(polygonStyle, polygonObject);
    writeFill(polygonStyle, polygonObject, polygonObject->lineColorId());
    writeJoinType(polygonStyle, polygonObject->joinType());
    const QString polygonStyleName = m_StyleCollector.insert(polygonStyle, QLatin1String("polygonStyle"));
    m_BodyWriter->addAttribute("draw:style-name", polygonStyleName);

    writeComment(polygonObject);

    m_BodyWriter->endElement(); // draw:polygon
}

void XFigOdgWriter::writeBoxObject(const XFigBoxObject *boxObject)
{
    m_BodyWriter->startElement("draw:rect");

    writeZIndex(boxObject);

    const XFigPoint upperleft = boxObject->upperLeft();
    m_BodyWriter->addAttributePt("svg:x", odfXCoord(upperleft.x()));
    m_BodyWriter->addAttributePt("svg:y", odfYCoord(upperleft.y()));
    m_BodyWriter->addAttributePt("svg:width", odfLength(boxObject->width()));
    m_BodyWriter->addAttributePt("svg:height", odfLength(boxObject->height()));

    const qint32 radius = boxObject->radius();
    if (radius != 0) {
        const double odfRadius = odfCornerRadius(radius);
        m_BodyWriter->addAttributePt("svg:rx", odfRadius);
        m_BodyWriter->addAttributePt("svg:ry", odfRadius);
    }

    {
        KoGenStyle boxStyle(KoGenStyle::GraphicAutoStyle, "graphic");
        writeStroke(boxStyle, boxObject);
        writeFill(boxStyle, boxObject, boxObject->lineColorId());
        writeJoinType(boxStyle, boxObject->joinType());
        const QString boxStyleName = m_StyleCollector.insert(boxStyle, QLatin1String("boxStyle"));
        m_BodyWriter->addAttribute("draw:style-name", boxStyleName);
    }

    writeComment(boxObject);

    m_BodyWriter->endElement(); // draw:rect
}

void XFigOdgWriter::writePictureBoxObject(const XFigPictureBoxObject * /*pictureBoxObject*/)
{
    // TODO
}

void XFigOdgWriter::writeSplineObject(const XFigSplineObject * /*object*/)
{
}

void XFigOdgWriter::writeArcObject(const XFigArcObject *arcObject)
{
    const XFigPoint centerPoint = arcObject->centerPoint();
    const XFigPoint point1 = arcObject->point1();
    const XFigPoint point3 = arcObject->point3();

    const XFigCoord diffX1 = point1.x() - centerPoint.x();
    const XFigCoord diffY1 = point1.y() - centerPoint.y();
    const XFigCoord diffX3 = point3.x() - centerPoint.x();
    const XFigCoord diffY3 = point3.y() - centerPoint.y();

    double startAngle = -atan2((qreal)diffY1, diffX1) * 180.0 / M_PI;
    double endAngle = -atan2((qreal)diffY3, diffX3) * 180.0 / M_PI;
    if (arcObject->direction() == XFigArcObject::Clockwise) {
        const double helper = startAngle;
        startAngle = endAngle;
        endAngle = helper;
    }
    const double radius = qSqrt((diffX1 * diffX1) + (diffY1 * diffY1));

    m_BodyWriter->startElement("draw:circle");

    writeZIndex(arcObject);

    m_BodyWriter->addAttributePt("svg:cx", odfXCoord(centerPoint.x()));
    m_BodyWriter->addAttributePt("svg:cy", odfXCoord(centerPoint.y()));
    m_BodyWriter->addAttributePt("svg:r", odfLength(radius));
    m_BodyWriter->addAttribute("draw:start-angle", startAngle);
    m_BodyWriter->addAttribute("draw:end-angle", endAngle);

    // TODO: cut in XFig has no line on the cut side, only on the curve
    const char *kind = (arcObject->subtype() == XFigArcObject::PieWedgeClosed) ? "section" : (arcObject->fillType() != XFigFillNone) ? "cut" : "arc";
    m_BodyWriter->addAttribute("draw:kind", kind);

    KoGenStyle arcStyle(KoGenStyle::GraphicAutoStyle, "graphic");
    writeStroke(arcStyle, arcObject);
    writeFill(arcStyle, arcObject, arcObject->lineColorId());
    writeCapType(arcStyle, arcObject);
    writeArrow(arcStyle, arcObject->backwardArrow(), (arcObject->direction() == XFigArcObject::Clockwise) ? LineEnd : LineStart);
    writeArrow(arcStyle, arcObject->forwardArrow(), (arcObject->direction() == XFigArcObject::Clockwise) ? LineStart : LineEnd);
    const QString arcStyleName = m_StyleCollector.insert(arcStyle, QLatin1String("arcStyle"));
    m_BodyWriter->addAttribute("draw:style-name", arcStyleName);

    writeComment(arcObject);

    m_BodyWriter->endElement(); // draw:circle
}

void XFigOdgWriter::writeTextObject(const XFigTextObject *textObject)
{
    m_BodyWriter->startElement("draw:frame");

    writeZIndex(textObject);

    const double length = odfLength(textObject->length()) * 1.3; // 1.3 to adapt to wider fonts being used
    const double height = odfLength(textObject->height()) * 1.3;

    const XFigTextAlignment alignment = textObject->textAlignment();
    const XFigPoint point = textObject->baselineStartPoint();
    double xCoord = odfXCoord(point.x());
    if (alignment == XFigTextCenterAligned) {
        xCoord -= length * 0.5;
    } else if (alignment == XFigTextRightAligned) {
        xCoord -= length;
    }
    // given point is at baseline, with height the ascend of the font
    double yCoord = odfYCoord(point.y() - textObject->height());

    m_BodyWriter->addAttribute("svg:x", "0pt");
    m_BodyWriter->addAttribute("svg:y", "0pt");
    m_BodyWriter->addAttributePt("svg:width", length);
    m_BodyWriter->addAttributePt("svg:height", height);
    const QString transformationString = QLatin1String("rotate(") + m_CLocale.toString(textObject->xAxisAngle()) + QLatin1String(")translate(")
        + m_CLocale.toString(xCoord) + QLatin1String("pt ") + m_CLocale.toString(yCoord) + QLatin1String("pt)");
    m_BodyWriter->addAttribute("draw:transform", transformationString);

    KoGenStyle frameStyle(KoGenStyle::GraphicAutoStyle, "graphic");
    frameStyle.addProperty(QLatin1String("style:overflow-behavior"), "clip");
    const QString frameStyleName = m_StyleCollector.insert(frameStyle, QLatin1String("frameStyle"));
    m_BodyWriter->addAttribute("draw:style-name", frameStyleName);

    m_BodyWriter->startElement("draw:text-box");

    m_BodyWriter->startElement("text:p", false); // false: we should not indent the inner tags

    KoGenStyle paragraphStyle(KoGenStyle::ParagraphAutoStyle, "paragraph");
    writeParagraphStyle(paragraphStyle, textObject);

    const QString paragraphStyleName = m_StyleCollector.insert(paragraphStyle, QLatin1String("paragraphStyle"));
    m_BodyWriter->addAttribute("text:style-name", paragraphStyleName);

    m_BodyWriter->startElement("text:span");

    KoGenStyle textSpanStyle(KoGenStyle::TextAutoStyle, "text");
    writeFont(textSpanStyle, textObject);

    const QString textSpanStyleName = m_StyleCollector.insert(textSpanStyle, QLatin1String("textSpanStyle"));
    m_BodyWriter->addAttribute("text:style-name", textSpanStyleName);

    m_BodyWriter->addTextNode(textObject->text());

    m_BodyWriter->endElement(); // text:span
    m_BodyWriter->endElement(); // text:p

    m_BodyWriter->endElement(); // draw:text-box

    writeComment(textObject);

    m_BodyWriter->endElement(); // draw:frame
}

void XFigOdgWriter::writeZIndex(const XFigAbstractGraphObject *graphObject)
{
    m_BodyWriter->addAttribute("draw:z-index", (1000 - graphObject->depth()));
}

void XFigOdgWriter::writePoints(const QList<XFigPoint> &points)
{
    const XFigPoint &firstPoint = points.at(0);

    XFigCoord minX = firstPoint.x();
    XFigCoord minY = firstPoint.y();
    XFigCoord maxX = firstPoint.x();
    XFigCoord maxY = firstPoint.y();

    QString pointsString;
    int i = 0;
    while (true) {
        const XFigPoint &point = points.at(i);
        const XFigCoord x = point.x();
        const XFigCoord y = point.y();

        if (x < minX) {
            minX = x;
        } else if (maxX < x) {
            maxX = x;
        }
        if (y < minY) {
            minY = y;
        } else if (maxY < y) {
            maxY = y;
        }

        pointsString += m_CLocale.toString(x) + QLatin1Char(',') + m_CLocale.toString(y);
        ++i;
        if (i >= points.count()) {
            break;
        }
        pointsString += QLatin1Char(' ');
    }
    const XFigCoord width = maxX - minX + 1;
    const XFigCoord height = maxY - minY + 1;
    const QString viewBoxString = QString::number(minX) + QLatin1Char(' ') + QString::number(minY) + QLatin1Char(' ') + QString::number(width)
        + QLatin1Char(' ') + QString::number(height);

    m_BodyWriter->addAttributePt("svg:x", odfXCoord(minX));
    m_BodyWriter->addAttributePt("svg:y", odfYCoord(minY));
    m_BodyWriter->addAttributePt("svg:width", odfLength(width));
    m_BodyWriter->addAttributePt("svg:height", odfLength(height));
    m_BodyWriter->addAttribute("svg:viewBox", viewBoxString);
    m_BodyWriter->addAttribute("draw:points", pointsString);
}

void XFigOdgWriter::writeComment(const XFigAbstractObject *object)
{
    const QString &comment = object->comment();
    if (comment.isEmpty()) {
        return;
    }

    m_BodyWriter->startElement("svg:desc");
    m_BodyWriter->addTextNode(comment);
    m_BodyWriter->endElement(); // svg:desc
}

void XFigOdgWriter::writeFill(KoGenStyle &odfStyle, const XFigFillable *fillable, qint32 penColorId)
{
    const XFigFillType fillType = fillable->fillType();

    const char *const fillString = (fillType == XFigFillSolid) ? "solid"
        : (fillType == XFigFillPattern)                        ? "hatch"
                                                               :
                                        /*(fillType == XFigFillNone)*/ "none";
    odfStyle.addProperty(QLatin1String("draw:fill"), fillString);

    if (fillType != XFigFillNone) {
        const qint32 fillColorId = fillable->fillColorId();

        QString colorString;
        if (fillType == XFigFillSolid) {
            // BLACK or DEFAULT color?
            if (fillColorId < 1) {
                // 0: white, 20: black, 1..19 shades of grey, from lighter to darker
                const int value = qRound((20 - fillable->fillTinting()) * 255.0 / 20.0);
                colorString = QColor(value, value, value).name();
                // WHITE color ?
            } else if (fillColorId == 7) {
                // 0: black, 20: white, 1..19 shades of grey, from darker to lighter
                const int value = qRound(fillable->fillTinting() * 255.0 / 20.0);
                colorString = QColor(value, value, value).name();
            } else {
                // TODO: tint blackness/whiteness of color
                const QColor *const color = m_Document->color(fillColorId);
                if (color != nullptr) {
                    colorString = color->name();
                }
            }

            odfStyle.addProperty(QLatin1String("draw:fill-color"), colorString);
        } else {
            // ODF 1.2 does not support a hatch pattern with a background
            // Options are to
            // * just do the hatch (not all XFig ones can be mapped)
            // * just do the background
            // * use pixmaps (needs pixmaps created with given colors)
            // Decision for now: just do the hatch
            const QColor *const color = m_Document->color(penColorId);
            if (color != nullptr) {
                colorString = color->name();
            }

            writeHatch(odfStyle, fillable->fillPatternType(), colorString);
        }
    }
}

void XFigOdgWriter::writeDotDash(KoGenStyle &odfStyle, int lineType, double distance)
{
    const double odfDistance = odfLineThickness(distance);
    odfStyle.addAttribute(QLatin1String("draw:style"), "rect");
    odfStyle.addAttribute(QLatin1String("draw:distance"), m_CLocale.toString(odfDistance) + QLatin1String("pt"));

    const char *displayName = nullptr;
    bool isFirstDot = false;
    const char *secondDotsNumber = nullptr;

    switch (lineType) {
    case XFigLineDashed:
        displayName = "Dashed";
        break;
    case XFigLineDotted:
        displayName = "Dotted";
        isFirstDot = true;
        break;
    case XFigLineDashDotted:
        displayName = "1 Dot 1 Dash";
        secondDotsNumber = "1";
        break;
    case XFigLineDashDoubleDotted:
        displayName = "1 Dash 2 Dots";
        secondDotsNumber = "2";
        break;
    case XFigLineDashTrippleDotted:
        displayName = "1 Dash 3 Dots";
        secondDotsNumber = "3";
        break;
    }

    odfStyle.addAttribute(QLatin1String("draw:display-name"), displayName);
    odfStyle.addAttribute(QLatin1String("draw:dots1"), "1");
    odfStyle.addAttribute(QLatin1String("draw:dots1-length"), isFirstDot ? QString::fromLatin1("100%") : m_CLocale.toString(odfDistance) + QLatin1String("pt"));
    if (secondDotsNumber != nullptr) {
        odfStyle.addAttribute(QLatin1String("draw:dots2"), QLatin1String(secondDotsNumber));
        odfStyle.addAttribute(QLatin1String("draw:dots2-length"), "100%");
    }
}

void XFigOdgWriter::writeStroke(KoGenStyle &odfStyle, const XFigLineable *lineable)
{
    const qint32 colorId = lineable->lineColorId();
    if (colorId >= 0) {
        const QColor *color = m_Document->color(colorId);
        if (color != nullptr) {
            odfStyle.addProperty(QLatin1String("svg:stroke-color"), color->name());
        }
    }

    odfStyle.addPropertyPt(QLatin1String("svg:stroke-width"), odfLineThickness(lineable->lineThickness()));

    const XFigLineType lineType = lineable->lineType();
    const bool isDashed = (lineType != XFigLineSolid) && (lineType != XFigLineDefault);

    odfStyle.addProperty(QLatin1String("draw:stroke"), (isDashed) ? "dash" : "solid");

    if (isDashed) {
        KoGenStyle dashStyle(KoGenStyle::StrokeDashStyle);
        writeDotDash(dashStyle, lineType, lineable->lineStyleValue());
        const QString dashStyleName = m_StyleCollector.insert(dashStyle, QLatin1String("dashStyle"));

        odfStyle.addProperty(QLatin1String("draw:stroke-dash"), dashStyleName);
    }
}

void XFigOdgWriter::writeJoinType(KoGenStyle &odfStyle, int joinType)
{
    const char *const linejoin = (joinType == XFigJoinRound) ? "round" : (joinType == XFigJoinBevel) ? "bevel" : "miter";
    odfStyle.addProperty(QLatin1String("draw:stroke-linejoin"), linejoin);
}

void XFigOdgWriter::writeCapType(KoGenStyle &odfStyle, const XFigLineEndable *lineEndable)
{
    const XFigCapType capType = lineEndable->capType();

    const char *const linecap = (capType == XFigCapRound) ? "round" : (capType == XFigCapProjecting) ? "square" : "butt";
    odfStyle.addProperty(QLatin1String("svg:stroke-linecap"), linecap);
}

static const struct ArrowData {
    const char *displayName;
    const char *viewBox;
    const char *d;
} arrowDataList[13] = {
    {
        // 0
        "Arrowheads 7", //"Stick Arrow",
        "0 0 1122 2243", //"0 0 1 1",
        "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,1 l0.5,-1 0.5,1"
    },
    {
        // 1
        "Arrowheads 6", //"Triangle Arrow",
        "0 0 1131 902", //"0 0 1 1",
        "m564 0-564 902h1131z" //"m0,1 l0.5,-1 0.5,1 -1,0z"
    },
    {
        // 2
        "Concave Spear Arrow",
        "0 0 1131 1580", //"0 0 1 1.25",
        "m1013 1491 118 89-567-1580-564 1580 114-85 136-68 148-46 161-17 161 13 153 46z" //"m0,1.25 l0.5,-1.25 0.5,1.25 -0.5,-0.25 -0.5,0.25z"
    },
    {// 3
     "Convex Spear Arrow",
     "0 0 1 1",
     "m0,0.75 l0.5,-0.75 0.5,0.75 -0.5,0.25 -0.5,-0.25z"},
    {
        // 4
        "Arrowheads 10", //"Diamond Arrow",
        "0 0 1131 1131", //"0 0 1 1",
        "m0 564 564 567 567-567-567-564z", //"m0,0.5 l0.5,-0.5 0.5,0.5 -0.5,0.5 -0.5,-0.5z"
    },
    {
        // 5
        "Arrowheads 9", //"Circle Arrow",
        "0 0 1131 1131", //"0 0 1 1",
        "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", //"m0,0.5 a0.5,0.5 0 1,1 0,0.5z"
    },
    {// 6
     "Half Circle Arrow",
     "0 0 1 1",
     "a0.5,0.5 0 1,0 1,0z"},
    {
        // 7
        "Arrowheads 1", //"Square Arrow",
        "0 0 10 10",
        "m0 0h10v10h-10z",
    },
    {// 8
     "Reverse Triangle Arrow",
     "0 0 1 1",
     "l0.5,1 0.5,-1 -1,0z"},
    {
        // 9
        "Wye Arrow",
        "0 0 1 1",
        "l0.5,1 0.5,-1" // TODO
    },
    {
        // 10
        "Arrowheads 3", //"Bar Arrow",
        "0 0 836 110",
        "m0 0h278 278 280v36 36 38h-278-278-280v-36-36z",
    },
    {
        // 11
        "Two Prong Fork Arrow",
        "0 0 1 1",
        "l0,1 1,0 0,-1" // TODO
    },
    {
        // 12
        "Reverse Two Prong Fork Arrow",
        "0 0 1 1",
        "m0,1 l0,-1 1,0 0,1" // TODO
    }};

// ODF seems to miss support for:
// * non-centric arrows
// * stroke-based arrows
// * scaling by length, not just width
// Non-centric arrows are for now simply mapped to the similar centric ones.
// TODO: calculate hollow/stroke arrow data; scale all with arrow->length()
static const int arrowDataMap[XFigArrowHeadTypeCount] = {
    0, // XFigArrowHeadStick
    1, // XFigArrowHeadHollowTriangle
    1, // XFigArrowHeadFilledTriangle
    2, // XFigArrowHeadHollowConcaveSpear
    2, // XFigArrowHeadFilledConcaveSpear
    3, // XFigArrowHeadHollowConvexSpear
    3, // XFigArrowHeadFilledConvexSpear
    4, // XFigArrowHeadHollowDiamond
    4, // XFigArrowHeadFilledDiamond
    5, // XFigArrowHeadHollowCircle
    5, // XFigArrowHeadFilledCircle
    6, // XFigArrowHeadHollowHalfCircle
    6, // XFigArrowHeadFilledHalfCircle
    7, // XFigArrowHeadHollowSquare
    7, // XFigArrowHeadFilledSquare
    8, // XFigArrowHeadHollowReverseTriangle
    8, // XFigArrowHeadFilledReverseTriangle
    2, // XFigArrowHeadTopHalfFilledConcaveSpear
    2, // XFigArrowHeadBottomHalfFilledConcaveSpear
    1, // XFigArrowHeadHollowTopHalfTriangle
    1, // XFigArrowHeadFilledTopHalfTriangle
    2, // XFigArrowHeadHollowTopHalfConcaveSpear
    2, // XFigArrowHeadFilledTopHalfConcaveSpear
    3, // XFigArrowHeadHollowTopHalfConvexSpear
    3, // XFigArrowHeadFilledTopHalfConvexSpear
    8, // XFigArrowHeadWye
    10, // XFigArrowHeadBar
    1, // XFigArrowHeadTwoProngFork
    1 // XFigArrowHeadReverseTwoProngFork
};

void XFigOdgWriter::writeArrow(KoGenStyle &odfStyle, const XFigArrowHead *arrow, LineEndType lineEndType)
{
    if (arrow == nullptr) {
        return;
    }

    KoGenStyle arrowStyle(KoGenStyle::MarkerStyle);
    const ArrowData &arrowData = arrowDataList[arrowDataMap[arrow->type()]];
    arrowStyle.addAttribute(QLatin1String("draw:display-name"), arrowData.displayName);
    arrowStyle.addAttribute(QLatin1String("svg:viewBox"), arrowData.viewBox);
    arrowStyle.addAttribute(QLatin1String("svg:d"), arrowData.d);
    const QString arrowStyleName = m_StyleCollector.insert(arrowStyle, QLatin1String("arrowStyle"));

    const char *const marker = (lineEndType == LineStart) ? "draw:marker-start" : "draw:marker-end";
    const char *const markerWidth = (lineEndType == LineStart) ? "draw:marker-start-width" : "draw:marker-end-width";
    const char *const markerCenter = (lineEndType == LineStart) ? "draw:marker-start-center" : "draw:marker-end-center";
    odfStyle.addProperty(QLatin1String(marker), arrowStyleName);
    odfStyle.addPropertyPt(QLatin1String(markerWidth), odfLength(arrow->width()));
    odfStyle.addProperty(QLatin1String(markerCenter), "1.0");
}

void XFigOdgWriter::writeHatch(KoGenStyle &odfStyle, int patternType, const QString &colorString)
{
    KoGenStyle hatchStyle(KoGenStyle::HatchStyle);

    const char *displayName = nullptr;
    const char *style = nullptr;
    const char *distance = nullptr;
    const char *rotation = nullptr;

    // Shingles, bricks, tire treads cannot be done, are mapped to horizontal/vertical lines
    // Fish, circles, hexagons, octagons cannot be done, are mapped to cross hatchess
    // 30 degree crosshatch cannot be done, mapped to 45 degree one
    switch (patternType) {
    default:
    case XFigFillVerticalShinglesSkewedDown:
    case XFigFillVerticalShinglesSkewedUp:
    case XFigFillVerticalTireTreads:
    case XFigFillVerticalBricks:
    case XFigFillVerticalLines:
        displayName = " Vertical";
        style = "single";
        distance = "0.102cm";
        rotation = "900";
        break;
    case XFigFillHorizontalShinglesSkewedRight:
    case XFigFillHorizontalShinglesSkewedLeft:
    case XFigFillHorizontalTireTreads:
    case XFigFillHorizontalBricks:
    case XFigFillHorizontalLines:
        displayName = " Horizontal";
        style = "single";
        distance = "0.102cm";
        rotation = "0";
        break;
    case XFigFillRightDiagonal30Degree:
        displayName = " 30 Degrees";
        style = "single";
        distance = "0.102cm";
        rotation = "300";
        break;
    case XFigFillLeftDiagonal30Degree:
        displayName = " -30 Degrees";
        style = "single";
        distance = "0.102cm";
        rotation = "3300";
        break;
    case XFigFillRightDiagonal45Degree:
        displayName = " 45 Degrees";
        style = "single";
        distance = "0.102cm";
        rotation = "450";
        break;
    case XFigFillLeftDiagonal45Degree:
        displayName = " -45 Degrees";
        style = "single";
        distance = "0.102cm";
        rotation = "3150";
        break;
    case XFigFillFishScales:
    case XFigFillSmallFishScales:
    case XFigFillCircles:
    case XFigFillHexagons:
    case XFigFillOctagons:
    case XFigFillCrossHatch:
        displayName = " Crossed 0 Degrees";
        style = "double";
        distance = "0.102cm";
        rotation = "900";
        break;
    case XFigFillCrossHatch30Degree:
    case XFigFillCrossHatch45Degree:
        displayName = " Crossed 45 Degrees";
        style = "double";
        distance = "0.102cm";
        rotation = "450";
        break;
    }

    hatchStyle.addAttribute("draw:display-name", colorString + QLatin1String(displayName));
    hatchStyle.addAttribute("draw:style", style);
    hatchStyle.addAttribute("draw:color", colorString);
    hatchStyle.addAttribute("draw:distance", distance);
    hatchStyle.addAttribute("draw:rotation", rotation);
    const QString hatchStyleName = m_StyleCollector.insert(hatchStyle, QLatin1String("hatchStyle"));

    odfStyle.addProperty("draw:fill-hatch-name", hatchStyleName);
}

void XFigOdgWriter::writeFont(KoGenStyle &odfStyle, const XFigTextObject *textObject)
{
    const XFigFontData &fontData = textObject->fontData();

    odfStyle.addPropertyPt(QLatin1String("fo:font-size"), fontData.mSize);
    const char *const weight = (fontData.mWeight == QFont::Bold) ? "bold" : (fontData.mWeight == QFont::DemiBold) ? "600" : "normal";
    odfStyle.addProperty(QLatin1String("fo:font-weight"), weight);
    const char *const style = (fontData.mStyle == QFont::StyleItalic) ? "italic" : (fontData.mStyle == QFont::StyleOblique) ? "oblique" : "normal";
    odfStyle.addProperty(QLatin1String("fo:font-style"), style);
    if (!fontData.mFamily.isEmpty())
        odfStyle.addProperty(QLatin1String("fo:font-family"), fontData.mFamily);
}

void XFigOdgWriter::writeParagraphStyle(KoGenStyle &odfStyle, const XFigTextObject *textObject)
{
    const XFigTextAlignment textAlignment = textObject->textAlignment();
    const char *const alignmentName = (textAlignment == XFigTextCenterAligned) ? "center"
        : (textAlignment == XFigTextRightAligned)                              ? "right"
                                                                               :
                                                  /* XFigTextLeftAligned */ "left";
    odfStyle.addProperty(QLatin1String("fo:text-align"), QLatin1String(alignmentName));

    odfStyle.addProperty(QLatin1String("fo:margin"), "0pt");
    odfStyle.addProperty(QLatin1String("fo:padding"), "0pt");
}
