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

#include "src/text/gpu/TextBlob.h"

#include "include/core/SkMatrix.h"
#include "include/core/SkScalar.h"
#include "include/private/base/SkTemplates.h"
#include "include/private/chromium/SkChromeRemoteGlyphCache.h"
#include "include/private/chromium/Slug.h"
#include "src/core/SkFontPriv.h"
#include "src/core/SkMaskFilterBase.h"
#include "src/core/SkMatrixProvider.h"
#include "src/core/SkPaintPriv.h"
#include "src/core/SkReadBuffer.h"
#include "src/core/SkRectPriv.h"
#include "src/core/SkStrikeCache.h"
#include "src/core/SkWriteBuffer.h"
#include "src/text/GlyphRun.h"
#include "src/text/gpu/SubRunAllocator.h"
#include "src/text/gpu/SubRunContainer.h"

#if defined(SK_GANESH)  // Ganesh Support
#include "src/gpu/ganesh/Device_v1.h"
#include "src/gpu/ganesh/GrClip.h"
#include "src/gpu/ganesh/GrRecordingContextPriv.h"
#include "src/gpu/ganesh/SurfaceDrawContext.h"
#endif

using namespace sktext::gpu;
namespace {
SkMatrix position_matrix(const SkMatrix& drawMatrix, SkPoint drawOrigin) {
    SkMatrix position_matrix = drawMatrix;
    return position_matrix.preTranslate(drawOrigin.x(), drawOrigin.y());
}

// Check for integer translate with the same 2x2 matrix.
// Returns the translation, and true if the change from initial matrix to the position matrix
// support using direct glyph masks.
std::tuple<bool, SkVector> can_use_direct(
        const SkMatrix& initialPositionMatrix, const SkMatrix& positionMatrix) {
    // The existing direct glyph info can be used if the initialPositionMatrix, and the
    // positionMatrix have the same 2x2, and the translation between them is integer.
    // Calculate the translation in source space to a translation in device space by mapping
    // (0, 0) through both the initial position matrix and the position matrix; take the difference.
    SkVector translation = positionMatrix.mapOrigin() - initialPositionMatrix.mapOrigin();
    return {initialPositionMatrix.getScaleX() == positionMatrix.getScaleX() &&
            initialPositionMatrix.getScaleY() == positionMatrix.getScaleY() &&
            initialPositionMatrix.getSkewX()  == positionMatrix.getSkewX()  &&
            initialPositionMatrix.getSkewY()  == positionMatrix.getSkewY()  &&
            SkScalarIsInt(translation.x()) && SkScalarIsInt(translation.y()),
            translation};
}


static SkColor compute_canonical_color(const SkPaint& paint, bool lcd) {
    SkColor canonicalColor = SkPaintPriv::ComputeLuminanceColor(paint);
    if (lcd) {
        // This is the correct computation for canonicalColor, but there are tons of cases where LCD
        // can be modified. For now we just regenerate if any run in a textblob has LCD.
        // TODO figure out where all of these modifications are and see if we can incorporate that
        //      logic at a higher level *OR* use sRGB
        //canonicalColor = SkMaskGamma::CanonicalColor(canonicalColor);

        // TODO we want to figure out a way to be able to use the canonical color on LCD text,
        // see the note above.  We pick a placeholder value for LCD text to ensure we always match
        // the same key
        return SK_ColorTRANSPARENT;
    } else {
        // A8, though can have mixed BMP text but it shouldn't matter because BMP text won't have
        // gamma corrected masks anyways, nor color
        U8CPU lum = SkComputeLuminance(SkColorGetR(canonicalColor),
                                       SkColorGetG(canonicalColor),
                                       SkColorGetB(canonicalColor));
        // reduce to our finite number of bits
        canonicalColor = SkMaskGamma::CanonicalColor(SkColorSetRGB(lum, lum, lum));
    }
    return canonicalColor;
}

// -- SlugImpl -------------------------------------------------------------------------------------
class SlugImpl final : public Slug {
public:
    SlugImpl(SubRunAllocator&& alloc,
             SubRunContainerOwner subRuns,
             SkRect sourceBounds,
             const SkPaint& paint,
             SkPoint origin);
    ~SlugImpl() override = default;

