/*
 * SPDX-FileCopyrightText: 2001-2015 Klaralvdalens Datakonsult AB. All rights reserved.
 *
 * This file is part of the KD Chart library.
 *
 * SPDX-License-Identifier: GPL-2.0-or-later
 */

#include "KChartLegend.h"
#include "KChartLayoutItems.h"
#include "KChartLegend_p.h"
#include "KChartPrintingParameters.h"
#include "KTextDocument.h"
#include <KChartAbstractDiagram.h>
#include <KChartDiagramObserver.h>
#include <KChartMarkerAttributes.h>
#include <KChartPalette.h>
#include <KChartTextAttributes.h>

#include <QAbstractTextDocumentLayout>
#include <QFont>
#include <QGridLayout>
#include <QLabel>
#include <QPainter>
#include <QTextCharFormat>
#include <QTextCursor>
#include <QTextDocumentFragment>
#include <QTextTableCell>
#include <QTimer>
#include <QtDebug>

using namespace KChart;

Legend::Private::Private()
    : referenceArea(nullptr)
    , position(Position::East)
    , alignment(Qt::AlignCenter)
    , textAlignment(Qt::AlignCenter)
    , relativePosition(RelativePosition())
    , orientation(Qt::Vertical)
    , order(Qt::AscendingOrder)
    , showLines(false)
    , titleText(QObject::tr("Legend"))
    , spacing(1)
    , useAutomaticMarkerSize(true)
    , legendStyle(MarkersOnly)
{
    // By default we specify a simple, hard point as the 'relative' position's ref. point,
    // since we can not be sure that there will be any parent specified for the legend.
    relativePosition.setReferencePoints(PositionPoints(QPointF(0.0, 0.0)));
    relativePosition.setReferencePosition(Position::NorthWest);
    relativePosition.setAlignment(Qt::AlignTop | Qt::AlignLeft);
    relativePosition.setHorizontalPadding(Measure(4.0, KChartEnums::MeasureCalculationModeAbsolute));
    relativePosition.setVerticalPadding(Measure(4.0, KChartEnums::MeasureCalculationModeAbsolute));
}

Legend::Private::~Private()
{
    // this block left empty intentionally
}

#define d d_func()

Legend::Legend(QWidget *parent)
    : AbstractAreaWidget(new Private(), parent)
{
    d->referenceArea = parent;
    init();
}

Legend::Legend(AbstractDiagram *diagram, QWidget *parent)
    : AbstractAreaWidget(new Private(), parent)
{
    d->referenceArea = parent;
    init();
    setDiagram(diagram);
}

Legend::~Legend()
{
    Q_EMIT destroyedLegend(this);
}

void Legend::init()
{
    setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);

    d->layout = new QGridLayout(this);
    d->layout->setContentsMargins(2, 2, 2, 2);
    d->layout->setSpacing(d->spacing);

    const Measure normalFontSizeTitle(12, KChartEnums::MeasureCalculationModeAbsolute);
    const Measure normalFontSizeLabels(10, KChartEnums::MeasureCalculationModeAbsolute);
    const Measure minimalFontSize(4, KChartEnums::MeasureCalculationModeAbsolute);

    TextAttributes textAttrs;
    textAttrs.setPen(QPen(Qt::black));
    textAttrs.setFont(QFont(QLatin1String("helvetica"), 10, QFont::Normal, false));
    textAttrs.setFontSize(normalFontSizeLabels);
    textAttrs.setMinimalFontSize(minimalFontSize);
    setTextAttributes(textAttrs);

    TextAttributes titleTextAttrs;
    titleTextAttrs.setPen(QPen(Qt::black));
    titleTextAttrs.setFont(QFont(QLatin1String("helvetica"), 12, QFont::Bold, false));
    titleTextAttrs.setFontSize(normalFontSizeTitle);
    titleTextAttrs.setMinimalFontSize(minimalFontSize);
    setTitleTextAttributes(titleTextAttrs);

    FrameAttributes frameAttrs;
    frameAttrs.setVisible(true);
    frameAttrs.setPen(QPen(Qt::black));
    frameAttrs.setPadding(1);
    setFrameAttributes(frameAttrs);

    d->position = Position::NorthEast;
    d->alignment = Qt::AlignCenter;
}

QSize Legend::minimumSizeHint() const
{
    return sizeHint();
}

// #define DEBUG_LEGEND_PAINT

QSize Legend::sizeHint() const
{
#ifdef DEBUG_LEGEND_PAINT
    qDebug() << "Legend::sizeHint() started";
#endif
    for (AbstractLayoutItem *paintItem : d->paintItems) {
        paintItem->sizeHint();
    }
    return AbstractAreaWidget::sizeHint();
}

void Legend::needSizeHint()
{
    buildLegend();
}

