// Copyright 2016 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "third_party/blink/renderer/platform/testing/picture_matchers.h"

#include <utility>

#include "third_party/blink/renderer/platform/wtf/text/wtf_string.h"
#include "third_party/blink/renderer/platform/wtf/vector.h"
#include "third_party/skia/include/core/SkCanvas.h"
#include "third_party/skia/include/core/SkPicture.h"
#include "ui/gfx/geometry/rect_conversions.h"
#include "ui/gfx/geometry/skia_conversions.h"

namespace blink {

namespace {

class DrawsRectangleCanvas : public SkCanvas {
 public:
  DrawsRectangleCanvas()
      : SkCanvas(800, 600),
        save_count_(0),
        alpha_(255),
        alpha_save_layer_count_(-1) {}
  const Vector<RectWithColor>& RectsWithColor() const { return rects_; }

  void onDrawRect(const SkRect& rect, const SkPaint& paint) override {
    SkPoint quad[4];
    getTotalMatrix().mapRectToQuad(quad, rect);

    SkRect device_rect = SkRect::BoundsOrEmpty(quad);
    SkIRect device_clip_bounds;
    gfx::RectF clipped_rect;
    if (getDeviceClipBounds(&device_clip_bounds) &&
        device_rect.intersect(SkRect::Make(device_clip_bounds)))
      clipped_rect = gfx::SkRectToRectF(device_rect);

    unsigned paint_alpha = static_cast<unsigned>(paint.getAlpha());
    SkPaint paint_with_alpha(paint);
    paint_with_alpha.setAlpha(static_cast<U8CPU>(alpha_ * paint_alpha / 255));
    // TODO(https://crbug.com/1351544): This class should use SkColor4f.
    Color color = Color::FromSkColor(paint_with_alpha.getColor());

    rects_.emplace_back(clipped_rect, color);
    SkCanvas::onDrawRect(rect, paint);
  }

  SkCanvas::SaveLayerStrategy getSaveLayerStrategy(
      const SaveLayerRec& rec) override {
    save_count_++;
    unsigned layer_alpha = static_cast<unsigned>(rec.fPaint->getAlpha());
    if (layer_alpha < 255) {
      DCHECK_EQ(alpha_save_layer_count_, -1);
      alpha_save_layer_count_ = save_count_;
      alpha_ = layer_alpha;
    }
    return SkCanvas::getSaveLayerStrategy(rec);
  }

  void willSave() override {
    save_count_++;
    SkCanvas::willSave();
  }

  void willRestore() override {
    DCHECK_GT(save_count_, 0);
    if (alpha_save_layer_count_ == save_count_) {
      alpha_ = 255;
      alpha_save_layer_count_ = -1;
    }
    save_count_--;
    SkCanvas::willRestore();
  }

 private:
  Vector<RectWithColor> rects_;
  int save_count_;
  unsigned alpha_;
  int alpha_save_layer_count_;
};

class DrawsRectanglesMatcher
    : public testing::MatcherInterface<const SkPicture&> {
 public:
  DrawsRectanglesMatcher(const Vector<RectWithColor>& rects_with_color)
      : rects_with_color_(rects_with_color) {}

  bool MatchAndExplain(const SkPicture& picture,
                       testing::MatchResultListener* listener) const override {
    DrawsRectangleCanvas canvas;
    picture.playback(&canvas);
    const auto& actual_rects = canvas.RectsWithColor();
    if (actual_rects.size() != rects_with_color_.size()) {
      *listener << "which draws " << actual_rects.size() << " rects";
      return false;
    }

    for (unsigned index = 0; index < actual_rects.size(); index++) {
      const auto& actual_rect_with_color = actual_rects[index];
      const auto& expect_rect_with_color = rects_with_color_[index];

      if (gfx::ToEnclosingRect(actual_rect_with_color.rect) !=
              gfx::ToEnclosingRect(expect_rect_with_color.rect) ||
          actual_rect_with_color.color != expect_rect_with_color.color) {
        if (listener->IsInterested()) {
          *listener << "at index " << index << " which draws "
                    << actual_rect_with_color.rect.ToString() << " with color "
                    << actual_rect_with_color.color.SerializeAsCSSColor()
                    << "\n";
        }
        return false;
      }
    }

    return true;
  }

  void DescribeTo(::std::ostream* os) const override {
    *os << "\n";
    for (unsigned index = 0; index < rects_with_color_.size(); index++) {
      const auto& rect_with_color = rects_with_color_[index];
      *os << "at index " << index << " rect draws "
          << rect_with_color.rect.ToString() << " with color "
          << rect_with_color.color.SerializeAsCSSColor() << "\n";
    }
  }

 private:
  const Vector<RectWithColor> rects_with_color_;
};

}  // namespace

testing::Matcher<const SkPicture&> DrawsRectangle(const gfx::RectF& rect,
                                                  Color color) {
  Vector<RectWithColor> rects_with_color;
  rects_with_color.push_back(RectWithColor(rect, color));
  return testing::MakeMatcher(new DrawsRectanglesMatcher(rects_with_color));
}

testing::Matcher<const SkPicture&> DrawsRectangles(
    const Vector<RectWithColor>& rects_with_color) {
  return testing::MakeMatcher(new DrawsRectanglesMatcher(rects_with_color));
}

}  // namespace blink