    static sk_sp<SlugImpl> Make(const SkMatrixProvider& viewMatrix,
                                const sktext::GlyphRunList& glyphRunList,
                                const SkPaint& initialPaint,
                                const SkPaint& drawingPaint,
                                SkStrikeDeviceInfo strikeDeviceInfo,
                                sktext::StrikeForGPUCacheInterface* strikeCache);
    static sk_sp<Slug> MakeFromBuffer(SkReadBuffer& buffer,
                                      const SkStrikeClient* client);
    void doFlatten(SkWriteBuffer& buffer) const override;

#if defined(SK_GANESH)
    void surfaceDraw(SkCanvas*,
                     const GrClip* clip,
                     const SkMatrixProvider& viewMatrix,
                     const SkPaint& paint,
                     skgpu::ganesh::SurfaceDrawContext* sdc) const;
#endif

    SkRect sourceBounds() const override { return fSourceBounds; }
    SkRect sourceBoundsWithOrigin() const override { return fSourceBounds.makeOffset(fOrigin); }
    const SkPaint& initialPaint() const override { return fInitialPaint; }

    const SkMatrix& initialPositionMatrix() const { return fSubRuns->initialPosition(); }
    SkPoint origin() const { return fOrigin; }

    // Change memory management to handle the data after Slug, but in the same allocation
    // of memory. Only allow placement new.
    void operator delete(void* p) { ::operator delete(p); }
    void* operator new(size_t) { SK_ABORT("All slugs are created by placement new."); }
    void* operator new(size_t, void* p) { return p; }

private:
    // The allocator must come first because it needs to be destroyed last. Other fields of this
    // structure may have pointers into it.
    SubRunAllocator fAlloc;
    SubRunContainerOwner fSubRuns;
    const SkRect fSourceBounds;
    const SkPaint fInitialPaint;
    const SkPoint fOrigin;
};

SlugImpl::SlugImpl(SubRunAllocator&& alloc,
                   SubRunContainerOwner subRuns,
                   SkRect sourceBounds,
                   const SkPaint& paint,
                   SkPoint origin)
        : fAlloc {std::move(alloc)}
        , fSubRuns(std::move(subRuns))
        , fSourceBounds{sourceBounds}
        , fInitialPaint{paint}
        , fOrigin{origin} {}

#if defined(SK_GANESH)
void SlugImpl::surfaceDraw(SkCanvas* canvas,
                           const GrClip* clip,
                           const SkMatrixProvider& viewMatrix,
                           const SkPaint& drawingPaint,
                           skgpu::ganesh::SurfaceDrawContext* sdc) const {
    fSubRuns->draw(canvas, clip, viewMatrix, fOrigin, drawingPaint, this, sdc);
}
#endif

void SlugImpl::doFlatten(SkWriteBuffer& buffer) const {
    buffer.writeRect(fSourceBounds);
    SkPaintPriv::Flatten(fInitialPaint, buffer);
    buffer.writePoint(fOrigin);
    fSubRuns->flattenAllocSizeHint(buffer);
    fSubRuns->flattenRuns(buffer);
}

sk_sp<Slug> SlugImpl::MakeFromBuffer(SkReadBuffer& buffer, const SkStrikeClient* client) {
    SkRect sourceBounds = buffer.readRect();
    SkASSERT(!sourceBounds.isEmpty());
    if (!buffer.validate(!sourceBounds.isEmpty())) { return nullptr; }
    SkPaint paint = buffer.readPaint();
    SkPoint origin = buffer.readPoint();
    int allocSizeHint = SubRunContainer::AllocSizeHintFromBuffer(buffer);

    auto [initializer, _, alloc] =
            SubRunAllocator::AllocateClassMemoryAndArena<SlugImpl>(allocSizeHint);

    SubRunContainerOwner container = SubRunContainer::MakeFromBufferInAlloc(buffer, client, &alloc);

    // Something went wrong while reading.
    SkASSERT(buffer.isValid());
    if (!buffer.isValid()) { return nullptr;}

    return sk_sp<SlugImpl>(initializer.initialize(
            std::move(alloc), std::move(container), sourceBounds, paint, origin));
}

sk_sp<SlugImpl> SlugImpl::Make(const SkMatrixProvider& viewMatrix,
                               const sktext::GlyphRunList& glyphRunList,
                               const SkPaint& initialPaint,
                               const SkPaint& drawingPaint,
                               SkStrikeDeviceInfo strikeDeviceInfo,
                               sktext::StrikeForGPUCacheInterface* strikeCache) {
    size_t subRunSizeHint = SubRunContainer::EstimateAllocSize(glyphRunList);
    auto [initializer, _, alloc] =
            SubRunAllocator::AllocateClassMemoryAndArena<SlugImpl>(subRunSizeHint);

    const SkMatrix positionMatrix =
            position_matrix(viewMatrix.localToDevice(), glyphRunList.origin());

    auto subRuns = SubRunContainer::MakeInAlloc(glyphRunList,
                                                positionMatrix,
                                                drawingPaint,
                                                strikeDeviceInfo,
                                                strikeCache,
                                                &alloc,
                                                SubRunContainer::kAddSubRuns,
                                                "Make Slug");

    sk_sp<SlugImpl> slug = sk_sp<SlugImpl>(initializer.initialize(
            std::move(alloc),
            std::move(subRuns),
            glyphRunList.sourceBounds(),
            initialPaint,
            glyphRunList.origin()));

    // There is nothing to draw here. This is particularly a problem with RSX form blobs where a
    // single space becomes a run with no glyphs.
    if (slug->fSubRuns->isEmpty()) { return nullptr; }

    return slug;
}
}  // namespace

