/*
 * Copyright 2016 Google Inc.
 *
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 */

#include "src/core/SkDistanceFieldGen.h"
#include "src/gpu/ops/GrAtlasTextOp.h"
#include "src/gpu/text/GrAtlasManager.h"
#include "src/gpu/text/GrTextBlob.h"
#include "src/gpu/text/GrTextTarget.h"

enum RegenMask {
    kNoRegen    = 0x0,
    kRegenPos   = 0x1,
    kRegenCol   = 0x2,
    kRegenTex   = 0x4,
    kRegenGlyph = 0x8,
};

////////////////////////////////////////////////////////////////////////////////////////////////////

static void regen_positions(char* vertex, size_t vertexStride, SkScalar transX, SkScalar transY) {
    SkPoint* point = reinterpret_cast<SkPoint*>(vertex);
    for (int i = 0; i < 4; ++i) {
        point->fX += transX;
        point->fY += transY;
        point = SkTAddOffset<SkPoint>(point, vertexStride);
    }
}

static void regen_colors(char* vertex, size_t vertexStride, GrColor color) {
    // This is a bit wonky, but sometimes we have LCD text, in which case we won't have color
    // vertices, hence vertexStride - sizeof(SkIPoint16)
    size_t colorOffset = vertexStride - sizeof(SkIPoint16) - sizeof(GrColor);
    GrColor* vcolor = reinterpret_cast<GrColor*>(vertex + colorOffset);
    for (int i = 0; i < 4; ++i) {
        *vcolor = color;
        vcolor = SkTAddOffset<GrColor>(vcolor, vertexStride);
    }
}

static void regen_texcoords(char* vertex, size_t vertexStride, const GrGlyph* glyph,
                            bool useDistanceFields) {
    // This is a bit wonky, but sometimes we have LCD text, in which case we won't have color
    // vertices, hence vertexStride - sizeof(SkIPoint16)
    size_t texCoordOffset = vertexStride - sizeof(SkIPoint16);

    uint16_t u0, v0, u1, v1;
    SkASSERT(glyph);
    int width = glyph->fBounds.width();
    int height = glyph->fBounds.height();

    if (useDistanceFields) {
        u0 = glyph->fAtlasLocation.fX + SK_DistanceFieldInset;
        v0 = glyph->fAtlasLocation.fY + SK_DistanceFieldInset;
        u1 = u0 + width - 2 * SK_DistanceFieldInset;
        v1 = v0 + height - 2 * SK_DistanceFieldInset;
    } else {
        u0 = glyph->fAtlasLocation.fX;
        v0 = glyph->fAtlasLocation.fY;
        u1 = u0 + width;
        v1 = v0 + height;
    }
    // We pack the 2bit page index in the low bit of the u and v texture coords
    uint32_t pageIndex = glyph->pageIndex();
    SkASSERT(pageIndex < 4);
    uint16_t uBit = (pageIndex >> 1) & 0x1;
    uint16_t vBit = pageIndex & 0x1;
    u0 <<= 1;
    u0 |= uBit;
    v0 <<= 1;
    v0 |= vBit;
    u1 <<= 1;
    u1 |= uBit;
    v1 <<= 1;
    v1 |= vBit;

    uint16_t* textureCoords = reinterpret_cast<uint16_t*>(vertex + texCoordOffset);
    textureCoords[0] = u0;
    textureCoords[1] = v0;
    textureCoords = SkTAddOffset<uint16_t>(textureCoords, vertexStride);
    textureCoords[0] = u0;
    textureCoords[1] = v1;
    textureCoords = SkTAddOffset<uint16_t>(textureCoords, vertexStride);
    textureCoords[0] = u1;
    textureCoords[1] = v0;
    textureCoords = SkTAddOffset<uint16_t>(textureCoords, vertexStride);
    textureCoords[0] = u1;
    textureCoords[1] = v1;

#ifdef DISPLAY_PAGE_INDEX
    // Enable this to visualize the page from which each glyph is being drawn.
    // Green Red Magenta Cyan -> 0 1 2 3; Black -> error
    GrColor hackColor;
    switch (pageIndex) {
        case 0:
            hackColor = GrColorPackRGBA(0, 255, 0, 255);
            break;
        case 1:
            hackColor = GrColorPackRGBA(255, 0, 0, 255);;
            break;
        case 2:
            hackColor = GrColorPackRGBA(255, 0, 255, 255);
            break;
        case 3:
            hackColor = GrColorPackRGBA(0, 255, 255, 255);
            break;
        default:
            hackColor = GrColorPackRGBA(0, 0, 0, 255);
            break;
    }
    regen_colors(vertex, vertexStride, hackColor);
#endif
}