void Legend::resizeLayout(const QSize &size)
{
#ifdef DEBUG_LEGEND_PAINT
    qDebug() << "Legend::resizeLayout started";
#endif
    if (d->layout) {
        d->reflowHDatasetItems(this);
        d->layout->setGeometry(QRect(QPoint(0, 0), size));
        activateTheLayout();
    }
#ifdef DEBUG_LEGEND_PAINT
    qDebug() << "Legend::resizeLayout done";
#endif
}

void Legend::activateTheLayout()
{
    if (d->layout && d->layout->parent()) {
        d->layout->activate();
    }
}

void Legend::setLegendStyle(LegendStyle style)
{
    if (d->legendStyle == style) {
        return;
    }
    d->legendStyle = style;
    setNeedRebuild();
}

Legend::LegendStyle Legend::legendStyle() const
{
    return d->legendStyle;
}

Legend *Legend::clone() const
{
    Legend *legend = new Legend(new Private(*d), nullptr);
    legend->setTextAttributes(textAttributes());
    legend->setTitleTextAttributes(titleTextAttributes());
    legend->setFrameAttributes(frameAttributes());
    legend->setUseAutomaticMarkerSize(useAutomaticMarkerSize());
    legend->setPosition(position());
    legend->setAlignment(alignment());
    legend->setTextAlignment(textAlignment());
    legend->setLegendStyle(legendStyle());
    return legend;
}

bool Legend::compare(const Legend *other) const
{
    if (other == this) {
        return true;
    }
    if (!other) {
        return false;
    }

    return (AbstractAreaBase::compare(other)) && (isVisible() == other->isVisible()) && (position() == other->position()) && (alignment() == other->alignment())
        && (textAlignment() == other->textAlignment()) && (floatingPosition() == other->floatingPosition()) && (orientation() == other->orientation())
        && (showLines() == other->showLines()) && (texts() == other->texts()) && (brushes() == other->brushes()) && (pens() == other->pens())
        && (markerAttributes() == other->markerAttributes()) && (useAutomaticMarkerSize() == other->useAutomaticMarkerSize())
        && (textAttributes() == other->textAttributes()) && (titleText() == other->titleText()) && (titleTextAttributes() == other->titleTextAttributes())
        && (spacing() == other->spacing()) && (legendStyle() == other->legendStyle());
}

void Legend::paint(QPainter *painter)
{
#ifdef DEBUG_LEGEND_PAINT
    qDebug() << "entering Legend::paint( QPainter* painter )";
#endif
    if (!diagram()) {
        return;
    }

    activateTheLayout();

    for (AbstractLayoutItem *paintItem : std::as_const(d->paintItems)) {
        paintItem->paint(painter);
    }

#ifdef DEBUG_LEGEND_PAINT
    qDebug() << "leaving Legend::paint( QPainter* painter )";
#endif
}

void Legend::paint(QPainter *painter, const QRect &rect)
{
    if (rect.isEmpty()) {
        return;
    }
    // set up the contents of the widget so we get a useful geometry
    QPaintDevice *prevDevice = GlobalMeasureScaling::paintDevice();
    GlobalMeasureScaling::setPaintDevice(painter->device());

    const QRect oldGeometry(geometry());
    const QRect newGeo(QPoint(0, 0), rect.size());
    if (oldGeometry != newGeo) {
        setGeometry(newGeo);
        needSizeHint();
    }
    painter->translate(rect.left(), rect.top());
    paintAll(*painter);
    painter->translate(-rect.left(), -rect.top());

    if (oldGeometry != newGeo) {
        setGeometry(oldGeometry);
    }
    GlobalMeasureScaling::setPaintDevice(prevDevice);
}

uint Legend::datasetCount() const
{
    int modelLabelsCount = 0;
    for (DiagramObserver *observer : d->observers) {
        AbstractDiagram *diagram = observer->diagram();
        Q_ASSERT(diagram->datasetLabels().count() == diagram->datasetBrushes().count());
        modelLabelsCount += diagram->datasetLabels().count();
    }
    return modelLabelsCount;
}

void Legend::setReferenceArea(const QWidget *area)
{
    if (area == d->referenceArea) {
        return;
    }
    d->referenceArea = area;
    setNeedRebuild();
}

const QWidget *Legend::referenceArea() const
{
    return d->referenceArea ? d->referenceArea : qobject_cast<const QWidget *>(parent());
}

AbstractDiagram *Legend::diagram() const
{
    if (d->observers.isEmpty()) {
        return nullptr;
    }
    return d->observers.first()->diagram();
}

DiagramList Legend::diagrams() const
{
    DiagramList list;
    for (int i = 0; i < d->observers.size(); ++i) {
        list << d->observers.at(i)->diagram();
    }
    return list;
}

ConstDiagramList Legend::constDiagrams() const
{
    ConstDiagramList list;
    for (int i = 0; i < d->observers.size(); ++i) {
        list << d->observers.at(i)->diagram();
    }
    return list;
}