namespace sktext::gpu {
// -- TextBlob::Key ------------------------------------------------------------------------------
auto TextBlob::Key::Make(const GlyphRunList& glyphRunList,
                         const SkPaint& paint,
                         const SkMatrix& drawMatrix,
                         const SkStrikeDeviceInfo& strikeDevice) -> std::tuple<bool, Key> {
    SkASSERT(strikeDevice.fSDFTControl != nullptr);
    SkMaskFilterBase::BlurRec blurRec;
    // It might be worth caching these things, but its not clear at this time
    // TODO for animated mask filters, this will fill up our cache.  We need a safeguard here
    const SkMaskFilter* maskFilter = paint.getMaskFilter();
    bool canCache = glyphRunList.canCache() &&
                    !(paint.getPathEffect() ||
                        (maskFilter && !as_MFB(maskFilter)->asABlur(&blurRec)));

    TextBlob::Key key;
    if (canCache) {
        bool hasLCD = glyphRunList.anyRunsLCD();

        // We canonicalize all non-lcd draws to use kUnknown_SkPixelGeometry
        SkPixelGeometry pixelGeometry = hasLCD ? strikeDevice.fSurfaceProps.pixelGeometry()
                                               : kUnknown_SkPixelGeometry;

        SkColor canonicalColor = compute_canonical_color(paint, hasLCD);

        key.fPixelGeometry = pixelGeometry;
        key.fUniqueID = glyphRunList.uniqueID();
        key.fStyle = paint.getStyle();
        if (key.fStyle != SkPaint::kFill_Style) {
            key.fFrameWidth = paint.getStrokeWidth();
            key.fMiterLimit = paint.getStrokeMiter();
            key.fJoin = paint.getStrokeJoin();
        }
        key.fHasBlur = maskFilter != nullptr;
        if (key.fHasBlur) {
            key.fBlurRec = blurRec;
        }
        key.fCanonicalColor = canonicalColor;
        key.fScalerContextFlags = SkTo<uint32_t>(strikeDevice.fScalerContextFlags);

        // Do any runs use direct drawing types?.
        key.fHasSomeDirectSubRuns = false;
        SkPoint glyphRunListLocation = glyphRunList.sourceBoundsWithOrigin().center();
        for (auto& run : glyphRunList) {
            SkScalar approximateDeviceTextSize =
                    SkFontPriv::ApproximateTransformedTextSize(run.font(), drawMatrix,
                                                               glyphRunListLocation);
            key.fHasSomeDirectSubRuns |=
                    strikeDevice.fSDFTControl->isDirect(approximateDeviceTextSize, paint,
                                                        drawMatrix);
        }

        if (key.fHasSomeDirectSubRuns) {
            // Store the fractional offset of the position. We know that the matrix can't be
            // perspective at this point.
            SkPoint mappedOrigin = drawMatrix.mapOrigin();
            key.fPositionMatrix = drawMatrix;
            key.fPositionMatrix.setTranslateX(
                    mappedOrigin.x() - SkScalarFloorToScalar(mappedOrigin.x()));
            key.fPositionMatrix.setTranslateY(
                    mappedOrigin.y() - SkScalarFloorToScalar(mappedOrigin.y()));
        } else {
            // For path and SDFT, the matrix doesn't matter.
            key.fPositionMatrix = SkMatrix::I();
        }
    }

    return {canCache, key};
}

bool TextBlob::Key::operator==(const TextBlob::Key& that) const {
    if (fUniqueID != that.fUniqueID) { return false; }
    if (fCanonicalColor != that.fCanonicalColor) { return false; }
    if (fStyle != that.fStyle) { return false; }
    if (fStyle != SkPaint::kFill_Style) {
        if (fFrameWidth != that.fFrameWidth ||
            fMiterLimit != that.fMiterLimit ||
            fJoin != that.fJoin) {
            return false;
        }
    }
    if (fPixelGeometry != that.fPixelGeometry) { return false; }
    if (fHasBlur != that.fHasBlur) { return false; }
    if (fHasBlur) {
        if (fBlurRec.fStyle != that.fBlurRec.fStyle || fBlurRec.fSigma != that.fBlurRec.fSigma) {
            return false;
        }
    }

    if (fScalerContextFlags != that.fScalerContextFlags) { return false; }

    // DirectSubRuns do not support perspective when used with a TextBlob. SDFT, Transformed,
    // Path, and Drawable do support perspective.
    if (fPositionMatrix.hasPerspective() && fHasSomeDirectSubRuns) { return false; }

    if (fHasSomeDirectSubRuns != that.fHasSomeDirectSubRuns) { return false; }

    if (fHasSomeDirectSubRuns) {
        auto [compatible, _] = can_use_direct(fPositionMatrix, that.fPositionMatrix);
        return compatible;
    }

    return true;
}

// -- TextBlob -----------------------------------------------------------------------------------
void TextBlob::operator delete(void* p) { ::operator delete(p); }
void* TextBlob::operator new(size_t) { SK_ABORT("All blobs are created by placement new."); }
void* TextBlob::operator new(size_t, void* p) { return p; }

TextBlob::~TextBlob() = default;

sk_sp<TextBlob> TextBlob::Make(const GlyphRunList& glyphRunList,
                               const SkPaint& paint,
                               const SkMatrix& positionMatrix,
                               SkStrikeDeviceInfo strikeDeviceInfo,
                               StrikeForGPUCacheInterface* strikeCache) {
    size_t subRunSizeHint = SubRunContainer::EstimateAllocSize(glyphRunList);
    auto [initializer, totalMemoryAllocated, alloc] =
            SubRunAllocator::AllocateClassMemoryAndArena<TextBlob>(subRunSizeHint);

    auto container = SubRunContainer::MakeInAlloc(
            glyphRunList, positionMatrix, paint,
            strikeDeviceInfo, strikeCache, &alloc, SubRunContainer::kAddSubRuns, "TextBlob");

    SkColor initialLuminance = SkPaintPriv::ComputeLuminanceColor(paint);
    sk_sp<TextBlob> blob = sk_sp<TextBlob>(initializer.initialize(std::move(alloc),
                                                                  std::move(container),
                                                                  totalMemoryAllocated,
                                                                  initialLuminance));
    return blob;
}

void TextBlob::addKey(const Key& key) {
    fKey = key;
}

bool TextBlob::canReuse(const SkPaint& paint, const SkMatrix& positionMatrix) const {
    // A singular matrix will create a TextBlob with no SubRuns, but unknown glyphs can also
    // cause empty runs. If there are no subRuns, then regenerate when the matrices don't match.
    if (fSubRuns->isEmpty() && fSubRuns->initialPosition() != positionMatrix) {
        return false;
    }

    // If we have LCD text then our canonical color will be set to transparent, in this case we have
    // to regenerate the blob on any color change
    // We use the grPaint to get any color filter effects
    if (fKey.fCanonicalColor == SK_ColorTRANSPARENT &&
        fInitialLuminance != SkPaintPriv::ComputeLuminanceColor(paint)) {
        return false;
    }

    return fSubRuns->canReuse(paint, positionMatrix);
}

const TextBlob::Key& TextBlob::key() const { return fKey; }

#if defined(SK_GANESH)
void TextBlob::draw(SkCanvas* canvas,
                    const GrClip* clip,
                    const SkMatrixProvider& viewMatrix,
                    SkPoint drawOrigin,
                    const SkPaint& paint,
                    skgpu::ganesh::SurfaceDrawContext* sdc) {
    fSubRuns->draw(canvas, clip, viewMatrix, drawOrigin, paint, this, sdc);
}
#endif

#if defined(SK_GRAPHITE)
void TextBlob::draw(SkCanvas* canvas,
                    SkPoint drawOrigin,
                    const SkPaint& paint,
                    skgpu::graphite::Device* device) {
    fSubRuns->draw(canvas, drawOrigin, paint, this, device);
}
#endif

#if GR_TEST_UTILS
struct SubRunContainerPeer {
    static const AtlasSubRun* getAtlasSubRun(const SubRunContainer& subRuns) {
        if (subRuns.isEmpty()) {
            return nullptr;
        }
        return subRuns.fSubRuns.front().testingOnly_atlasSubRun();
    }
};
#endif

const AtlasSubRun* TextBlob::testingOnlyFirstSubRun() const {
#if GR_TEST_UTILS
    return SubRunContainerPeer::getAtlasSubRun(*fSubRuns);
#else
    return nullptr;
#endif
}

TextBlob::TextBlob(SubRunAllocator&& alloc,
                   SubRunContainerOwner subRuns,
                   int totalMemorySize,
                   SkColor initialLuminance)
        : fAlloc{std::move(alloc)}
        , fSubRuns{std::move(subRuns)}
        , fSize(totalMemorySize)
        , fInitialLuminance{initialLuminance} { }

sk_sp<Slug> SkMakeSlugFromBuffer(SkReadBuffer& buffer, const SkStrikeClient* client) {
    return SlugImpl::MakeFromBuffer(buffer, client);
}
}  // namespace sktext::gpu

