// Copyright 2019 Google LLC
#ifndef TextLine_DEFINED
#define TextLine_DEFINED

#include "include/core/SkPoint.h"
#include "include/core/SkRect.h"
#include "include/core/SkScalar.h"
#include "include/private/base/SkTArray.h"
#include "modules/skparagraph/include/DartTypes.h"
#include "modules/skparagraph/include/Metrics.h"
#include "modules/skparagraph/include/ParagraphPainter.h"
#include "modules/skparagraph/include/TextStyle.h"
#include "modules/skparagraph/src/Run.h"
#include "src/base/SkBitmaskEnum.h"

#include <stddef.h>
#include <functional>
#include <memory>
#include <vector>

class SkString;

namespace skia {
namespace textlayout {

class ParagraphImpl;

class TextLine {
public:

    struct ClipContext {
      const Run* run;
      size_t pos;
      size_t size;
      SkScalar fTextShift; // Shifts the text inside the run so it's placed at the right position
      SkRect clip;
      SkScalar fExcludedTrailingSpaces;
      bool clippingNeeded;
    };

    enum TextAdjustment {
        GlyphCluster = 0x01,    // All text producing glyphs pointing to the same ClusterIndex
        GlyphemeCluster = 0x02, // base glyph + all attached diacritics
        Grapheme = 0x04,        // Text adjusted to graphemes
        GraphemeGluster = 0x05, // GlyphCluster & Grapheme
    };

    TextLine() = default;
    TextLine(const TextLine&) = delete;
    TextLine& operator=(const TextLine&) = delete;
    TextLine(TextLine&&) = default;
    TextLine& operator=(TextLine&&) = default;
    ~TextLine() = default;

    TextLine(ParagraphImpl* owner,
             SkVector offset,
             SkVector advance,
             BlockRange blocks,
             TextRange textExcludingSpaces,
             TextRange text,
             TextRange textIncludingNewlines,
             ClusterRange clusters,
             ClusterRange clustersWithGhosts,
             SkScalar widthWithSpaces,
             InternalLineMetrics sizes);

    TextRange trimmedText() const { return fTextExcludingSpaces; }
    TextRange textWithNewlines() const { return fTextIncludingNewlines; }
    TextRange text() const { return fText; }
    ClusterRange clusters() const { return fClusterRange; }
    ClusterRange clustersWithSpaces() const { return fGhostClusterRange; }
    Run* ellipsis() const { return fEllipsis.get(); }
    InternalLineMetrics sizes() const { return fSizes; }
    bool empty() const { return fTextExcludingSpaces.empty(); }

    SkScalar spacesWidth() const { return fWidthWithSpaces - width(); }
    SkScalar height() const { return fAdvance.fY; }
    SkScalar width() const {
        return fAdvance.fX + (fEllipsis != nullptr ? fEllipsis->fAdvance.fX : 0);
    }
    SkScalar widthWithoutEllipsis() const { return fAdvance.fX; }
    SkVector offset() const;

    SkScalar alphabeticBaseline() const { return fSizes.alphabeticBaseline(); }
    SkScalar ideographicBaseline() const { return fSizes.ideographicBaseline(); }
    SkScalar baseline() const { return fSizes.baseline(); }

    using RunVisitor = std::function<bool(
            const Run* run, SkScalar runOffset, TextRange textRange, SkScalar* width)>;
    void iterateThroughVisualRuns(bool includingGhostSpaces, const RunVisitor& runVisitor) const;
    using RunStyleVisitor = std::function<void(
            TextRange textRange, const TextStyle& style, const ClipContext& context)>;
    SkScalar iterateThroughSingleRunByStyles(TextAdjustment textAdjustment,
                                             const Run* run,
                                             SkScalar runOffset,
                                             TextRange textRange,
                                             StyleType styleType,
                                             const RunStyleVisitor& visitor) const;

    using ClustersVisitor = std::function<bool(const Cluster* cluster, ClusterIndex index, bool ghost)>;
    void iterateThroughClustersInGlyphsOrder(bool reverse,
                                             bool includeGhosts,
                                             const ClustersVisitor& visitor) const;

    void format(TextAlign align, SkScalar maxWidth);
    void paint(ParagraphPainter* painter, SkScalar x, SkScalar y);
    void visit(SkScalar x, SkScalar y);
    void ensureTextBlobCachePopulated();

    void createEllipsis(SkScalar maxWidth, const SkString& ellipsis, bool ltr);