void Legend::addDiagram(AbstractDiagram *newDiagram)
{
    if (newDiagram) {
        DiagramObserver *observer = new DiagramObserver(newDiagram, this);

        DiagramObserver *oldObs = d->findObserverForDiagram(newDiagram);
        if (oldObs) {
            delete oldObs;
            d->observers[d->observers.indexOf(oldObs)] = observer;
        } else {
            d->observers.append(observer);
        }
        connect(observer, SIGNAL(diagramAboutToBeDestroyed(KChart::AbstractDiagram *)), SLOT(resetDiagram(KChart::AbstractDiagram *)));
        connect(observer, SIGNAL(diagramDataChanged(KChart::AbstractDiagram *)), SLOT(setNeedRebuild()));
        connect(observer, SIGNAL(diagramDataHidden(KChart::AbstractDiagram *)), SLOT(setNeedRebuild()));
        connect(observer, SIGNAL(diagramAttributesChanged(KChart::AbstractDiagram *)), SLOT(setNeedRebuild()));
        setNeedRebuild();
    }
}

void Legend::removeDiagram(AbstractDiagram *oldDiagram)
{
    int datasetBrushOffset = 0;
    QList<AbstractDiagram *> diagrams = this->diagrams();
    for (int i = 0; i < diagrams.count(); i++) {
        if (diagrams.at(i) == oldDiagram) {
            for (int i = 0; i < oldDiagram->datasetBrushes().count(); i++) {
                d->brushes.remove(datasetBrushOffset + i);
                d->texts.remove(datasetBrushOffset + i);
            }
            for (int i = 0; i < oldDiagram->datasetPens().count(); i++) {
                d->pens.remove(datasetBrushOffset + i);
            }
            break;
        }
        datasetBrushOffset += diagrams.at(i)->datasetBrushes().count();
    }

    if (oldDiagram) {
        DiagramObserver *oldObs = d->findObserverForDiagram(oldDiagram);
        if (oldObs) {
            delete oldObs;
            d->observers.removeAt(d->observers.indexOf(oldObs));
        }
        setNeedRebuild();
    }
}

void Legend::removeDiagrams()
{
    // removeDiagram() may change the d->observers list. So, build up the list of
    // diagrams to remove first and then remove them one by one.
    QList<AbstractDiagram *> diagrams;
    for (int i = 0; i < d->observers.size(); ++i) {
        diagrams.append(d->observers.at(i)->diagram());
    }
    for (int i = 0; i < diagrams.count(); ++i) {
        removeDiagram(diagrams[i]);
    }
}

void Legend::replaceDiagram(AbstractDiagram *newDiagram, AbstractDiagram *oldDiagram)
{
    AbstractDiagram *old = oldDiagram;
    if (!d->observers.isEmpty() && !old) {
        old = d->observers.first()->diagram();
        if (!old) {
            d->observers.removeFirst(); // first entry had a 0 diagram
        }
    }
    if (old) {
        removeDiagram(old);
    }
    if (newDiagram) {
        addDiagram(newDiagram);
    }
}

uint Legend::dataSetOffset(AbstractDiagram *diagram)
{
    uint offset = 0;

    for (int i = 0; i < d->observers.count(); ++i) {
        if (d->observers.at(i)->diagram() == diagram) {
            return offset;
        }
        AbstractDiagram *diagram = d->observers.at(i)->diagram();
        if (!diagram->model()) {
            continue;
        }
        offset = offset + diagram->model()->columnCount();
    }

    return offset;
}

void Legend::setDiagram(AbstractDiagram *newDiagram)
{
    replaceDiagram(newDiagram);
}

void Legend::resetDiagram(AbstractDiagram *oldDiagram)
{
    removeDiagram(oldDiagram);
}

void Legend::setVisible(bool visible)
{
    // do NOT bail out if visible == isVisible(), because the return value of isVisible() also depends
    // on the visibility of the parent.
    QWidget::setVisible(visible);
    emitPositionChanged();
}

void Legend::setNeedRebuild()
{
    buildLegend();
    sizeHint();
}

void Legend::setPosition(Position position)
{
    if (d->position == position) {
        return;
    }
    d->position = position;
    emitPositionChanged();
}

void Legend::emitPositionChanged()
{
    Q_EMIT positionChanged(this);
    Q_EMIT propertiesChanged();
}

Position Legend::position() const
{
    return d->position;
}

void Legend::setAlignment(Qt::Alignment alignment)
{
    if (d->alignment == alignment) {
        return;
    }
    d->alignment = alignment;
    emitPositionChanged();
}

Qt::Alignment Legend::alignment() const
{
    return d->alignment;
}

void Legend::setTextAlignment(Qt::Alignment alignment)
{
    if (d->textAlignment == alignment) {
        return;
    }
    d->textAlignment = alignment;
    emitPositionChanged();
}

Qt::Alignment Legend::textAlignment() const
{
    return d->textAlignment;
}

