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

#include "base/containers/hash_tables.h"
#include "base/logging.h"
#include "base/time/time.h"
#include "skia/ext/benchmarking_canvas.h"
#include "third_party/skia/include/utils/SkProxyCanvas.h"

namespace skia {

class AutoStamper {
public:
  AutoStamper(TimingCanvas* timing_canvas);
  ~AutoStamper();

private:
  TimingCanvas* timing_canvas_;
  base::TimeTicks start_ticks_;
};

class TimingCanvas : public SkProxyCanvas {
public:
  TimingCanvas(int width, int height, const BenchmarkingCanvas* track_canvas)
      : tracking_canvas_(track_canvas) {
    canvas_ = skia::AdoptRef(SkCanvas::NewRasterN32(width, height));

    setProxy(canvas_.get());
  }

  virtual ~TimingCanvas() {
  }

  double GetTime(size_t index) {
    TimingsMap::const_iterator timing_info = timings_map_.find(index);
    return timing_info != timings_map_.end()
        ? timing_info->second.InMillisecondsF()
        : 0.0;
  }

  // SkCanvas overrides.
  virtual void willSave() OVERRIDE {
    AutoStamper stamper(this);
    SkProxyCanvas::willSave();
  }

  virtual SaveLayerStrategy willSaveLayer(const SkRect* bounds,
                                          const SkPaint* paint,
                                          SaveFlags flags) OVERRIDE {
    AutoStamper stamper(this);
    return SkProxyCanvas::willSaveLayer(bounds, paint, flags);
  }

  virtual void willRestore() OVERRIDE {
    AutoStamper stamper(this);
    SkProxyCanvas::willRestore();
  }

  virtual void drawPaint(const SkPaint& paint) OVERRIDE {
    AutoStamper stamper(this);
    SkProxyCanvas::drawPaint(paint);
  }

  virtual void drawPoints(PointMode mode, size_t count, const SkPoint pts[],
                          const SkPaint& paint) OVERRIDE {
    AutoStamper stamper(this);
    SkProxyCanvas::drawPoints(mode, count, pts, paint);
  }

  virtual void drawOval(const SkRect& rect, const SkPaint& paint) OVERRIDE {
    AutoStamper stamper(this);
    SkProxyCanvas::drawOval(rect, paint);
  }

  virtual void drawRect(const SkRect& rect, const SkPaint& paint) OVERRIDE {
    AutoStamper stamper(this);
    SkProxyCanvas::drawRect(rect, paint);
  }

  virtual void drawRRect(const SkRRect& rrect, const SkPaint& paint) OVERRIDE {
    AutoStamper stamper(this);
    SkProxyCanvas::drawRRect(rrect, paint);
  }

  virtual void drawPath(const SkPath& path, const SkPaint& paint) OVERRIDE {
    AutoStamper stamper(this);
    SkProxyCanvas::drawPath(path, paint);
  }

  virtual void drawBitmap(const SkBitmap& bitmap, SkScalar left, SkScalar top,
                          const SkPaint* paint = NULL) OVERRIDE {
    AutoStamper stamper(this);
    SkProxyCanvas::drawBitmap(bitmap, left, top, paint);
  }

  virtual void drawBitmapRectToRect(const SkBitmap& bitmap, const SkRect* src,
                                    const SkRect& dst,
                                    const SkPaint* paint,
                                    DrawBitmapRectFlags flags) OVERRIDE {
    AutoStamper stamper(this);
    SkProxyCanvas::drawBitmapRectToRect(bitmap, src, dst, paint, flags);
  }

  virtual void drawBitmapMatrix(const SkBitmap& bitmap, const SkMatrix& m,
                                const SkPaint* paint = NULL) OVERRIDE {
    AutoStamper stamper(this);
    SkProxyCanvas::drawBitmapMatrix(bitmap, m, paint);
  }

  virtual void drawSprite(const SkBitmap& bitmap, int left, int top,
                          const SkPaint* paint = NULL) OVERRIDE {
    AutoStamper stamper(this);
    SkProxyCanvas::drawSprite(bitmap, left, top, paint);
  }

  virtual void drawVertices(VertexMode vmode, int vertexCount,
                            const SkPoint vertices[], const SkPoint texs[],
                            const SkColor colors[], SkXfermode* xmode,
                            const uint16_t indices[], int indexCount,
                            const SkPaint& paint) OVERRIDE {
    AutoStamper stamper(this);
    SkProxyCanvas::drawVertices(vmode, vertexCount, vertices, texs, colors,
                                xmode, indices, indexCount, paint);
  }

