// Copyright 2021 Google LLC.
#ifndef TextLine_DEFINED
#define TextLine_DEFINED

#include "experimental/sktext/include/Types.h"
#include "include/core/SkFont.h"
#include "include/core/SkFontMetrics.h"

namespace skia {
namespace text {

class TextMetrics {

public:
  TextMetrics() {
      clean();
  }

  TextMetrics(const SkFont& font) {

      SkFontMetrics metrics;
      font.getMetrics(&metrics);
      fAscent = metrics.fAscent;
      fDescent = metrics.fDescent;
      fLeading = metrics.fLeading;
  }

  TextMetrics(const TextMetrics&) = default;

  TextMetrics& operator=(const TextMetrics&) = default;

  void merge(TextMetrics tail) {
      this->fAscent = std::min(this->fAscent, tail.fAscent);
      this->fDescent = std::max(this->fDescent, tail.fDescent);
      this->fLeading = std::max(this->fLeading, tail.fLeading);
  }

  void clean() {
      this->fAscent = 0;
      this->fDescent = 0;
      this->fLeading = 0;
  }

  SkScalar height() const {
      return this->fDescent - this->fAscent + this->fLeading;
  }

    SkScalar baseline() const {
          return - this->fAscent + this->fLeading / 2;
    }

    SkScalar above() const { return - this->fAscent + this->fLeading / 2; }
    SkScalar below() const { return this->fDescent + this->fLeading / 2; }

private:
    SkScalar fAscent;
    SkScalar fDescent;
    SkScalar fLeading;
};

class GlyphPos {
public:

    GlyphPos() : fRunIndex(EMPTY_INDEX), fGlyphIndex(EMPTY_INDEX) { }
    GlyphPos(size_t runIndex, size_t glyphIndex) : fRunIndex(runIndex), fGlyphIndex(glyphIndex) { }

    bool operator==(const GlyphPos& other) const {
        return this->fRunIndex == other.fRunIndex && this->fGlyphIndex == other.fGlyphIndex;
    }

    size_t runIndex() const { return fRunIndex; }
    size_t glyphIndex() const { return fGlyphIndex; }
    void setGlyphIndex(size_t index) { fGlyphIndex = index; }

    bool isEmpty() const { return fRunIndex == EMPTY_INDEX; }

private:
    size_t fRunIndex;
    size_t fGlyphIndex;
};

class Stretch {
public:

    Stretch() : fGlyphStart(), fGlyphEnd(), fWidth(0), fTextRange(EMPTY_RANGE), fTextMetrics() { }

    Stretch(GlyphPos glyphStart, size_t textIndex, const TextMetrics& metrics)
        : fGlyphStart(glyphStart)
        , fGlyphEnd(glyphStart)
        , fWidth(0.0)
        , fTextRange(textIndex, textIndex)
        , fTextMetrics(metrics) { }

    Stretch(RunIndex runIndex, GlyphRange glyphRange, TextRange textRange, SkScalar width, const TextMetrics& metrics)
        : fGlyphStart(runIndex, glyphRange.fStart)
        , fGlyphEnd(runIndex, glyphRange.fEnd)
        , fWidth(width)
        , fTextRange(textRange)
        , fTextMetrics(metrics) { }

    Stretch(const Stretch&) = default;
    Stretch(Stretch&&) = default;
    Stretch& operator=(Stretch&&) = default;
    Stretch& operator=(const Stretch&) = default;

    bool isEmpty() const {
        if (fGlyphStart.isEmpty() || fGlyphEnd.isEmpty()) {
            return true;
        } else {
            return fGlyphStart == fGlyphEnd;
        }
    }

    void clean() {
        fGlyphStart = fGlyphEnd;
        fTextRange.fStart = fTextRange.fEnd;
        fWidth = 0.0f;
        fTextMetrics.clean();
    }

    void moveTo(Stretch& tail) {

        if (tail.isEmpty()) {
            return;
        }

        if (this->isEmpty()) {
            if (!tail.isEmpty()) {
                this->fGlyphStart = tail.fGlyphStart;
                this->fGlyphEnd = tail.fGlyphEnd;
                this->fWidth = tail.fWidth;
                this->fTextRange = tail.fTextRange;
                this->fTextMetrics = tail.fTextMetrics;
            }
            tail.clean();
            return;
        }

        SkASSERT(this->fGlyphEnd.runIndex() != tail.fGlyphStart.runIndex() ||
                       this->fGlyphEnd.glyphIndex() == tail.fGlyphStart.glyphIndex());
        this->fGlyphEnd = tail.fGlyphEnd;
        this->fWidth += tail.fWidth;
        this->fTextRange.merge(tail.fTextRange);
        this->fTextMetrics.merge(tail.fTextMetrics);
        tail.clean();
    }

    void finish(size_t glyphIndex, size_t textIndex, SkScalar width) {
        this->fTextRange.fEnd = textIndex;
        this->fGlyphEnd.setGlyphIndex(glyphIndex);
        this->fWidth = width;
    }

    SkScalar width() const { return fWidth; }
    TextRange textRange() const { return fTextRange; }
    void setTextRange(TextRange range) { fTextRange = range; }

    const TextMetrics& textMetrics() const { return fTextMetrics; }
    GlyphPos glyphStart() const { return fGlyphStart; }
    GlyphPos glyphEnd() const { return fGlyphEnd; }
    size_t glyphStartIndex() const { return fGlyphStart.glyphIndex(); }
    size_t textStart() const { return fTextRange.fStart; }

private:
    GlyphPos fGlyphStart;
    GlyphPos fGlyphEnd;
    SkScalar fWidth;
    TextRange fTextRange;
    TextMetrics fTextMetrics;
};

class LogicalLine {
public:
    LogicalLine(const Stretch& stretch, const Stretch& spaces, SkScalar verticalOffset, bool hardLineBreak);
    ~LogicalLine() = default;

    TextMetrics getMetrics() const { return fTextMetrics; }
    GlyphPos glyphStart() const { return fTextStart; }
    GlyphPos glyphEnd() const { return fTextEnd; }
    GlyphPos glyphTrailingEnd() const { return fWhitespacesEnd; }
    SkScalar width() const { return fTextWidth; }
    SkScalar withWithTrailingSpaces() const { return fTextWidth + fSpacesWidth; }
    SkScalar horizontalOffset() const { return fHorizontalOffset; }
    SkScalar verticalOffset() const { return fVerticalOffset; }
    SkScalar height() const { return fTextMetrics.height(); }
    SkScalar baseline() const { return fTextMetrics.baseline(); }
    TextRange text() const { return fText; }
    TextRange whitespaces() const { return fWhitespaces; }
    bool isHardLineBreak() const { return fHardLineBreak; }

private:
    friend class WrappedText;

    GlyphPos fTextStart;
    GlyphPos fTextEnd;
    GlyphPos fWhitespacesEnd;
    TextRange fText;
    TextRange fWhitespaces;
    SkScalar fTextWidth;
    SkScalar fSpacesWidth;
    SkScalar fHorizontalOffset;
    SkScalar fVerticalOffset;
    TextMetrics fTextMetrics;
    bool fHardLineBreak;
};

} // namespace text
} // namespace skia
#endif
