/*
 * Copyright 2006 The Android Open Source Project
 *
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 */

#include "src/core/SkStrike.h"

#include "include/core/SkDrawable.h"
#include "include/core/SkFontStyle.h"
#include "include/core/SkMatrix.h"
#include "include/core/SkPath.h"
#include "include/core/SkString.h"
#include "include/core/SkTraceMemoryDump.h"
#include "include/core/SkTypeface.h"
#include "include/private/base/SkDebug.h"
#include "include/private/base/SkTFitsIn.h"
#include "src/core/SkGlyph.h"
#include "src/core/SkMask.h"
#include "src/core/SkReadBuffer.h"
#include "src/core/SkScalerContext.h"
#include "src/core/SkStrikeCache.h"
#include "src/core/SkWriteBuffer.h"
#include "src/text/StrikeForGPU.h"

#include <cctype>
#include <new>
#include <optional>
#include <utility>

using namespace skglyph;

static SkFontMetrics use_or_generate_metrics(
        const SkFontMetrics* metrics, SkScalerContext* context) {
    SkFontMetrics answer;
    if (metrics) {
        answer = *metrics;
    } else {
        context->getFontMetrics(&answer);
    }
    return answer;
}

SkStrike::SkStrike(SkStrikeCache* strikeCache,
                   const SkStrikeSpec& strikeSpec,
                   std::unique_ptr<SkScalerContext> scaler,
                   const SkFontMetrics* metrics,
                   std::unique_ptr<SkStrikePinner> pinner)
        : fFontMetrics{use_or_generate_metrics(metrics, scaler.get())}
        , fRoundingSpec{scaler->isSubpixel(),
                        scaler->computeAxisAlignmentForHText()}
        , fStrikeSpec{strikeSpec}
        , fStrikeCache{strikeCache}
        , fScalerContext{std::move(scaler)}
        , fPinner{std::move(pinner)} {
    SkASSERT(fScalerContext != nullptr);
}

class SK_SCOPED_CAPABILITY SkStrike::Monitor {
public:
    Monitor(SkStrike* strike) SK_ACQUIRE(strike->fStrikeLock)
            : fStrike{strike} {
        fStrike->lock();
    }

    ~Monitor() SK_RELEASE_CAPABILITY() {
        fStrike->unlock();
    }

private:
    SkStrike* const fStrike;
};

void SkStrike::lock() {
    fStrikeLock.acquire();
    fMemoryIncrease = 0;
}

void SkStrike::unlock() {
    const size_t memoryIncrease = fMemoryIncrease;
    fStrikeLock.release();
    this->updateMemoryUsage(memoryIncrease);
}

void
SkStrike::FlattenGlyphsByType(SkWriteBuffer& buffer,
                              SkSpan<SkGlyph> images,
                              SkSpan<SkGlyph> paths,
                              SkSpan<SkGlyph> drawables) {
    SkASSERT_RELEASE(SkTFitsIn<int>(images.size()) &&
                     SkTFitsIn<int>(paths.size()) &&
                     SkTFitsIn<int>(drawables.size()));

    buffer.writeInt(images.size());
    for (SkGlyph& glyph : images) {
        SkASSERT(SkMask::IsValidFormat(glyph.maskFormat()));
        glyph.flattenMetrics(buffer);
        glyph.flattenImage(buffer);
    }

    buffer.writeInt(paths.size());
    for (SkGlyph& glyph : paths) {
        SkASSERT(SkMask::IsValidFormat(glyph.maskFormat()));
        glyph.flattenMetrics(buffer);
        glyph.flattenPath(buffer);
    }

    buffer.writeInt(drawables.size());
    for (SkGlyph& glyph : drawables) {
        SkASSERT(SkMask::IsValidFormat(glyph.maskFormat()));
        glyph.flattenMetrics(buffer);
        glyph.flattenDrawable(buffer);
    }
}

