/*
 * 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 "KChartTextLabelCache.h"

#include <cmath>

#include <QApplication>
#include <QImage>
#include <QPainter>
#include <QtDebug>

#ifndef NDEBUG
int HitCount = 0;
int MissCount = 0;
#define INC_HIT_COUNT                                                                                                                                          \
    {                                                                                                                                                          \
        ++HitCount;                                                                                                                                            \
    }
#define INC_MISS_COUNT                                                                                                                                         \
    {                                                                                                                                                          \
        ++MissCount;                                                                                                                                           \
    }
#define DUMP_CACHE_STATS                                                                                                                                       \
    if (HitCount != 0 && MissCount != 0) {                                                                                                                     \
        int total = HitCount + MissCount;                                                                                                                      \
        qreal hitQuote = (1.0 * HitCount) / total;                                                                                                             \
        qDebug() << "PrerenderedLabel dtor: hits/misses/total:" << HitCount << "/" << MissCount << "/" << total << "(" << 100 * hitQuote << "% hits)";         \
    }
#else
#define INC_HIT_COUNT
#define INC_MISS_COUNT
#define DUMP_CACHE_STATS
#endif

PrerenderedElement::PrerenderedElement()
    : m_referencePoint(KChartEnums::PositionNorthWest)
{
}

void PrerenderedElement::setPosition(const QPointF &position)
{ // this does not invalidate the element
    m_position = position;
}

const QPointF &PrerenderedElement::position() const
{
    return m_position;
}

void PrerenderedElement::setReferencePoint(KChartEnums::PositionValue point)
{ // this does not invalidate the element
    m_referencePoint = point;
}

KChartEnums::PositionValue PrerenderedElement::referencePoint() const
{
    return m_referencePoint;
}

PrerenderedLabel::PrerenderedLabel()
    : PrerenderedElement()
    , m_dirty(true)
    , m_font(qApp->font())
    , m_brush(Qt::black)
    , m_pen(Qt::black) // do not use anything invisible
    , m_angle(0.0)
{
}

PrerenderedLabel::~PrerenderedLabel()
{
    DUMP_CACHE_STATS;
}

void PrerenderedLabel::invalidate() const
{
    m_dirty = true;
}

void PrerenderedLabel::setFont(const QFont &font)
{
    m_font = font;
    invalidate();
}

const QFont &PrerenderedLabel::font() const
{
    return m_font;
}

void PrerenderedLabel::setText(const QString &text)
{
    m_text = text;
    invalidate();
}

const QString &PrerenderedLabel::text() const
{
    return m_text;
}

void PrerenderedLabel::setBrush(const QBrush &brush)
{
    m_brush = brush;
    invalidate();
}

const QBrush &PrerenderedLabel::brush() const
{
    return m_brush;
}

void PrerenderedLabel::setAngle(qreal angle)
{
    m_angle = angle;
    invalidate();
}

qreal PrerenderedLabel::angle() const
{
    return m_angle;
}

const QPixmap &PrerenderedLabel::pixmap() const
{
    if (m_dirty) {
        INC_MISS_COUNT;
        paint();
    } else {
        INC_HIT_COUNT;
    }
    return m_pixmap;
}

void PrerenderedLabel::paint() const
{
    // FIXME find a better value using font metrics of text (this
    // requires finding the diameter of the circle formed by rotating
    // the bounding rect around the center):
    const int Width = 1000;
    const int Height = Width;

    QRectF boundingRect;
    const QColor FullTransparent(255, 255, 255, 0);
#ifdef Q_WS_X11
    QImage pixmap(Width, Height, QImage::Format_ARGB32_Premultiplied);
    qWarning() << "PrerenderedLabel::paint: using QImage for prerendered labels "
               << "to work around XRender/Qt4 bug.";
#else
    QPixmap pixmap(Width, Height);
#endif
    // pixmap.fill( FullTransparent );
    {
        static const QPointF Center(0.0, 0.0);
        QPointF textBottomRight;
        QPainter painter(&pixmap);
        painter.setRenderHint(QPainter::TextAntialiasing, true);
        painter.setRenderHint(QPainter::Antialiasing, true);

        // QImage (X11 workaround) does not have fill():
        painter.setPen(FullTransparent);
        painter.setBrush(FullTransparent);
        QPainter::CompositionMode mode = painter.compositionMode();
        painter.setCompositionMode(QPainter::CompositionMode_Clear);
        painter.drawRect(0, 0, Width, Height);
        painter.setCompositionMode(mode);

        QTransform matrix;
        matrix.translate(0.5 * Width, 0.5 * Height);
        matrix.rotate(m_angle);
        painter.setWorldTransform(matrix);

        painter.setPen(m_pen);
        painter.setBrush(m_brush);
        painter.setFont(m_font);
        QRectF container(-0.5 * Width, -0.5 * Height, Width, 0.5 * Height);
        painter.drawText(container, Qt::AlignHCenter | Qt::AlignBottom, m_text, &boundingRect);
        m_referenceBottomLeft = QPointF(boundingRect.bottomLeft().x(), 0.0);
        textBottomRight = QPointF(boundingRect.bottomRight().x(), 0.0);
        m_textAscendVector = boundingRect.topRight() - textBottomRight;
        m_textBaseLineVector = textBottomRight - m_referenceBottomLeft;

        // FIXME translate topright by char height
        boundingRect = matrix.mapRect(boundingRect);
        m_referenceBottomLeft = matrix.map(m_referenceBottomLeft) - boundingRect.topLeft();
        textBottomRight = matrix.map(textBottomRight) - boundingRect.topLeft();
        m_textAscendVector = matrix.map(m_textAscendVector) - matrix.map(Center);
        m_textBaseLineVector = matrix.map(m_textBaseLineVector) - matrix.map(Center);
    }

    m_dirty = false; // now all the calculation vectors are valid

    QPixmap temp(static_cast<int>(boundingRect.width()), static_cast<int>(boundingRect.height()));
    {
        temp.fill(FullTransparent);
        QPainter painter(&temp);
#ifdef Q_WS_X11
        painter.drawImage(QPointF(0.0, 0.0), pixmap, boundingRect);
#else
        painter.drawPixmap(QPointF(0.0, 0.0), pixmap, boundingRect);
#endif
// #define PRERENDEREDLABEL_DEBUG
#ifdef PRERENDEREDLABEL_DEBUG
        painter.setPen(QPen(Qt::red, 2));
        painter.setBrush(Qt::red);
        // paint markers for the reference points
        QList<KChartEnums::PositionValue> positions;
        positions << KChartEnums::PositionCenter << KChartEnums::PositionNorthWest << KChartEnums::PositionNorth << KChartEnums::PositionNorthEast
                  << KChartEnums::PositionEast << KChartEnums::PositionSouthEast << KChartEnums::PositionSouth << KChartEnums::PositionSouthWest
                  << KChartEnums::PositionWest;
        for (KChartEnums::PositionValue position : std::as_const(positions)) { // krazy:exclude=foreach
            static const double Radius = 0.5;
            static const double Diameter = 2 * Radius;

            QPointF point(referencePointLocation(position));
            painter.drawEllipse(QRectF(point - QPointF(Radius, Radius), QSizeF(Diameter, Diameter)));
        }
#endif
    }

    m_pixmap = temp;
}

QPointF PrerenderedLabel::referencePointLocation() const
{
    return referencePointLocation(referencePoint());
}

QPointF PrerenderedLabel::referencePointLocation(KChartEnums::PositionValue position) const
{
    if (m_dirty) {
        INC_MISS_COUNT;
        paint();
    } else {
        INC_HIT_COUNT;
    }

    switch (position) {
    case KChartEnums::PositionCenter:
        return m_referenceBottomLeft + 0.5 * m_textBaseLineVector + 0.5 * m_textAscendVector;
    case KChartEnums::PositionNorthWest:
        return m_referenceBottomLeft + m_textAscendVector;
    case KChartEnums::PositionNorth:
        return m_referenceBottomLeft + 0.5 * m_textBaseLineVector + m_textAscendVector;
    case KChartEnums::PositionNorthEast:
        return m_referenceBottomLeft + m_textBaseLineVector + m_textAscendVector;
    case KChartEnums::PositionEast:
        return m_referenceBottomLeft + 0.5 * m_textAscendVector;
    case KChartEnums::PositionSouthEast:
        return m_referenceBottomLeft + m_textBaseLineVector;
    case KChartEnums::PositionSouth:
        return m_referenceBottomLeft + 0.5 * m_textBaseLineVector;
    case KChartEnums::PositionSouthWest:
        return m_referenceBottomLeft;
    case KChartEnums::PositionWest:
        return m_referenceBottomLeft + m_textBaseLineVector + 0.5 * m_textAscendVector;

    case KChartEnums::PositionUnknown: // intentional fall-through
    case KChartEnums::PositionFloating: // intentional fall-through
        return QPointF();
    }

    return QPointF();
}