#if defined(SK_GANESH)
namespace skgpu::ganesh {
sk_sp<Slug>
Device::convertGlyphRunListToSlug(const sktext::GlyphRunList& glyphRunList,
                                  const SkPaint& initialPaint,
                                  const SkPaint& drawingPaint) {
    return SlugImpl::Make(this->asMatrixProvider(),
                          glyphRunList,
                          initialPaint,
                          drawingPaint,
                          this->strikeDeviceInfo(),
                          SkStrikeCache::GlobalStrikeCache());
}

void Device::drawSlug(SkCanvas* canvas, const Slug* slug, const SkPaint& drawingPaint) {
    const SlugImpl* slugImpl = static_cast<const SlugImpl*>(slug);
    auto matrixProvider = this->asMatrixProvider();
#if defined(SK_DEBUG)
    if (!fContext->priv().options().fSupportBilerpFromGlyphAtlas) {
        // We can draw a slug if the atlas has padding or if the creation matrix and the
        // drawing matrix are the same. If they are the same, then the Slug will use the direct
        // drawing code and not use bi-lerp.
        SkMatrix slugMatrix = slugImpl->initialPositionMatrix();
        SkMatrix positionMatrix = matrixProvider.localToDevice();
        positionMatrix.preTranslate(slugImpl->origin().x(), slugImpl->origin().y());
        SkASSERT(slugMatrix == positionMatrix);
    }
#endif
    slugImpl->surfaceDraw(
            canvas, this->clip(), matrixProvider, drawingPaint, fSurfaceDrawContext.get());
}

sk_sp<Slug> MakeSlug(const SkMatrixProvider& drawMatrix,
                     const sktext::GlyphRunList& glyphRunList,
                     const SkPaint& initialPaint,
                     const SkPaint& drawingPaint,
                     SkStrikeDeviceInfo strikeDeviceInfo,
                     sktext::StrikeForGPUCacheInterface* strikeCache) {
    return SlugImpl::Make(
            drawMatrix, glyphRunList, initialPaint, drawingPaint, strikeDeviceInfo, strikeCache);
}
}  // namespace skgpu::ganesh
#endif
