// Copyright (C) 2016 Klaralvdalens Datakonsult AB (KDAB).
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only

#include <QtGui/qrawfont.h>
#include <QtGui/qglyphrun.h>
#include <QtGui/private/qrawfont_p.h>

#include "qdistancefieldglyphcache_p.h"
#include "qtextureatlas_p.h"

#include <QtGui/qpainterpath.h>
#include <QtGui/qfont.h>
#include <QtGui/qpainterpath.h>
#include <QtGui/private/qdistancefield_p.h>
#include <Qt3DCore/private/qnode_p.h>
#include <Qt3DExtras/private/qtextureatlas_p.h>

QT_BEGIN_NAMESPACE

#define DEFAULT_IMAGE_PADDING 1


namespace Qt3DExtras {

using namespace Qt3DCore;

// ref-count glyphs and keep track of where they are stored
class StoredGlyph {
public:
    StoredGlyph() = default;
    StoredGlyph(const QRawFont &font, quint32 glyph, bool doubleResolution);

    int refCount() const { return int(m_ref); }
    void ref() { ++m_ref; }
    int deref() { m_ref = std::max(m_ref - 1, quint32(0)); return int(m_ref); }

    bool addToTextureAtlas(QTextureAtlas *atlas);
    void removeFromTextureAtlas();

    QTextureAtlas *atlas() const { return m_atlas; }
    QRectF glyphPathBoundingRect() const { return m_glyphPathBoundingRect; }
    QRectF texCoords() const;

private:
    quint32 m_ref = 0;
    QTextureAtlas *m_atlas = nullptr;
    QTextureAtlas::TextureId m_atlasEntry = QTextureAtlas::InvalidTexture;
    QRectF m_glyphPathBoundingRect;
    QImage m_distanceFieldImage;    // only used until added to texture atlas
};

// A DistanceFieldFont stores all glyphs for a given QRawFont.
// it will use multiple QTextureAtlasess to store the distance
// fields and uses ref-counting for each glyph to ensure that
// unused glyphs are removed from the texture atlasses.
class DistanceFieldFont
{
public:
    DistanceFieldFont(const QRawFont &font, bool doubleRes, Qt3DCore::QNode *parent);
    ~DistanceFieldFont();

    StoredGlyph findGlyph(quint32 glyph) const;
    StoredGlyph refGlyph(quint32 glyph);
    void derefGlyph(quint32 glyph);

    bool doubleGlyphResolution() const { return m_doubleGlyphResolution; }

private:
    QRawFont m_font;
    bool m_doubleGlyphResolution;
    Qt3DCore::QNode *m_parentNode; // parent node for the QTextureAtlasses

    QHash<quint32, StoredGlyph> m_glyphs;