void Legend::setLegendSymbolAlignment(Qt::Alignment alignment)
{
    if (d->legendLineSymbolAlignment == alignment) {
        return;
    }
    d->legendLineSymbolAlignment = alignment;
    emitPositionChanged();
}

Qt::Alignment Legend::legendSymbolAlignment() const
{
    return d->legendLineSymbolAlignment;
}

void Legend::setFloatingPosition(const RelativePosition &relativePosition)
{
    d->position = Position::Floating;
    if (d->relativePosition != relativePosition) {
        d->relativePosition = relativePosition;
        emitPositionChanged();
    }
}

const RelativePosition Legend::floatingPosition() const
{
    return d->relativePosition;
}

void Legend::setOrientation(Qt::Orientation orientation)
{
    if (d->orientation == orientation) {
        return;
    }
    d->orientation = orientation;
    setNeedRebuild();
    emitPositionChanged();
}

Qt::Orientation Legend::orientation() const
{
    return d->orientation;
}

void Legend::setSortOrder(Qt::SortOrder order)
{
    if (d->order == order) {
        return;
    }
    d->order = order;
    setNeedRebuild();
    emitPositionChanged();
}

Qt::SortOrder Legend::sortOrder() const
{
    return d->order;
}

void Legend::setShowLines(bool legendShowLines)
{
    if (d->showLines == legendShowLines) {
        return;
    }
    d->showLines = legendShowLines;
    setNeedRebuild();
    emitPositionChanged();
}

bool Legend::showLines() const
{
    return d->showLines;
}

void Legend::setUseAutomaticMarkerSize(bool useAutomaticMarkerSize)
{
    d->useAutomaticMarkerSize = useAutomaticMarkerSize;
    setNeedRebuild();
    emitPositionChanged();
}

bool Legend::useAutomaticMarkerSize() const
{
    return d->useAutomaticMarkerSize;
}

void Legend::resetTexts()
{
    if (!d->texts.count()) {
        return;
    }
    d->texts.clear();
    setNeedRebuild();
}

void Legend::setText(uint dataset, const QString &text)
{
    if (d->texts[dataset] == text) {
        return;
    }
    d->texts[dataset] = text;
    setNeedRebuild();
}

QString Legend::text(uint dataset) const
{
    if (d->texts.find(dataset) != d->texts.end()) {
        return d->texts[dataset];
    } else {
        return d->modelLabels[dataset];
    }
}

const QMap<uint, QString> Legend::texts() const
{
    return d->texts;
}

void Legend::setColor(uint dataset, const QColor &color)
{
    if (d->brushes[dataset] != color) {
        d->brushes[dataset] = color;
        setNeedRebuild();
        update();
    }
}

void Legend::setBrush(uint dataset, const QBrush &brush)
{
    if (d->brushes[dataset] != brush) {
        d->brushes[dataset] = brush;
        setNeedRebuild();
        update();
    }
}

QBrush Legend::brush(uint dataset) const
{
    if (d->brushes.contains(dataset)) {
        return d->brushes[dataset];
    } else {
        return d->modelBrushes[dataset];
    }
}

const QMap<uint, QBrush> Legend::brushes() const
{
    return d->brushes;
}

void Legend::setBrushesFromDiagram(AbstractDiagram *diagram)
{
    bool changed = false;
    QList<QBrush> datasetBrushes = diagram->datasetBrushes();
    for (int i = 0; i < datasetBrushes.count(); i++) {
        if (d->brushes[i] != datasetBrushes[i]) {
            d->brushes[i] = datasetBrushes[i];
            changed = true;
        }
    }
    if (changed) {
        setNeedRebuild();
        update();
    }
}

void Legend::setPen(uint dataset, const QPen &pen)
{
    if (d->pens[dataset] == pen) {
        return;
    }
    d->pens[dataset] = pen;
    setNeedRebuild();
    update();
}

QPen Legend::pen(uint dataset) const
{
    if (d->pens.find(dataset) != d->pens.end()) {
        return d->pens[dataset];
    } else {
        return d->modelPens[dataset];
    }
}

const QMap<uint, QPen> Legend::pens() const
{
    return d->pens;
}

void Legend::setMarkerAttributes(uint dataset, const MarkerAttributes &markerAttributes)
{
    if (d->markerAttributes[dataset] == markerAttributes) {
        return;
    }
    d->markerAttributes[dataset] = markerAttributes;
    setNeedRebuild();
    update();
}

MarkerAttributes Legend::markerAttributes(uint dataset) const
{
    if (d->markerAttributes.find(dataset) != d->markerAttributes.end()) {
        return d->markerAttributes[dataset];
    } else if (static_cast<uint>(d->modelMarkers.count()) > dataset) {
        return d->modelMarkers[dataset];
    } else {
        return MarkerAttributes();
    }
}

const QMap<uint, MarkerAttributes> Legend::markerAttributes() const
{
    return d->markerAttributes;
}

