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

#ifndef SkSVGTextPriv_DEFINED
#define SkSVGTextPriv_DEFINED

#include "include/core/SkContourMeasure.h"
#include "include/core/SkFont.h"
#include "include/core/SkMatrix.h"
#include "include/core/SkPaint.h"
#include "include/core/SkPoint.h"
#include "include/core/SkRefCnt.h"
#include "include/core/SkTypes.h"
#include "include/private/base/SkTArray.h"
#include "include/private/base/SkTo.h"
#include "modules/skshaper/include/SkShaper.h"

#include <cstddef>
#include <cstdint>
#include <functional>
#include <limits>
#include <memory>
#include <optional>
#include <vector>

class SkSVGLengthContext;
class SkSVGRenderContext;
class SkSVGTextContainer;
class SkSVGTextPath;
class SkString;
class SkTextBlob;
enum class SkSVGXmlSpace;
struct SkRSXform;

// SkSVGTextContext is responsible for sequencing input text chars into "chunks".
// A single text chunk can span multiple structural elements (<text>, <tspan>, etc),
// and per [1] new chunks are emitted
//
//   a) for each top level text element (<text>, <textPath>)
//   b) whenever a character with an explicit absolute position is encountered
//
// The implementation queues shaped run data until a full text chunk is resolved, at which
// point we have enough information to perform final alignment and rendering.
//
// [1] https://www.w3.org/TR/SVG11/text.html#TextLayoutIntroduction
class SkSVGTextContext final : SkShaper::RunHandler {
public:
    using ShapedTextCallback = std::function<void(const SkSVGRenderContext&,
                                                  const sk_sp<SkTextBlob>&,
                                                  const SkPaint*,
                                                  const SkPaint*)>;

    // Helper for encoding optional positional attributes.
    class PosAttrs {
    public:
        // TODO: rotate
        enum Attr : size_t {
            kX      = 0,
            kY      = 1,
            kDx     = 2,
            kDy     = 3,
            kRotate = 4,
        };

        float  operator[](Attr a) const { return fStorage[a]; }
        float& operator[](Attr a)       { return fStorage[a]; }

        bool has(Attr a) const { return fStorage[a] != kNone; }
        bool hasAny()    const {
            return this->has(kX)
                || this->has(kY)
                || this->has(kDx)
                || this->has(kDy)
                || this->has(kRotate);
        }

        void setImplicitRotate(bool imp) { fImplicitRotate = imp; }
        bool isImplicitRotate() const { return fImplicitRotate; }

    private:
        inline static constexpr auto kNone = std::numeric_limits<float>::infinity();

        float fStorage[5]     = { kNone, kNone, kNone, kNone, kNone };
        bool  fImplicitRotate = false;
    };

    // Helper for cascading position attribute resolution (x, y, dx, dy, rotate) [1]:
    //   - each text position element can specify an arbitrary-length attribute array
    //   - for each character, we look up a given attribute first in its local attribute array,
    //     then in the ancestor chain (cascading/fallback) - and return the first value encountered.
    //   - the lookup is based on character index relative to the text content subtree
    //     (i.e. the index crosses chunk boundaries)
    //
    // [1] https://www.w3.org/TR/SVG11/text.html#TSpanElementXAttribute
    class ScopedPosResolver {
    public:
        ScopedPosResolver(const SkSVGTextContainer&, const SkSVGLengthContext&, SkSVGTextContext*,
                          size_t);

        ScopedPosResolver(const SkSVGTextContainer&, const SkSVGLengthContext&, SkSVGTextContext*);

        ~ScopedPosResolver();

        PosAttrs resolve(size_t charIndex) const;

    private:
        SkSVGTextContext*         fTextContext;
        const ScopedPosResolver*  fParent;          // parent resolver (fallback)
        const size_t              fCharIndexOffset; // start index for the current resolver
        const std::vector<float>  fX,
                                  fY,
                                  fDx,
                                  fDy;
        const std::vector<float>& fRotate;

