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

#ifndef skgpu_ganesh_AtlasTextOp_DEFINED
#define skgpu_ganesh_AtlasTextOp_DEFINED

#include "include/core/SkColor.h"
#include "include/core/SkMatrix.h"
#include "include/core/SkRect.h"
#include "include/core/SkRefCnt.h"
#include "include/core/SkString.h"
#include "include/private/SkColorData.h"
#include "include/private/base/SkAssert.h"
#include "include/private/base/SkPoint_impl.h"
#include "include/private/gpu/ganesh/GrTypesPriv.h"
#include "src/gpu/AtlasTypes.h"
#include "src/gpu/ganesh/GrAppliedClip.h"
#include "src/gpu/ganesh/GrBuffer.h"
#include "src/gpu/ganesh/GrCaps.h"
#include "src/gpu/ganesh/GrColorInfo.h"
#include "src/gpu/ganesh/GrColorSpaceXform.h"
#include "src/gpu/ganesh/GrProcessorSet.h"
#include "src/gpu/ganesh/effects/GrDistanceFieldGeoProc.h"
#include "src/gpu/ganesh/ops/GrMeshDrawOp.h"
#include "src/gpu/ganesh/ops/GrOp.h"

#include <cstddef>
#include <cstdint>
#include <utility>

class GrDstProxyView;
class GrGeometryProcessor;
class GrMeshDrawTarget;
class GrOpFlushState;
class GrPaint;
class GrProgramInfo;
class GrRecordingContext;
class GrSurfaceProxy;
class GrSurfaceProxyView;
class SkArenaAlloc;
class SkFont;
class SkPaint;
enum class GrXferBarrierFlags;
struct GrShaderCaps;

namespace sktext { namespace gpu { class AtlasSubRun; } }

namespace skgpu::ganesh {
class SurfaceDrawContext;

class AtlasTextOp final : public GrMeshDrawOp {
public:
    DEFINE_OP_CLASS_ID

    ~AtlasTextOp() override {
        for (const Geometry* g = fHead; g != nullptr;) {
            const Geometry* next = g->fNext;
            g->~Geometry();
            g = next;
        }
    }

    void* operator new(size_t s);
    void operator delete(void* b) noexcept;
    static void ClearCache();

    struct Geometry {
        Geometry(const sktext::gpu::AtlasSubRun& subRun,
                 const SkMatrix& drawMatrix,
                 SkPoint drawOrigin,
                 SkIRect clipRect,
                 sk_sp<SkRefCnt>&& supportData,
                 const SkPMColor4f& color)
            : fSubRun{subRun}
            , fSupportDataKeepAlive{std::move(supportData)}
            , fDrawMatrix{drawMatrix}
            , fDrawOrigin{drawOrigin}
            , fClipRect{clipRect}
            , fColor{color} {
                SkASSERT(fSupportDataKeepAlive != nullptr);
        }

        static Geometry* Make(const sktext::gpu::AtlasSubRun& subRun,
                              const SkMatrix& drawMatrix,
                              SkPoint drawOrigin,
                              SkIRect clipRect,
                              sk_sp<SkRefCnt>&& supportData,
                              const SkPMColor4f& color,
                              SkArenaAlloc* alloc);

        void fillVertexData(void* dst, int offset, int count) const;

        const sktext::gpu::AtlasSubRun& fSubRun;

        // Keep the TextBlob or Slug alive until the op is deleted.
        sk_sp<SkRefCnt> fSupportDataKeepAlive;

        const SkMatrix fDrawMatrix;
        const SkPoint fDrawOrigin;

        // fClipRect is only used in the DirectMaskSubRun case to do geometric clipping.
        // TransformedMaskSubRun, and SDFTSubRun don't use this field, and expect an empty rect.
        const SkIRect fClipRect;

        // Color is updated after processor analysis if it was determined the shader resolves to
        // a constant color that we then evaluate on the CPU.
        // TODO: This can be made const once processor analysis is separated from op creation.
        SkPMColor4f fColor;
        Geometry* fNext{nullptr};
    };

    const char* name() const override { return "AtlasTextOp"; }

    void visitProxies(const GrVisitProxyFunc&) const override;

    FixedFunctionFlags fixedFunctionFlags() const override;

    GrProcessorSet::Analysis finalize(const GrCaps&, const GrAppliedClip*, GrClampType) override;

    enum class MaskType : uint32_t {
        kGrayscaleCoverage,
        kLCDCoverage,
        kColorBitmap,
#if !defined(SK_DISABLE_SDF_TEXT)
        kAliasedDistanceField,
        kGrayscaleDistanceField,
        kLCDDistanceField,
        kLCDBGRDistanceField,

        kLast = kLCDBGRDistanceField
#else
        kLast = kColorBitmap
#endif
    };
    inline static constexpr int kMaskTypeCount = static_cast<int>(MaskType::kLast) + 1;

#if defined(GR_TEST_UTILS)
    static GrOp::Owner CreateOpTestingOnly(skgpu::ganesh::SurfaceDrawContext*,
                                           const SkPaint&,
                                           const SkFont&,
                                           const SkMatrix&,
                                           const char* text,
                                           int x,
                                           int y);
#endif

private:
    friend class GrOp; // for ctor

    struct FlushInfo {
        sk_sp<const GrBuffer> fVertexBuffer;
        sk_sp<const GrBuffer> fIndexBuffer;
        GrGeometryProcessor*  fGeometryProcessor;
        const GrSurfaceProxy** fPrimProcProxies;
        int fGlyphsToFlush = 0;
        int fVertexOffset = 0;
        int fNumDraws = 0;
    };