void Legend::setTextAttributes(const TextAttributes &a)
{
    if (d->textAttributes == a) {
        return;
    }
    d->textAttributes = a;
    setNeedRebuild();
}

TextAttributes Legend::textAttributes() const
{
    return d->textAttributes;
}

void Legend::setTitleText(const QString &text)
{
    if (d->titleText == text) {
        return;
    }
    d->titleText = text;
    setNeedRebuild();
}

QString Legend::titleText() const
{
    return d->titleText;
}

void Legend::setTitleTextAttributes(const TextAttributes &a)
{
    if (d->titleTextAttributes == a) {
        return;
    }
    d->titleTextAttributes = a;
    setNeedRebuild();
}

TextAttributes Legend::titleTextAttributes() const
{
    return d->titleTextAttributes;
}

void Legend::forceRebuild()
{
#ifdef DEBUG_LEGEND_PAINT
    qDebug() << "entering Legend::forceRebuild()";
#endif
    buildLegend();
#ifdef DEBUG_LEGEND_PAINT
    qDebug() << "leaving Legend::forceRebuild()";
#endif
}

void Legend::setSpacing(uint space)
{
    if (d->spacing == space && d->layout->spacing() == int(space)) {
        return;
    }
    d->spacing = space;
    d->layout->setSpacing(space);
    setNeedRebuild();
}

uint Legend::spacing() const
{
    return d->spacing;
}

void Legend::setDefaultColors()
{
    Palette pal = Palette::defaultPalette();
    for (int i = 0; i < pal.size(); i++) {
        setBrush(i, pal.getBrush(i));
    }
}

void Legend::setRainbowColors()
{
    Palette pal = Palette::rainbowPalette();
    for (int i = 0; i < pal.size(); i++) {
        setBrush(i, pal.getBrush(i));
    }
}

void Legend::setSubduedColors(bool ordered)
{
    Palette pal = Palette::subduedPalette();
    if (ordered) {
        for (int i = 0; i < pal.size(); i++) {
            setBrush(i, pal.getBrush(i));
        }
    } else {
        static const int s_subduedColorsCount = 18;
        Q_ASSERT(pal.size() >= s_subduedColorsCount);
        static const int order[s_subduedColorsCount] = {0, 5, 10, 15, 2, 7, 12, 17, 4, 9, 14, 1, 6, 11, 16, 3, 8, 13};
        for (int i = 0; i < s_subduedColorsCount; i++) {
            setBrush(i, pal.getBrush(order[i]));
        }
    }
}

void Legend::resizeEvent(QResizeEvent *event)
{
    Q_UNUSED(event);
#ifdef DEBUG_LEGEND_PAINT
    qDebug() << "Legend::resizeEvent() called";
#endif
    forceRebuild();
    sizeHint();
    QTimer::singleShot(0, this, SLOT(emitPositionChanged()));
}

void Legend::Private::fetchPaintOptions(Legend *q)
{
    modelLabels.clear();
    modelBrushes.clear();
    modelPens.clear();
    modelMarkers.clear();
    // retrieve the diagrams' settings for all non-hidden datasets
    for (int i = 0; i < observers.size(); ++i) {
        const AbstractDiagram *diagram = observers.at(i)->diagram();
        if (!diagram) {
            continue;
        }
        const QStringList diagramLabels = diagram->datasetLabels();
        const QList<QBrush> diagramBrushes = diagram->datasetBrushes();
        const QList<QPen> diagramPens = diagram->datasetPens();
        const QList<MarkerAttributes> diagramMarkers = diagram->datasetMarkers();

        const bool ascend = q->sortOrder() == Qt::AscendingOrder;
        int dataset = ascend ? 0 : diagramLabels.count() - 1;
        const int end = ascend ? diagramLabels.count() : -1;
        for (; dataset != end; dataset += ascend ? 1 : -1) {
            if (diagram->isHidden(dataset) || q->datasetIsHidden(dataset)) {
                continue;
            }
            modelLabels += diagramLabels[dataset];
            modelBrushes += diagramBrushes[dataset];
            modelPens += diagramPens[dataset];
            modelMarkers += diagramMarkers[dataset];
        }
    }

    Q_ASSERT(modelLabels.count() == modelBrushes.count());
}

QSizeF Legend::Private::markerSize(Legend *q, int dataset, qreal fontHeight) const
{
    QSizeF suppliedSize = q->markerAttributes(dataset).markerSize();
    if (q->useAutomaticMarkerSize() || !suppliedSize.isValid()) {
        return QSizeF(fontHeight, fontHeight);
    } else {
        return suppliedSize;
    }
}

QSizeF Legend::Private::maxMarkerSize(Legend *q, qreal fontHeight) const
{
    QSizeF ret(1.0, 1.0);
    if (q->legendStyle() != LinesOnly) {
        for (int dataset = 0; dataset < modelLabels.count(); ++dataset) {
            ret = ret.expandedTo(markerSize(q, dataset, fontHeight));
        }
    }
    return ret;
}