GrTextBlob::VertexRegenerator::VertexRegenerator(GrResourceProvider* resourceProvider,
                                                 GrTextBlob* blob,
                                                 GrTextBlob::SubRun* subRun,
                                                 const SkMatrix& viewMatrix, SkScalar x, SkScalar y,
                                                 GrColor color,
                                                 GrDeferredUploadTarget* uploadTarget,
                                                 GrStrikeCache* grStrikeCache,
                                                 GrAtlasManager* fullAtlasManager)
        : fResourceProvider(resourceProvider)
        , fViewMatrix(viewMatrix)
        , fBlob(blob)
        , fUploadTarget(uploadTarget)
        , fGrStrikeCache(grStrikeCache)
        , fFullAtlasManager(fullAtlasManager)
        , fSubRun(subRun)
        , fColor(color) {
    // Compute translation if any
    fSubRun->computeTranslation(fViewMatrix, x, y, &fTransX, &fTransY);

    // Because the GrStrikeCache may evict the strike a blob depends on using for
    // generating its texture coords, we have to track whether or not the strike has
    // been abandoned.  If it hasn't been abandoned, then we can use the GrGlyph*s as is
    // otherwise we have to get the new strike, and use that to get the correct glyphs.
    // Because we do not have the packed ids, and thus can't look up our glyphs in the
    // new strike, we instead keep our ref to the old strike and use the packed ids from
    // it.  These ids will still be valid as long as we hold the ref.  When we are done
    // updating our cache of the GrGlyph*s, we drop our ref on the old strike
    if (fSubRun->strike()->isAbandoned()) {
        fRegenFlags |= kRegenGlyph;
        fRegenFlags |= kRegenTex;
    }
    if (kARGB_GrMaskFormat != fSubRun->maskFormat() && fSubRun->color() != color) {
        fRegenFlags |= kRegenCol;
    }
    if (0.f != fTransX || 0.f != fTransY) {
        fRegenFlags |= kRegenPos;
    }
}