    // For testing internal structures
    void scanStyles(StyleType style, const RunStyleVisitor& visitor);

    void setMaxRunMetrics(const InternalLineMetrics& metrics) { fMaxRunMetrics = metrics; }
    InternalLineMetrics getMaxRunMetrics() const { return fMaxRunMetrics; }

    bool isFirstLine() const;
    bool isLastLine() const;
    void getRectsForRange(TextRange textRange,
                          RectHeightStyle rectHeightStyle,
                          RectWidthStyle rectWidthStyle,
                          std::vector<TextBox>& boxes) const;
    void getRectsForPlaceholders(std::vector<TextBox>& boxes);
    PositionWithAffinity getGlyphPositionAtCoordinate(SkScalar dx);

    ClipContext measureTextInsideOneRun(TextRange textRange,
                                        const Run* run,
                                        SkScalar runOffsetInLine,
                                        SkScalar textOffsetInRunInLine,
                                        bool includeGhostSpaces,
                                        TextAdjustment textAdjustment) const;

    LineMetrics getMetrics() const;

    SkRect extendHeight(const ClipContext& context) const;

    void shiftVertically(SkScalar shift) { fOffset.fY += shift; }

    void setAscentStyle(LineMetricStyle style) { fAscentStyle = style; }
    void setDescentStyle(LineMetricStyle style) { fDescentStyle = style; }

    bool endsWithHardLineBreak() const;

private:
    std::unique_ptr<Run> shapeEllipsis(const SkString& ellipsis, const Cluster* cluster);
    void justify(SkScalar maxWidth);

    void buildTextBlob(TextRange textRange, const TextStyle& style, const ClipContext& context);
    void paintBackground(ParagraphPainter* painter,
                         SkScalar x,
                         SkScalar y,
                         TextRange textRange,
                         const TextStyle& style,
                         const ClipContext& context) const;
    void paintShadow(ParagraphPainter* painter,
                     SkScalar x,
                     SkScalar y,
                     TextRange textRange,
                     const TextStyle& style,
                     const ClipContext& context) const;
    void paintDecorations(ParagraphPainter* painter,
                          SkScalar x,
                          SkScalar y,
                          TextRange textRange,
                          const TextStyle& style,
                          const ClipContext& context) const;

    void shiftCluster(const Cluster* cluster, SkScalar shift, SkScalar prevShift);

    ParagraphImpl* fOwner;
    BlockRange fBlockRange;
    TextRange fTextExcludingSpaces;
    TextRange fText;
    TextRange fTextIncludingNewlines;
    ClusterRange fClusterRange;
    ClusterRange fGhostClusterRange;
    // Avoid the malloc/free in the common case of one run per line
    skia_private::STArray<1, size_t, true> fRunsInVisualOrder;
    SkVector fAdvance;                  // Text size
    SkVector fOffset;                   // Text position
    SkScalar fShift;                    // Let right
    SkScalar fWidthWithSpaces;
    std::unique_ptr<Run> fEllipsis;     // In case the line ends with the ellipsis
    InternalLineMetrics fSizes;                 // Line metrics as a max of all run metrics and struts
    InternalLineMetrics fMaxRunMetrics;         // No struts - need it for GetRectForRange(max height)
    bool fHasBackground;
    bool fHasShadows;
    bool fHasDecorations;

    LineMetricStyle fAscentStyle;
    LineMetricStyle fDescentStyle;

    struct TextBlobRecord {
        void paint(ParagraphPainter* painter, SkScalar x, SkScalar y);

        sk_sp<SkTextBlob> fBlob;
        SkPoint fOffset = SkPoint::Make(0.0f, 0.0f);
        ParagraphPainter::SkPaintOrID fPaint;
        SkRect fBounds = SkRect::MakeEmpty();
        bool fClippingNeeded = false;
        SkRect fClipRect = SkRect::MakeEmpty();

        // Extra fields only used for the (experimental) visitor
        const Run* fVisitor_Run;
        size_t     fVisitor_Pos;
    };
    bool fTextBlobCachePopulated;
public:
    std::vector<TextBlobRecord> fTextBlobCache;
};
}  // namespace textlayout
}  // namespace skia

namespace sknonstd {
    template <> struct is_bitmask_enum<skia::textlayout::TextLine::TextAdjustment> : std::true_type {};
}  // namespace sknonstd

#endif  // TextLine_DEFINED