        // cache for the last known index with explicit positioning
        mutable size_t           fLastPosIndex = std::numeric_limits<size_t>::max();

    };

    SkSVGTextContext(const SkSVGRenderContext&,
                     const ShapedTextCallback&,
                     const SkSVGTextPath* = nullptr);
    ~SkSVGTextContext() override;

    // Shape and queue codepoints for final alignment.
    void shapeFragment(const SkString&, const SkSVGRenderContext&, SkSVGXmlSpace);

    // Perform final adjustments and push shaped blobs to the callback.
    void flushChunk(const SkSVGRenderContext& ctx);

    const ShapedTextCallback& getCallback() const { return fCallback; }

private:
    struct PositionAdjustment {
        SkVector offset;
        float    rotation;
    };

    struct ShapeBuffer {
        skia_private::STArray<128, char              , true> fUtf8;
        // per-utf8-char cumulative pos adjustments
        skia_private::STArray<128, PositionAdjustment, true> fUtf8PosAdjust;

        void reserve(size_t size) {
            fUtf8.reserve_exact(fUtf8.size() + SkToInt(size));
            fUtf8PosAdjust.reserve_exact(fUtf8PosAdjust.size() + SkToInt(size));
        }

        void reset() {
            fUtf8.clear();
            fUtf8PosAdjust.clear();
        }

        void append(SkUnichar, PositionAdjustment);
    };

    struct RunRec {
        SkFont                                font;
        std::unique_ptr<SkPaint>              fillPaint,
                                              strokePaint;
        std::unique_ptr<SkGlyphID[]>          glyphs;        // filled by SkShaper
        std::unique_ptr<SkPoint[]>            glyphPos;      // filled by SkShaper
        std::unique_ptr<PositionAdjustment[]> glyhPosAdjust; // deferred positioning adjustments
        size_t                                glyphCount;
        SkVector                              advance;
    };

    // Caches path information to accelerate position lookups.
    class PathData {
    public:
        PathData(const SkSVGRenderContext&, const SkSVGTextPath&);

        SkMatrix getMatrixAt(float offset) const;

        float length() const { return fLength; }

    private:
        std::vector<sk_sp<SkContourMeasure>> fContours;
        float                                fLength = 0; // total path length
    };

    void shapePendingBuffer(const SkSVGRenderContext&, const SkFont&);

    SkRSXform computeGlyphXform(SkGlyphID, const SkFont&, const SkPoint& glyph_pos,
                                const PositionAdjustment&) const;

    // SkShaper callbacks
    void beginLine() override {}
    void runInfo(const RunInfo&) override {}
    void commitRunInfo() override {}
    Buffer runBuffer(const RunInfo& ri) override;
    void commitRunBuffer(const RunInfo& ri) override;
    void commitLine() override;

    // http://www.w3.org/TR/SVG11/text.html#TextLayout
    const SkSVGRenderContext&       fRenderContext; // original render context
    const ShapedTextCallback&       fCallback;
    std::unique_ptr<SkShaper>       fShaper;
    std::vector<RunRec>             fRuns;
    const ScopedPosResolver*        fPosResolver = nullptr;
    std::unique_ptr<PathData>       fPathData;

    // shaper state
    ShapeBuffer                     fShapeBuffer;
    std::vector<uint32_t>           fShapeClusterBuffer;

    // chunk state
    SkPoint                         fChunkPos     = {0,0}; // current text chunk position
    SkVector                        fChunkAdvance = {0,0}; // cumulative advance
    float                           fChunkAlignmentFactor; // current chunk alignment

    // tracks the global text subtree char index (cross chunks).  Used for position resolution.
    size_t                          fCurrentCharIndex = 0;

    // cached for access from SkShaper callbacks.
    std::optional<SkPaint>          fCurrentFill;
    std::optional<SkPaint>          fCurrentStroke;

    bool                            fPrevCharSpace = true; // WS filter state
    bool                            fForcePrimitiveShaping = false;
};

#endif // SkSVGTextPriv_DEFINED