bool SkStrike::mergeFromBuffer(SkReadBuffer& buffer) {
    // Read glyphs with images for the current strike.
    const int imagesCount = buffer.readInt();
    if (imagesCount == 0 && !buffer.isValid()) {
        return false;
    }

    {
        Monitor m{this};
        for (int curImage = 0; curImage < imagesCount; ++curImage) {
            if (!this->mergeGlyphAndImageFromBuffer(buffer)) {
                return false;
            }
        }
    }

    // Read glyphs with paths for the current strike.
    const int pathsCount = buffer.readInt();
    if (pathsCount == 0 && !buffer.isValid()) {
        return false;
    }
    {
        Monitor m{this};
        for (int curPath = 0; curPath < pathsCount; ++curPath) {
            if (!this->mergeGlyphAndPathFromBuffer(buffer)) {
                return false;
            }
        }
    }

    // Read glyphs with drawables for the current strike.
    const int drawablesCount = buffer.readInt();
    if (drawablesCount == 0 && !buffer.isValid()) {
        return false;
    }
    {
        Monitor m{this};
        for (int curDrawable = 0; curDrawable < drawablesCount; ++curDrawable) {
            if (!this->mergeGlyphAndDrawableFromBuffer(buffer)) {
                return false;
            }
        }
    }

    return true;
}

SkGlyph* SkStrike::mergeGlyphAndImage(SkPackedGlyphID toID, const SkGlyph& fromGlyph) {
    Monitor m{this};
    // TODO(herb): remove finding the glyph when setting the metrics and image are separated
    SkGlyphDigest* digest = fDigestForPackedGlyphID.find(toID);
    if (digest != nullptr) {
        SkGlyph* glyph = fGlyphForIndex[digest->index()];
        if (fromGlyph.setImageHasBeenCalled()) {
            if (glyph->setImageHasBeenCalled()) {
                // Should never set an image on a glyph which already has an image.
                SkDEBUGFAIL("Re-adding image to existing glyph. This should not happen.");
            }
            // TODO: assert that any metrics on fromGlyph are the same.
            fMemoryIncrease += glyph->setMetricsAndImage(&fAlloc, fromGlyph);
        }
        return glyph;
    } else {
        SkGlyph* glyph = fAlloc.make<SkGlyph>(toID);
        fMemoryIncrease += glyph->setMetricsAndImage(&fAlloc, fromGlyph) + sizeof(SkGlyph);
        (void)this->addGlyphAndDigest(glyph);
        return glyph;
    }
}

const SkPath* SkStrike::mergePath(SkGlyph* glyph, const SkPath* path, bool hairline, bool modified) {
    Monitor m{this};
    if (glyph->setPathHasBeenCalled()) {
        SkDEBUGFAIL("Re-adding path to existing glyph. This should not happen.");
    }
    if (glyph->setPath(&fAlloc, path, hairline, modified)) {
        fMemoryIncrease += glyph->path()->approximateBytesUsed();
    }

    return glyph->path();
}

const SkDrawable* SkStrike::mergeDrawable(SkGlyph* glyph, sk_sp<SkDrawable> drawable) {
    Monitor m{this};
    if (glyph->setDrawableHasBeenCalled()) {
        SkDEBUGFAIL("Re-adding drawable to existing glyph. This should not happen.");
    }
    if (glyph->setDrawable(&fAlloc, std::move(drawable))) {
        fMemoryIncrease += glyph->drawable()->approximateBytesUsed();
        SkASSERT(fMemoryIncrease > 0);
    }

    return glyph->drawable();
}

void SkStrike::findIntercepts(const SkScalar bounds[2], SkScalar scale, SkScalar xPos,
                              SkGlyph* glyph, SkScalar* array, int* count) {
    SkAutoMutexExclusive lock{fStrikeLock};
    glyph->ensureIntercepts(bounds, scale, xPos, array, count, &fAlloc);
}

SkSpan<const SkGlyph*> SkStrike::metrics(
        SkSpan<const SkGlyphID> glyphIDs, const SkGlyph* results[]) {
    Monitor m{this};
    return this->internalPrepare(glyphIDs, kMetricsOnly, results);
}

SkSpan<const SkGlyph*> SkStrike::preparePaths(
        SkSpan<const SkGlyphID> glyphIDs, const SkGlyph* results[]) {
    Monitor m{this};
    return this->internalPrepare(glyphIDs, kMetricsAndPath, results);
}

SkSpan<const SkGlyph*> SkStrike::prepareImages(
        SkSpan<const SkPackedGlyphID> glyphIDs, const SkGlyph* results[]) {
    const SkGlyph** cursor = results;
    Monitor m{this};
    for (auto glyphID : glyphIDs) {
        SkGlyph* glyph = this->glyph(glyphID);
        this->prepareForImage(glyph);
        *cursor++ = glyph;
    }

    return {results, glyphIDs.size()};
}