bool GrTextBlob::VertexRegenerator::doRegen(GrTextBlob::VertexRegenerator::Result* result,
                                            bool regenPos, bool regenCol, bool regenTexCoords,
                                            bool regenGlyphs) {
    SkASSERT(!regenGlyphs || regenTexCoords);
    if (regenTexCoords) {
        fSubRun->resetBulkUseToken();

        const SkStrikeSpec& strikeSpec = fSubRun->strikeSpec();

        if (!fMetricsAndImages.isValid()
            || fMetricsAndImages->descriptor() != strikeSpec.descriptor()) {
            fMetricsAndImages.init(strikeSpec);
        }

        if (regenGlyphs) {
            // Take the glyphs from the old strike, and translate them a new strike.
            sk_sp<GrTextStrike> newStrike = strikeSpec.findOrCreateGrStrike(fGrStrikeCache);

            // Start this batch at the start of the subRun plus any glyphs that were previously
            // processed.
            size_t glyphStart = fSubRun->glyphStartIndex() + fCurrGlyph;
            SkSpan<GrGlyph*> glyphs{&(fBlob->fGlyphs[glyphStart]),
                                    fSubRun->glyphCount() - fCurrGlyph};

            // Convert old glyphs to newStrike.
            for (auto& glyph : glyphs) {
                SkPackedGlyphID id = glyph->fPackedID;
                glyph = newStrike->getGlyph(id, fMetricsAndImages.get());
                SkASSERT(id == glyph->fPackedID);
            }

            fSubRun->setStrike(newStrike);
        }
    }

    sk_sp<GrTextStrike> grStrike = fSubRun->refStrike();
    bool hasW = fSubRun->hasW();
    auto vertexStride = GetVertexStride(fSubRun->maskFormat(), hasW);
    char* currVertex = fBlob->fVertices + fSubRun->vertexStartIndex() +
                       fCurrGlyph * kVerticesPerGlyph * vertexStride;
    result->fFirstVertex = currVertex;

    for (int glyphIdx = fCurrGlyph; glyphIdx < (int)fSubRun->glyphCount(); glyphIdx++) {
        GrGlyph* glyph = nullptr;
        if (regenTexCoords) {
            size_t glyphOffset = glyphIdx + fSubRun->glyphStartIndex();
            glyph = fBlob->fGlyphs[glyphOffset];
            SkASSERT(glyph && glyph->fMaskFormat == fSubRun->maskFormat());

            if (!fFullAtlasManager->hasGlyph(glyph)) {
                GrDrawOpAtlas::ErrorCode code;
                code = grStrike->addGlyphToAtlas(fResourceProvider, fUploadTarget, fGrStrikeCache,
                                                 fFullAtlasManager, glyph,
                                                 fMetricsAndImages.get(), fSubRun->maskFormat(),
                                                 fSubRun->needsTransform());
                if (GrDrawOpAtlas::ErrorCode::kError == code) {
                    // Something horrible has happened - drop the op
                    return false;
                }
                else if (GrDrawOpAtlas::ErrorCode::kTryAgain == code) {
                    fBrokenRun = glyphIdx > 0;
                    result->fFinished = false;
                    return true;
                }
            }
            auto tokenTracker = fUploadTarget->tokenTracker();
            fFullAtlasManager->addGlyphToBulkAndSetUseToken(fSubRun->bulkUseToken(), glyph,
                                                            tokenTracker->nextDrawToken());
        }

        if (regenPos) {
            regen_positions(currVertex, vertexStride, fTransX, fTransY);
        }
        if (regenCol) {
            regen_colors(currVertex, vertexStride, fColor);
        }
        if (regenTexCoords) {
            regen_texcoords(currVertex, vertexStride, glyph, fSubRun->drawAsDistanceFields());
        }

        currVertex += vertexStride * GrAtlasTextOp::kVerticesPerGlyph;
        ++result->fGlyphsRegenerated;
        ++fCurrGlyph;
    }

    // We may have changed the color so update it here
    fSubRun->setColor(fColor);
    if (regenTexCoords) {
        fSubRun->setAtlasGeneration(fBrokenRun
                                    ? GrDrawOpAtlas::kInvalidAtlasGeneration
                                    : fFullAtlasManager->atlasGeneration(fSubRun->maskFormat()));
    } else {
        // For the non-texCoords case we need to ensure that we update the associated use tokens
        fFullAtlasManager->setUseTokenBulk(*fSubRun->bulkUseToken(),
                                           fUploadTarget->tokenTracker()->nextDrawToken(),
                                           fSubRun->maskFormat());
    }
    return true;
}

bool GrTextBlob::VertexRegenerator::regenerate(GrTextBlob::VertexRegenerator::Result* result) {
    uint64_t currentAtlasGen = fFullAtlasManager->atlasGeneration(fSubRun->maskFormat());
    // If regenerate() is called multiple times then the atlas gen may have changed. So we check
    // this each time.
    if (fSubRun->atlasGeneration() != currentAtlasGen) {
        fRegenFlags |= kRegenTex;
    }

    if (fRegenFlags) {
        return this->doRegen(result,
                             fRegenFlags & kRegenPos,
                             fRegenFlags & kRegenCol,
                             fRegenFlags & kRegenTex,
                             fRegenFlags & kRegenGlyph);
    } else {
        bool hasW = fSubRun->hasW();
        auto vertexStride = GetVertexStride(fSubRun->maskFormat(), hasW);
        result->fFinished = true;
        result->fGlyphsRegenerated = fSubRun->glyphCount() - fCurrGlyph;
        result->fFirstVertex = fBlob->fVertices + fSubRun->vertexStartIndex() +
                               fCurrGlyph * kVerticesPerGlyph * vertexStride;
        fCurrGlyph = fSubRun->glyphCount();

        // set use tokens for all of the glyphs in our subrun.  This is only valid if we
        // have a valid atlas generation
        fFullAtlasManager->setUseTokenBulk(*fSubRun->bulkUseToken(),
                                           fUploadTarget->tokenTracker()->nextDrawToken(),
                                           fSubRun->maskFormat());
        return true;
    }
    SK_ABORT("Should not get here");
}