HDatasetItem::HDatasetItem()
    : markerLine(nullptr)
    , label(nullptr)
    , separatorLine(nullptr)
    , spacer(nullptr)
{
}

static void updateToplevelLayout(QWidget *w)
{
    while (w) {
        if (w->isWindow()) {
            // The null check has proved necessary during destruction of the Legend / Chart
            if (w->layout()) {
                w->layout()->update();
            }
            break;
        } else {
            w = qobject_cast<QWidget *>(w->parent());
            Q_ASSERT(w);
        }
    }
}

void Legend::buildLegend()
{
    /* Grid layout partitioning (horizontal orientation): row zero is the title, row one the divider
       line between title and dataset items, row two for each item: line, marker, text label and separator
       line in that order.
       In a vertically oriented legend, row pairs (2, 3), ... contain a possible separator line (first row)
       and (second row) line, marker, text label each. */
    d->destroyOldLayout();

    if (orientation() == Qt::Vertical) {
        d->layout->setColumnStretch(6, 1);
    } else {
        d->layout->setColumnStretch(6, 0);
    }

    d->fetchPaintOptions(this);

    const KChartEnums::MeasureOrientation measureOrientation =
        orientation() == Qt::Vertical ? KChartEnums::MeasureOrientationMinimum : KChartEnums::MeasureOrientationHorizontal;

    // legend caption
    if (!titleText().isEmpty() && titleTextAttributes().isVisible()) {
        TextLayoutItem *titleItem = new TextLayoutItem(titleText(), titleTextAttributes(), referenceArea(), measureOrientation, d->textAlignment);
        titleItem->setParentWidget(this);

        d->paintItems << titleItem;
        d->layout->addItem(titleItem, 0, 0, 1, 5, Qt::AlignCenter);

        // The line between the title and the legend items, if any.
        if (showLines() && d->modelLabels.count()) {
            HorizontalLineLayoutItem *lineItem = new HorizontalLineLayoutItem;
            d->paintItems << lineItem;
            d->layout->addItem(lineItem, 1, 0, 1, 5, Qt::AlignCenter);
        }
    }

    qreal fontHeight = textAttributes().calculatedFontSize(referenceArea(), measureOrientation);
    {
        QFont tmpFont = textAttributes().font();
        tmpFont.setPointSizeF(fontHeight);
        if (GlobalMeasureScaling::paintDevice()) {
            fontHeight = QFontMetricsF(tmpFont, GlobalMeasureScaling::paintDevice()).height();
        } else {
            fontHeight = QFontMetricsF(tmpFont).height();
        }
    }

    const QSizeF maxMarkerSize = d->maxMarkerSize(this, fontHeight);

    // If we show a marker on a line, we paint it after 8 pixels
    // of the line have been painted. This allows to see the line style
    // at the right side of the marker without the line needing to
    // be too long.
    // (having the marker in the middle of the line would require longer lines)
    const int lineLengthLeftOfMarker = 8;

    int maxLineLength = 18;
    {
        bool hasComplexPenStyle = false;
        for (int dataset = 0; dataset < d->modelLabels.count(); ++dataset) {
            const QPen pn = pen(dataset);
            const Qt::PenStyle ps = pn.style();
            if (ps != Qt::NoPen) {
                maxLineLength = qMin(pn.width() * 18, maxLineLength);
                if (ps != Qt::SolidLine) {
                    hasComplexPenStyle = true;
                }
            }
        }
        if (legendStyle() != LinesOnly) {
            if (hasComplexPenStyle)
                maxLineLength += lineLengthLeftOfMarker;
            maxLineLength += int(maxMarkerSize.width());
        }
    }

    // for all datasets: add (line)marker items and text items to the layout;
    // actual layout happens in flowHDatasetItems() for horizontal layout, here for vertical
    for (int dataset = 0; dataset < d->modelLabels.count(); ++dataset) {
        const int vLayoutRow = 2 + dataset * 2;
        HDatasetItem dsItem;

        // It is possible to set the marker brush through markerAttributes as well as
        // the dataset brush set in the diagram - the markerAttributes have higher precedence.
        MarkerAttributes markerAttrs = markerAttributes(dataset);
        markerAttrs.setMarkerSize(d->markerSize(this, dataset, fontHeight));
        const QBrush markerBrush = markerAttrs.markerColor().isValid() ? QBrush(markerAttrs.markerColor()) : brush(dataset);

        switch (legendStyle()) {
        case MarkersOnly:
            dsItem.markerLine = new MarkerLayoutItem(diagram(), markerAttrs, markerBrush, markerAttrs.pen(), Qt::AlignLeft | Qt::AlignVCenter);
            break;
        case LinesOnly:
            dsItem.markerLine = new LineLayoutItem(diagram(), maxLineLength, pen(dataset), d->legendLineSymbolAlignment, Qt::AlignCenter);
            break;
        case MarkersAndLines:
            dsItem.markerLine = new LineWithMarkerLayoutItem(diagram(),
                                                             maxLineLength,
                                                             pen(dataset),
                                                             lineLengthLeftOfMarker,
                                                             markerAttrs,
                                                             markerBrush,
                                                             markerAttrs.pen(),
                                                             Qt::AlignCenter);
            break;
        default:
            Q_ASSERT(false);
        }

        dsItem.label = new TextLayoutItem(text(dataset), textAttributes(), referenceArea(), measureOrientation, d->textAlignment);
        dsItem.label->setParentWidget(this);

        // horizontal layout is deferred to flowDatasetItems()

        if (orientation() == Qt::Horizontal) {
            d->hLayoutDatasets << dsItem;
            continue;
        }

        // (actual) vertical layout here
        if (dsItem.markerLine) {
            d->layout->addItem(dsItem.markerLine, vLayoutRow, 1, 1, 1, Qt::AlignCenter);
            d->paintItems << dsItem.markerLine;
        }
        d->layout->addItem(dsItem.label, vLayoutRow, 3, 1, 1, Qt::AlignLeft | Qt::AlignVCenter);
        d->paintItems << dsItem.label;

        // horizontal separator line, only between items
        if (showLines() && dataset != d->modelLabels.count() - 1) {
            HorizontalLineLayoutItem *lineItem = new HorizontalLineLayoutItem;
            d->layout->addItem(lineItem, vLayoutRow + 1, 0, 1, 5, Qt::AlignCenter);
            d->paintItems << lineItem;
        }
    }

    if (orientation() == Qt::Horizontal) {
        d->flowHDatasetItems(this);
    }

    // vertical line (only in vertical mode)
    if (orientation() == Qt::Vertical && showLines() && d->modelLabels.count()) {
        VerticalLineLayoutItem *lineItem = new VerticalLineLayoutItem;
        d->paintItems << lineItem;
        d->layout->addItem(lineItem, 2, 2, d->modelLabels.count() * 2, 1);
    }

    updateToplevelLayout(this);

    Q_EMIT propertiesChanged();
#ifdef DEBUG_LEGEND_PAINT
    qDebug() << "leaving Legend::buildLegend()";
#endif
}