    QList<QTextureAtlas*> m_atlasses;
};

StoredGlyph::StoredGlyph(const QRawFont &font, quint32 glyph, bool doubleResolution)
    : m_ref(1)
    , m_atlas(nullptr)
    , m_atlasEntry(QTextureAtlas::InvalidTexture)
{
    // create new single-channel distance field image for given glyph
    const QPainterPath path = font.pathForGlyph(glyph);
    const QDistanceField dfield(font, glyph, doubleResolution);
    m_distanceFieldImage = dfield.toImage(QImage::Format_Alpha8);

    // scale bounding rect down (as in QSGDistanceFieldGlyphCache::glyphData())
    const QRectF pathBound = path.boundingRect();
    float f = 1.0f / QT_DISTANCEFIELD_SCALE(doubleResolution);
    m_glyphPathBoundingRect = QRectF(pathBound.left() * f, -pathBound.top() * f, pathBound.width() * f, pathBound.height() * f);
}

bool StoredGlyph::addToTextureAtlas(QTextureAtlas *atlas)
{
    if (m_atlas || m_distanceFieldImage.isNull())
        return false;

    const auto texId = atlas->addImage(m_distanceFieldImage, DEFAULT_IMAGE_PADDING);
    if (texId != QTextureAtlas::InvalidTexture) {
        m_atlas = atlas;
        m_atlasEntry = texId;
        m_distanceFieldImage = QImage();    // free glyph image data
        return true;
    }

    return false;
}

void StoredGlyph::removeFromTextureAtlas()
{
    if (m_atlas) {
        m_atlas->removeImage(m_atlasEntry);
        m_atlas = nullptr;
        m_atlasEntry = QTextureAtlas::InvalidTexture;
    }
}

QRectF StoredGlyph::texCoords() const
{
    return m_atlas ? m_atlas->imageTexCoords(m_atlasEntry) : QRectF();
}

DistanceFieldFont::DistanceFieldFont(const QRawFont &font, bool doubleRes, Qt3DCore::QNode *parent)
    : m_font(font)
    , m_doubleGlyphResolution(doubleRes)
    , m_parentNode(parent)
{
    Q_ASSERT(m_parentNode);
}

DistanceFieldFont::~DistanceFieldFont()
{
    qDeleteAll(m_atlasses);
}

StoredGlyph DistanceFieldFont::findGlyph(quint32 glyph) const
{
    const auto it = m_glyphs.find(glyph);
    return (it != m_glyphs.cend()) ? it.value() : StoredGlyph();
}

StoredGlyph DistanceFieldFont::refGlyph(quint32 glyph)
{
    // if glyph already exists, just increase ref-count
    auto it = m_glyphs.find(glyph);
    if (it != m_glyphs.end()) {
        it.value().ref();
        return it.value();
    }

    // need to create new glyph
    StoredGlyph storedGlyph(m_font, glyph, m_doubleGlyphResolution);

    // see if one of the existing atlasses can hold the distance field image
    for (int i = 0; i < m_atlasses.size(); i++)
        if (storedGlyph.addToTextureAtlas(m_atlasses[i]))
            break;

    // if no texture atlas is big enough (or no exists yet), allocate a new one
    if (!storedGlyph.atlas()) {
        // this should be enough to store 40-60 glyphs, which should be sufficient for most
        // scenarios
        const int size = m_doubleGlyphResolution ? 512 : 256;

        QTextureAtlas *atlas = new QTextureAtlas();
        atlas->setWidth(size);
        atlas->setHeight(size);
        atlas->setFormat(Qt3DRender::QAbstractTexture::R8_UNorm);
        atlas->setPixelFormat(QOpenGLTexture::Red);
        atlas->setMinificationFilter(Qt3DRender::QAbstractTexture::Linear);
        atlas->setMagnificationFilter(Qt3DRender::QAbstractTexture::Linear);
        atlas->setParent(m_parentNode);
        m_atlasses << atlas;

        if (!storedGlyph.addToTextureAtlas(atlas))
            qWarning() << Q_FUNC_INFO << "Couldn't add glyph to newly allocated atlas. Glyph could be huge?";
    }

    m_glyphs.insert(glyph, storedGlyph);
    return storedGlyph;
}

void DistanceFieldFont::derefGlyph(quint32 glyph)
{
    auto it = m_glyphs.find(glyph);
    if (it == m_glyphs.end())
        return;

    // TODO
    // possible optimization: keep unreferenced glyphs as the texture atlas
    // still has space. only if a new glyph needs to be allocated, and there
    // is no more space within the atlas, then we can actually remove the glyphs
    // from the atlasses.

    // remove glyph if no refs anymore
    if (it.value().deref() <= 0) {
        QTextureAtlas *atlas = it.value().atlas();
        it.value().removeFromTextureAtlas();

        // remove atlas, if it contains no glyphs anymore
        if (atlas && atlas->imageCount() == 0) {
            Q_ASSERT(m_atlasses.contains(atlas));

            m_atlasses.removeAll(atlas);

            // This function might have been called as a result of destroying
            // the scene root which traverses the entire scene tree. Calling
            // delete on the atlas here could lead to dangling pointers in the
            // least of children being traversed for destruction.
            atlas->deleteLater();
        }

        m_glyphs.erase(it);
    }
}

// copied from QSGDistanceFieldGlyphCacheManager::fontKey
// we use this function to compare QRawFonts, as QRawFont doesn't
// implement a stable comparison function
QString QDistanceFieldGlyphCache::fontKey(const QRawFont &font)
{
    QFontEngine *fe = QRawFontPrivate::get(font)->fontEngine;
    if (!fe->faceId().filename.isEmpty()) {
        QByteArray keyName = fe->faceId().filename;
        if (font.style() != QFont::StyleNormal)
            keyName += QByteArray(" I");
        if (font.weight() != QFont::Normal)
            keyName += ' ' + QByteArray::number(font.weight());
        keyName += QByteArray(" DF");
        return QString::fromUtf8(keyName);
    } else {
        return QString::fromLatin1("%1_%2_%3_%4")
                  .arg(font.familyName())
                  .arg(font.styleName())
                  .arg(font.weight())
                  .arg(font.style());
    }
}

DistanceFieldFont* QDistanceFieldGlyphCache::getOrCreateDistanceFieldFont(const QRawFont &font)
{
    // return, if font already exists (make sure to only create one DistanceFieldFont for
    // each unique QRawFont, by building a hash on the QRawFont that ignores the font size)
    const QString key = fontKey(font);
    const auto it = m_fonts.constFind(key);
    if (it != m_fonts.cend())
        return it.value();

    // logic taken from QSGDistanceFieldGlyphCache::QSGDistanceFieldGlyphCache
    QRawFontPrivate *fontD = QRawFontPrivate::get(font);
    const int glyphCount = fontD->fontEngine->glyphCount();
    const bool useDoubleRes = qt_fontHasNarrowOutlines(font) && glyphCount < QT_DISTANCEFIELD_HIGHGLYPHCOUNT();

    // only keep one FontCache with a fixed pixel size for each distinct font type
    QRawFont actualFont = font;
    actualFont.setPixelSize(QT_DISTANCEFIELD_BASEFONTSIZE(useDoubleRes) * QT_DISTANCEFIELD_SCALE(useDoubleRes));

    // create new font cache
    // we set the parent node to nullptr, since the parent node of QTextureAtlasses
    // will be set when we pass them to QText2DMaterial later
    Q_ASSERT(m_rootNode);
    DistanceFieldFont *dff = new DistanceFieldFont(actualFont, useDoubleRes, m_rootNode);
    m_fonts.insert(key, dff);
    return dff;
}

QDistanceFieldGlyphCache::QDistanceFieldGlyphCache()
    : m_rootNode(nullptr)
{
}

QDistanceFieldGlyphCache::~QDistanceFieldGlyphCache()
{
}

void QDistanceFieldGlyphCache::setRootNode(QNode *rootNode)
{
    m_rootNode = rootNode;
}

QNode *QDistanceFieldGlyphCache::rootNode() const
{
    return m_rootNode;
}

bool QDistanceFieldGlyphCache::doubleGlyphResolution(const QRawFont &font)
{
    return getOrCreateDistanceFieldFont(font)->doubleGlyphResolution();
}

namespace {
QDistanceFieldGlyphCache::Glyph refAndGetGlyph(DistanceFieldFont *dff, quint32 glyph)
{
    QDistanceFieldGlyphCache::Glyph ret;

    if (dff) {
        const auto entry = dff->refGlyph(glyph);

        Q_ASSERT(entry.atlas());
        ret.glyphPathBoundingRect = entry.glyphPathBoundingRect();
        ret.texCoords = entry.texCoords();
        ret.texture = entry.atlas();
    }

    return ret;
}
} // anonymous

QList<QDistanceFieldGlyphCache::Glyph> QDistanceFieldGlyphCache::refGlyphs(const QGlyphRun &run)
{
    DistanceFieldFont *dff = getOrCreateDistanceFieldFont(run.rawFont());
    QList<QDistanceFieldGlyphCache::Glyph> ret;

    const auto glyphs = run.glyphIndexes();
    for (quint32 glyph : glyphs)
        ret << refAndGetGlyph(dff, glyph);

    return ret;
}

QDistanceFieldGlyphCache::Glyph QDistanceFieldGlyphCache::refGlyph(const QRawFont &font, quint32 glyph)
{
    return refAndGetGlyph(getOrCreateDistanceFieldFont(font), glyph);
}

void QDistanceFieldGlyphCache::derefGlyphs(const QGlyphRun &run)
{
    DistanceFieldFont *dff = getOrCreateDistanceFieldFont(run.rawFont());

    const auto glyphs = run.glyphIndexes();
    for (quint32 glyph : glyphs)
        dff->derefGlyph(glyph);
}

void QDistanceFieldGlyphCache::derefGlyph(const QRawFont &font, quint32 glyph)
{
    getOrCreateDistanceFieldFont(font)->derefGlyph(glyph);
}

} // namespace Qt3DExtras

QT_END_NAMESPACE