SkSpan<const SkGlyph*> SkStrike::prepareDrawables(
        SkSpan<const SkGlyphID> glyphIDs, const SkGlyph* results[]) {
    const SkGlyph** cursor = results;
    {
        Monitor m{this};
        for (auto glyphID : glyphIDs) {
            SkGlyph* glyph = this->glyph(SkPackedGlyphID{glyphID});
            this->prepareForDrawable(glyph);
            *cursor++ = glyph;
        }
    }

    return {results, glyphIDs.size()};
}

void SkStrike::glyphIDsToPaths(SkSpan<sktext::IDOrPath> idsOrPaths) {
    Monitor m{this};
    for (sktext::IDOrPath& idOrPath : idsOrPaths) {
        SkGlyph* glyph = this->glyph(SkPackedGlyphID{idOrPath.fGlyphID});
        this->prepareForPath(glyph);
        new (&idOrPath.fPath) SkPath{*glyph->path()};
    }
}

void SkStrike::glyphIDsToDrawables(SkSpan<sktext::IDOrDrawable> idsOrDrawables) {
    Monitor m{this};
    for (sktext::IDOrDrawable& idOrDrawable : idsOrDrawables) {
        SkGlyph* glyph = this->glyph(SkPackedGlyphID{idOrDrawable.fGlyphID});
        this->prepareForDrawable(glyph);
        SkASSERT(glyph->drawable() != nullptr);
        idOrDrawable.fDrawable = glyph->drawable();
    }
}

void SkStrike::dump() const {
    SkAutoMutexExclusive lock{fStrikeLock};
    const SkTypeface* face = fScalerContext->getTypeface();
    const SkScalerContextRec& rec = fScalerContext->getRec();
    SkString name;
    face->getFamilyName(&name);

    SkString msg;
    SkFontStyle style = face->fontStyle();
    msg.printf("cache typeface:%x %25s:(%d,%d,%d)\n %s glyphs:%3d",
               face->uniqueID(), name.c_str(), style.weight(), style.width(), style.slant(),
               rec.dump().c_str(), fDigestForPackedGlyphID.count());
    SkDebugf("%s\n", msg.c_str());
}

void SkStrike::dumpMemoryStatistics(SkTraceMemoryDump* dump) const {
    SkAutoMutexExclusive lock{fStrikeLock};
    const SkTypeface* face = fScalerContext->getTypeface();
    const SkScalerContextRec& rec = fScalerContext->getRec();

    SkString fontName;
    face->getFamilyName(&fontName);
    // Replace all special characters with '_'.
    for (size_t index = 0; index < fontName.size(); ++index) {
        if (!std::isalnum(fontName[index])) {
            fontName[index] = '_';
        }
    }

    SkString dumpName = SkStringPrintf("%s/%s_%u/%p",
                                       SkStrikeCache::kGlyphCacheDumpName,
                                       fontName.c_str(),
                                       rec.fTypefaceID,
                                       this);

    dump->dumpNumericValue(dumpName.c_str(), "size", "bytes", fMemoryUsed);
    dump->dumpNumericValue(dumpName.c_str(),
                           "glyph_count", "objects",
                           fDigestForPackedGlyphID.count());
    dump->setMemoryBacking(dumpName.c_str(), "malloc", nullptr);
}

SkGlyph* SkStrike::glyph(SkGlyphDigest digest) {
    return fGlyphForIndex[digest.index()];
}

SkGlyph* SkStrike::glyph(SkPackedGlyphID packedGlyphID) {
    SkGlyphDigest digest = this->digestFor(kDirectMask, packedGlyphID);
    return this->glyph(digest);
}

SkGlyphDigest SkStrike::digestFor(ActionType actionType, SkPackedGlyphID packedGlyphID) {
    SkGlyphDigest* digestPtr = fDigestForPackedGlyphID.find(packedGlyphID);
    if (digestPtr != nullptr && digestPtr->actionFor(actionType) != GlyphAction::kUnset) {
        return *digestPtr;
    }

    SkGlyph* glyph;
    if (digestPtr != nullptr) {
        glyph = fGlyphForIndex[digestPtr->index()];
    } else {
        glyph = fAlloc.make<SkGlyph>(fScalerContext->makeGlyph(packedGlyphID, &fAlloc));
        fMemoryIncrease += sizeof(SkGlyph);
        digestPtr = this->addGlyphAndDigest(glyph);
    }

    digestPtr->setActionFor(actionType, glyph, this);

    return *digestPtr;
}