int HDatasetItem::height() const
{
    return qMax(markerLine->sizeHint().height(), label->sizeHint().height());
}

void Legend::Private::reflowHDatasetItems(Legend *q)
{
    if (hLayoutDatasets.isEmpty()) {
        return;
    }

    paintItems.clear();
    // Dissolve exactly the QHBoxLayout(s) created as "currentLine" in flowHDatasetItems - don't remove the
    // caption and line under the caption! Those are easily identified because they aren't QLayouts.
    for (int i = layout->count() - 1; i >= 0; i--) {
        QLayoutItem *const item = layout->itemAt(i);
        QLayout *const hbox = item->layout();
        if (!hbox) {
            AbstractLayoutItem *alItem = dynamic_cast<AbstractLayoutItem *>(item);
            Q_ASSERT(alItem);
            paintItems << alItem;
            continue;
        }
        Q_ASSERT(dynamic_cast<QHBoxLayout *>(hbox));
        layout->takeAt(i);
        // detach children so they aren't deleted with the parent
        for (int j = hbox->count() - 1; j >= 0; j--) {
            hbox->takeAt(j);
        }
        delete hbox;
    }

    flowHDatasetItems(q);
}

// this works pretty much like flow layout for text, and it is only applicable to dataset items
// laid out horizontally
void Legend::Private::flowHDatasetItems(Legend *q)
{
    const int separatorLineWidth = 3; // hardcoded in VerticalLineLayoutItem::sizeHint()

    const int allowedWidth = q->areaGeometry().width();
    QHBoxLayout *currentLine = new QHBoxLayout;
    int columnSpan = 5;
    int mainLayoutColumn = 0;
    int row = 0;
    if (!titleText.isEmpty() && titleTextAttributes.isVisible()) {
        ++row;
        if (q->showLines()) {
            ++row;
        }
    }
    layout->addItem(currentLine,
                    row,
                    mainLayoutColumn,
                    /*rowSpan*/ 1,
                    columnSpan,
                    Qt::AlignLeft | Qt::AlignVCenter);
    mainLayoutColumn += columnSpan;

    for (int dataset = 0; dataset < hLayoutDatasets.size(); dataset++) {
        HDatasetItem *hdsItem = &hLayoutDatasets[dataset];

        bool spacerUsed = false;
        bool separatorUsed = false;
        if (!currentLine->isEmpty()) {
            const int separatorWidth = (q->showLines() ? separatorLineWidth : 0) + q->spacing();
            const int payloadWidth = hdsItem->markerLine->sizeHint().width() + hdsItem->label->sizeHint().width();
            if (currentLine->sizeHint().width() + separatorWidth + payloadWidth > allowedWidth) {
                // too wide, "line break"
#ifdef DEBUG_LEGEND_PAINT
                qDebug() << Q_FUNC_INFO << "break" << mainLayoutColumn << currentLine->sizeHint().width()
                         << currentLine->sizeHint().width() + separatorWidth + payloadWidth << allowedWidth;
#endif
                currentLine = new QHBoxLayout;
                layout->addItem(currentLine,
                                row,
                                mainLayoutColumn,
                                /*rowSpan*/ 1,
                                columnSpan,
                                Qt::AlignLeft | Qt::AlignVCenter);
                mainLayoutColumn += columnSpan;
            } else {
                // > 1 dataset item in line, put spacing and maybe a separator between them
                if (!hdsItem->spacer) {
                    hdsItem->spacer = new QSpacerItem(q->spacing(), 1);
                }
                currentLine->addItem(hdsItem->spacer);
                spacerUsed = true;

                if (q->showLines()) {
                    if (!hdsItem->separatorLine) {
                        hdsItem->separatorLine = new VerticalLineLayoutItem;
                    }
                    paintItems << hdsItem->separatorLine;
                    currentLine->addItem(hdsItem->separatorLine);
                    separatorUsed = true;
                }
            }
        }
        // those have no parents in the current layout, so they wouldn't get cleaned up otherwise
        if (!spacerUsed) {
            delete hdsItem->spacer;
            hdsItem->spacer = nullptr;
        }
        if (!separatorUsed) {
            delete hdsItem->separatorLine;
            hdsItem->separatorLine = nullptr;
        }

        currentLine->addItem(hdsItem->markerLine);
        paintItems << hdsItem->markerLine;
        currentLine->addItem(hdsItem->label);
        paintItems << hdsItem->label;
    }
}