  virtual void drawData(const void* data, size_t length) OVERRIDE {
    AutoStamper stamper(this);
    SkProxyCanvas::drawData(data, length);
  }

protected:
  virtual void onDrawText(const void* text, size_t byteLength, SkScalar x,
                          SkScalar y, const SkPaint& paint) OVERRIDE {
    AutoStamper stamper(this);
    SkProxyCanvas::onDrawText(text, byteLength, x, y, paint);
  }

  virtual void onDrawPosText(const void* text, size_t byteLength,
                             const SkPoint pos[],
                             const SkPaint& paint) OVERRIDE {
    AutoStamper stamper(this);
    SkProxyCanvas::onDrawPosText(text, byteLength, pos, paint);
  }

  virtual void onDrawPosTextH(const void* text, size_t byteLength,
                              const SkScalar xpos[], SkScalar constY,
                              const SkPaint& paint) OVERRIDE {
    AutoStamper stamper(this);
    SkProxyCanvas::onDrawPosTextH(text, byteLength, xpos, constY, paint);
  }

  virtual void onDrawTextOnPath(const void* text, size_t byteLength,
                                const SkPath& path, const SkMatrix* matrix,
                                const SkPaint& paint) OVERRIDE {
    AutoStamper stamper(this);
    SkProxyCanvas::onDrawTextOnPath(text, byteLength, path, matrix, paint);
  }

  virtual void onClipRect(const SkRect& rect, SkRegion::Op op,
                          ClipEdgeStyle edge_style) OVERRIDE {
    AutoStamper stamper(this);
    SkProxyCanvas::onClipRect(rect, op, edge_style);
  }

  virtual void onClipRRect(const SkRRect& rrect, SkRegion::Op op,
                          ClipEdgeStyle edge_style) OVERRIDE {
    AutoStamper stamper(this);
    SkProxyCanvas::onClipRRect(rrect, op, edge_style);
  }

  virtual void onClipPath(const SkPath& path, SkRegion::Op op,
                          ClipEdgeStyle edge_style) OVERRIDE {
    AutoStamper stamper(this);
    SkProxyCanvas::onClipPath(path, op, edge_style);
  }

  virtual void onClipRegion(const SkRegion& region,
                            SkRegion::Op op) OVERRIDE {
    AutoStamper stamper(this);
    SkProxyCanvas::onClipRegion(region, op);
  }

  virtual void onDrawPicture(const SkPicture* picture, const SkMatrix* matrix,
                             const SkPaint* paint) OVERRIDE {
    AutoStamper stamper(this);
    SkProxyCanvas::onDrawPicture(picture, matrix, paint);
  }

private:
  typedef base::hash_map<size_t, base::TimeDelta> TimingsMap;
  TimingsMap timings_map_;

  skia::RefPtr<SkCanvas> canvas_;

  friend class AutoStamper;
  const BenchmarkingCanvas* tracking_canvas_;
};

AutoStamper::AutoStamper(TimingCanvas *timing_canvas)
    : timing_canvas_(timing_canvas) {
  start_ticks_ = base::TimeTicks::HighResNow();
}

AutoStamper::~AutoStamper() {
  base::TimeDelta delta = base::TimeTicks::HighResNow() - start_ticks_;
  int command_index = timing_canvas_->tracking_canvas_->CommandCount() - 1;
  DCHECK_GE(command_index, 0);
  timing_canvas_->timings_map_[command_index] = delta;
}

BenchmarkingCanvas::BenchmarkingCanvas(int width, int height)
    : SkNWayCanvas(width, height) {
  debug_canvas_ = skia::AdoptRef(SkNEW_ARGS(SkDebugCanvas, (width, height)));
  timing_canvas_ = skia::AdoptRef(SkNEW_ARGS(TimingCanvas, (width, height, this)));

  addCanvas(debug_canvas_.get());
  addCanvas(timing_canvas_.get());
}

BenchmarkingCanvas::~BenchmarkingCanvas() {
  removeAll();
}

size_t BenchmarkingCanvas::CommandCount() const {
  return debug_canvas_->getSize();
}

SkDrawCommand* BenchmarkingCanvas::GetCommand(size_t index) {
  DCHECK_LT(index, static_cast<size_t>(debug_canvas_->getSize()));
  return debug_canvas_->getDrawCommandAt(index);
}

double BenchmarkingCanvas::GetTime(size_t index) {
  DCHECK_LT(index,  static_cast<size_t>(debug_canvas_->getSize()));
  return timing_canvas_->GetTime(index);
}

} // namespace skia