SkGlyphDigest* SkStrike::addGlyphAndDigest(SkGlyph* glyph) {
    size_t index = fGlyphForIndex.size();
    SkGlyphDigest digest = SkGlyphDigest{index, *glyph};
    SkGlyphDigest* newDigest = fDigestForPackedGlyphID.set(digest);
    fGlyphForIndex.push_back(glyph);
    return newDigest;
}

bool SkStrike::prepareForImage(SkGlyph* glyph) {
    if (glyph->setImage(&fAlloc, fScalerContext.get())) {
        fMemoryIncrease += glyph->imageSize();
    }
    return glyph->image() != nullptr;
}

bool SkStrike::prepareForPath(SkGlyph* glyph) {
    if (glyph->setPath(&fAlloc, fScalerContext.get())) {
        fMemoryIncrease += glyph->path()->approximateBytesUsed();
    }
    return glyph->path() !=nullptr;
}

bool SkStrike::prepareForDrawable(SkGlyph* glyph) {
    if (glyph->setDrawable(&fAlloc, fScalerContext.get())) {
        size_t increase = glyph->drawable()->approximateBytesUsed();
        SkASSERT(increase > 0);
        fMemoryIncrease += increase;
    }
    return glyph->drawable() != nullptr;
}

SkGlyph* SkStrike::mergeGlyphFromBuffer(SkReadBuffer& buffer) {
    SkASSERT(buffer.isValid());
    std::optional<SkGlyph> prototypeGlyph = SkGlyph::MakeFromBuffer(buffer);
    if (!buffer.validate(prototypeGlyph.has_value())) {
        return nullptr;
    }

    // Check if this glyph has already been seen.
    SkGlyphDigest* digestPtr = fDigestForPackedGlyphID.find(prototypeGlyph->getPackedID());
    if (digestPtr != nullptr) {
        return fGlyphForIndex[digestPtr->index()];
    }

    // This is the first time. Allocate a new glyph.
    SkGlyph* glyph = fAlloc.make<SkGlyph>(prototypeGlyph.value());
    fMemoryIncrease += sizeof(SkGlyph);
    this->addGlyphAndDigest(glyph);
    return glyph;
}

bool SkStrike::mergeGlyphAndImageFromBuffer(SkReadBuffer& buffer) {
    SkASSERT(buffer.isValid());
    SkGlyph* glyph = this->mergeGlyphFromBuffer(buffer);
    if (!buffer.validate(glyph != nullptr)) {
        return false;
    }
    fMemoryIncrease += glyph->addImageFromBuffer(buffer, &fAlloc);
    return buffer.isValid();
}

bool SkStrike::mergeGlyphAndPathFromBuffer(SkReadBuffer& buffer) {
    SkASSERT(buffer.isValid());
    SkGlyph* glyph = this->mergeGlyphFromBuffer(buffer);
    if (!buffer.validate(glyph != nullptr)) {
        return false;
    }
    fMemoryIncrease += glyph->addPathFromBuffer(buffer, &fAlloc);
    return buffer.isValid();
}

bool SkStrike::mergeGlyphAndDrawableFromBuffer(SkReadBuffer& buffer) {
    SkASSERT(buffer.isValid());
    SkGlyph* glyph = this->mergeGlyphFromBuffer(buffer);
    if (!buffer.validate(glyph != nullptr)) {
        return false;
    }
    fMemoryIncrease += glyph->addDrawableFromBuffer(buffer, &fAlloc);
    return buffer.isValid();
}

SkSpan<const SkGlyph*> SkStrike::internalPrepare(
        SkSpan<const SkGlyphID> glyphIDs, PathDetail pathDetail, const SkGlyph** results) {
    const SkGlyph** cursor = results;
    for (auto glyphID : glyphIDs) {
        SkGlyph* glyph = this->glyph(SkPackedGlyphID{glyphID});
        if (pathDetail == kMetricsAndPath) {
            this->prepareForPath(glyph);
        }
        *cursor++ = glyph;
    }

    return {results, glyphIDs.size()};
}

void SkStrike::updateMemoryUsage(size_t increase) {
    if (increase > 0) {
        // fRemoved and the cache's total memory are managed under the cache's lock. This allows
        // them to be accessed under LRU operation.
        SkAutoMutexExclusive lock{fStrikeCache->fLock};
        fMemoryUsed += increase;
        if (!fRemoved) {
            fStrikeCache->fTotalMemoryUsed += increase;
        }
    }
}