bool Legend::hasHeightForWidth() const
{
    // this is better than using orientation() because, for layout purposes, we're not height-for-width
    // *yet* before buildLegend() has been called, and the layout logic might get upset if we say
    // something that will only be true in the future
    return !d->hLayoutDatasets.isEmpty();
}

int Legend::heightForWidth(int width) const
{
    if (d->hLayoutDatasets.isEmpty()) {
        return -1;
    }

    int ret = 0;
    // space for caption and line under caption (if any)
    for (int i = 0; i < 2; i++) {
        if (QLayoutItem *item = d->layout->itemAtPosition(i, 0)) {
            ret += item->sizeHint().height();
        }
    }
    const int separatorLineWidth = 3; // ### hardcoded in VerticalLineLayoutItem::sizeHint()

    int currentLineWidth = 0;
    int currentLineHeight = 0;
    for (const HDatasetItem &hdsItem : d->hLayoutDatasets) {
        const int payloadWidth = hdsItem.markerLine->sizeHint().width() + hdsItem.label->sizeHint().width();
        if (!currentLineWidth) {
            // first iteration
            currentLineWidth = payloadWidth;
        } else {
            const int separatorWidth = (showLines() ? separatorLineWidth : 0) + spacing();
            currentLineWidth += separatorWidth + payloadWidth;
            if (currentLineWidth > width) {
                // too wide, "line break"
#ifdef DEBUG_LEGEND_PAINT
                qDebug() << Q_FUNC_INFO << "heightForWidth break" << currentLineWidth << currentLineWidth + separatorWidth + payloadWidth << width;
#endif
                ret += currentLineHeight + spacing();
                currentLineWidth = payloadWidth;
                currentLineHeight = 0;
            }
        }
        currentLineHeight = qMax(currentLineHeight, hdsItem.height());
    }
    ret += currentLineHeight; // one less spacings than lines
    return ret;
}

void Legend::Private::destroyOldLayout()
{
    // in the horizontal layout case, the QHBoxLayout destructor also deletes child layout items
    // (it isn't documented that QLayoutItems delete their children)
    for (int i = layout->count() - 1; i >= 0; i--) {
        delete layout->takeAt(i);
    }
    Q_ASSERT(!layout->count());
    hLayoutDatasets.clear();
    paintItems.clear();
}

void Legend::setHiddenDatasets(const QList<uint> hiddenDatasets)
{
    d->hiddenDatasets = hiddenDatasets;
}

const QList<uint> Legend::hiddenDatasets() const
{
    return d->hiddenDatasets;
}

void Legend::setDatasetHidden(uint dataset, bool hidden)
{
    if (hidden && !d->hiddenDatasets.contains(dataset)) {
        d->hiddenDatasets.append(dataset);
    } else if (!hidden && d->hiddenDatasets.contains(dataset)) {
        d->hiddenDatasets.removeAll(dataset);
    }
}

bool Legend::datasetIsHidden(uint dataset) const
{
    return d->hiddenDatasets.contains(dataset);
}
