// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef FLUTTER_DISPLAY_LIST_GEOMETRY_REGION_H_
#define FLUTTER_DISPLAY_LIST_GEOMETRY_REGION_H_

#include "third_party/skia/include/core/SkRect.h"

#include <memory>
#include <vector>

namespace flutter {

/// Represents a region as a collection of non-overlapping rectangles.
/// Implements a subset of SkRegion functionality optimized for quickly
/// converting set of overlapping rectangles to non-overlapping rectangles.
class DlRegion {
 public:
  /// Creates an empty region.
  DlRegion() = default;

  /// Creates region by bulk adding the rectangles.
  /// Matches SkRegion::op(rect, SkRegion::kUnion_Op) behavior.
  explicit DlRegion(const std::vector<SkIRect>& rects);

  /// Creates region covering area of a rectangle.
  explicit DlRegion(const SkIRect& rect);

  DlRegion(const DlRegion&) = default;
  DlRegion(DlRegion&&) = default;

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

  /// Creates union region of region a and b.
  /// Matches SkRegion a; a.op(b, SkRegion::kUnion_Op) behavior.
  static DlRegion MakeUnion(const DlRegion& a, const DlRegion& b);

  /// Creates intersection region of region a and b.
  /// Matches SkRegion a; a.op(b, SkRegion::kIntersect_Op) behavior.
  static DlRegion MakeIntersection(const DlRegion& a, const DlRegion& b);

  /// Returns list of non-overlapping rectangles that cover current region.
  /// If |deband| is false, each span line will result in separate rectangles,
  /// closely matching SkRegion::Iterator behavior.
  /// If |deband| is true, matching rectangles from adjacent span lines will be
  /// merged into single rectangle.
  std::vector<SkIRect> getRects(bool deband = true) const;

  /// Returns maximum and minimum axis values of rectangles in this region.
  /// If region is empty returns SKIRect::MakeEmpty().
  const SkIRect& bounds() const { return bounds_; }

  /// Returns whether this region intersects with a rectangle.
  bool intersects(const SkIRect& rect) const;

  /// Returns whether this region intersects with another region.
  bool intersects(const DlRegion& region) const;

  /// Returns true if region is empty (contains no rectangles).
  bool isEmpty() const { return lines_.empty(); }

  /// Returns true if region is not empty and contains more than one rectangle.
  bool isComplex() const;

  /// Returns true if region can be represented by single rectangle or is
  /// empty.
  bool isSimple() const { return !isComplex(); }

 private:
  typedef std::uint32_t SpanChunkHandle;

  struct Span {
    int32_t left;
    int32_t right;

    Span() = default;
    Span(int32_t left, int32_t right) : left(left), right(right) {}
  };

  /// Holds spans for the region. Having custom allocated memory that doesn't
  /// do zero initialization every time the buffer gets resized improves
  /// performance measurably.
  class SpanBuffer {
   public:
    SpanBuffer() = default;
    SpanBuffer(const SpanBuffer&);
    SpanBuffer(SpanBuffer&& m);
    SpanBuffer& operator=(const SpanBuffer&);
    SpanBuffer& operator=(SpanBuffer&& m);

    void reserve(size_t capacity);
    size_t capacity() const { return capacity_; }

    SpanChunkHandle storeChunk(const Span* begin, const Span* end);
    size_t getChunkSize(SpanChunkHandle handle) const;
    void getSpans(SpanChunkHandle handle,
                  const DlRegion::Span*& begin,
                  const DlRegion::Span*& end) const;

    ~SpanBuffer();

   private:
    void setChunkSize(SpanChunkHandle handle, size_t size);

    size_t capacity_ = 0;
    size_t size_ = 0;

    // Spans for the region chunks. First span in each chunk contains the
    // chunk size.
    Span* spans_ = nullptr;
  };

  struct SpanLine {
    int32_t top;
    int32_t bottom;
    SpanChunkHandle chunk_handle;
  };

  void setRects(const std::vector<SkIRect>& rects);

  void appendLine(int32_t top,
                  int32_t bottom,
                  const Span* begin,
                  const Span* end);
  void appendLine(int32_t top,
                  int32_t bottom,
                  const SpanBuffer& buffer,
                  SpanChunkHandle handle) {
    const Span *begin, *end;
    buffer.getSpans(handle, begin, end);
    appendLine(top, bottom, begin, end);
  }

  typedef std::vector<Span> SpanVec;
  SpanLine makeLine(int32_t top, int32_t bottom, const SpanVec&);
  SpanLine makeLine(int32_t top,
                    int32_t bottom,
                    const Span* begin,
                    const Span* end);
  static size_t unionLineSpans(std::vector<Span>& res,
                               const SpanBuffer& a_buffer,
                               SpanChunkHandle a_handle,
                               const SpanBuffer& b_buffer,
                               SpanChunkHandle b_handle);
  static size_t intersectLineSpans(std::vector<Span>& res,
                                   const SpanBuffer& a_buffer,
                                   SpanChunkHandle a_handle,
                                   const SpanBuffer& b_buffer,
                                   SpanChunkHandle b_handle);

  bool spansEqual(SpanLine& line, const Span* begin, const Span* end) const;

  static bool spansIntersect(const Span* begin1,
                             const Span* end1,
                             const Span* begin2,
                             const Span* end2);

  static void getIntersectionIterators(
      const std::vector<SpanLine>& a_lines,
      const std::vector<SpanLine>& b_lines,
      std::vector<SpanLine>::const_iterator& a_it,
      std::vector<SpanLine>::const_iterator& b_it);

  std::vector<SpanLine> lines_;
  SkIRect bounds_ = SkIRect::MakeEmpty();
  SpanBuffer span_buffer_;
};

}  // namespace flutter

#endif  // FLUTTER_DISPLAY_LIST_GEOMETRY_REGION_H_