    AtlasTextOp(MaskType maskType,
                bool needsTransform,
                int glyphCount,
                SkRect deviceRect,
                Geometry* geo,
                const GrColorInfo& dstColorInfo,
                GrPaint&& paint);

    AtlasTextOp(MaskType maskType,
                bool needsTransform,
                int glyphCount,
                SkRect deviceRect,
                SkColor luminanceColor,
                bool useGammaCorrectDistanceTable,
                uint32_t DFGPFlags,
                Geometry* geo,
                GrPaint&& paint);

    GrProgramInfo* programInfo() override {
        // TODO [PI]: implement
        return nullptr;
    }

    void addGeometry(Geometry* geometry) {
        *fTail = geometry;
        // The geometry may have many entries. Find the end.
        do {
            fTail = &(*fTail)->fNext;
        } while (*fTail != nullptr);
    }

    void onCreateProgramInfo(const GrCaps*,
                             SkArenaAlloc*,
                             const GrSurfaceProxyView& writeView,
                             bool usesMSAASurface,
                             GrAppliedClip&&,
                             const GrDstProxyView&,
                             GrXferBarrierFlags renderPassXferBarriers,
                             GrLoadOp colorLoadOp) override {
        // We cannot surface the AtlasTextOp's programInfo at record time. As currently
        // implemented, the GP is modified at flush time based on the number of pages in the
        // atlas.
    }

    void onPrePrepareDraws(GrRecordingContext*,
                           const GrSurfaceProxyView& writeView,
                           GrAppliedClip*,
                           const GrDstProxyView&,
                           GrXferBarrierFlags renderPassXferBarriers,
                           GrLoadOp colorLoadOp) override {
        // TODO [PI]: implement
    }

    void onPrepareDraws(GrMeshDrawTarget*) override;
    void onExecute(GrOpFlushState*, const SkRect& chainBounds) override;

#if defined(GR_TEST_UTILS)
    SkString onDumpInfo() const override;
#endif

    skgpu::MaskFormat maskFormat() const {
        switch (this->maskType()) {
            case MaskType::kLCDCoverage:
                return skgpu::MaskFormat::kA565;
            case MaskType::kColorBitmap:
                return skgpu::MaskFormat::kARGB;
            case MaskType::kGrayscaleCoverage:
#if !defined(SK_DISABLE_SDF_TEXT)
            case MaskType::kAliasedDistanceField:
            case MaskType::kGrayscaleDistanceField:
            case MaskType::kLCDDistanceField:
            case MaskType::kLCDBGRDistanceField:
#endif
                return skgpu::MaskFormat::kA8;
        }
        // SkUNREACHABLE;
        return skgpu::MaskFormat::kA8;
    }

#if !defined(SK_DISABLE_SDF_TEXT)
    bool usesDistanceFields() const {
        return MaskType::kAliasedDistanceField == this->maskType() ||
               MaskType::kGrayscaleDistanceField == this->maskType() ||
               MaskType::kLCDDistanceField == this->maskType() ||
               MaskType::kLCDBGRDistanceField == this->maskType();
    }

    bool isLCD() const {
        return MaskType::kLCDCoverage == this->maskType() ||
               MaskType::kLCDDistanceField == this->maskType() ||
               MaskType::kLCDBGRDistanceField == this->maskType();
    }
#else
    bool isLCD() const {
        return MaskType::kLCDCoverage == this->maskType();
    }
#endif

    inline void createDrawForGeneratedGlyphs(
            GrMeshDrawTarget* target, FlushInfo* flushInfo) const;

    MaskType maskType() const { return static_cast<MaskType>(fMaskType); }

    CombineResult onCombineIfPossible(GrOp* t, SkArenaAlloc*, const GrCaps& caps) override;

#if !defined(SK_DISABLE_SDF_TEXT)
    GrGeometryProcessor* setupDfProcessor(SkArenaAlloc*,
                                          const GrShaderCaps&,
                                          const SkMatrix& localMatrix,
                                          const GrSurfaceProxyView* views,
                                          unsigned int numActiveViews) const;
#endif

    GrProcessorSet fProcessors;
    int fNumGlyphs; // Sum of glyphs in each geometry's subrun

    // All combinable atlas ops have equal bit field values
    uint32_t fDFGPFlags                    : 10; // Distance field properties
    uint32_t fMaskType                     : 3;  // MaskType
    uint32_t fUsesLocalCoords              : 1;  // Filled in post processor analysis
    uint32_t fNeedsGlyphTransform          : 1;
    uint32_t fHasPerspective               : 1;  // True if perspective affects draw
    uint32_t fUseGammaCorrectDistanceTable : 1;
    static_assert(kMaskTypeCount <= 8, "MaskType does not fit in 3 bits");
#if !defined(SK_DISABLE_SDF_TEXT)
    static_assert(kInvalid_DistanceFieldEffectFlag <= (1 << 9), "DFGP Flags do not fit in 10 bits");
#endif

    // Only needed for color emoji
    sk_sp<GrColorSpaceXform> fColorSpaceXform;

    // Only used for distance fields; per-channel luminance for LCD, or gamma-corrected luminance
    // for single-channel distance fields.
    const SkColor fLuminanceColor{0};

    Geometry* fHead{nullptr};
    Geometry** fTail{&fHead};

    using INHERITED = GrMeshDrawOp;
};

} // namespace skgpu::ganesh

#endif // skgpu_ganesh_AtlasTextOp_DEFINED
