/*
 * Copyright 2011 Google Inc.
 *
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 */
#include "Benchmark.h"
#include "SkCanvas.h"
#include "SkColor.h"
#include "SkPaint.h"
#include "SkPicture.h"
#include "SkPictureRecorder.h"
#include "SkPoint.h"
#include "SkRandom.h"
#include "SkRect.h"
#include "SkString.h"

// This is designed to emulate about 4 screens of textual content


class PicturePlaybackBench : public Benchmark {
public:
    PicturePlaybackBench(const char name[])  {
        fName.printf("picture_playback_%s", name);
        fPictureWidth = SkIntToScalar(PICTURE_WIDTH);
        fPictureHeight = SkIntToScalar(PICTURE_HEIGHT);
        fTextSize = SkIntToScalar(TEXT_SIZE);
    }

    enum {
        PICTURE_WIDTH = 1000,
        PICTURE_HEIGHT = 4000,
        TEXT_SIZE = 10
    };
protected:
    virtual const char* onGetName() {
        return fName.c_str();
    }

    virtual void onDraw(int loops, SkCanvas* canvas) {

        SkPictureRecorder recorder;
        SkCanvas* pCanvas = recorder.beginRecording(PICTURE_WIDTH, PICTURE_HEIGHT, nullptr, 0);
        this->recordCanvas(pCanvas);
        SkAutoTUnref<SkPicture> picture(recorder.endRecording());

        const SkPoint translateDelta = getTranslateDelta(loops);

        for (int i = 0; i < loops; i++) {
            picture->playback(canvas);
            canvas->translate(translateDelta.fX, translateDelta.fY);
        }
    }

    virtual void recordCanvas(SkCanvas* canvas) = 0;
    virtual SkPoint getTranslateDelta(int N) {
        SkIPoint canvasSize = onGetSize();
        return SkPoint::Make(SkIntToScalar((PICTURE_WIDTH - canvasSize.fX)/N),
                             SkIntToScalar((PICTURE_HEIGHT- canvasSize.fY)/N));
    }

    SkString fName;
    SkScalar fPictureWidth;
    SkScalar fPictureHeight;
    SkScalar fTextSize;
private:
    typedef Benchmark INHERITED;
};


class TextPlaybackBench : public PicturePlaybackBench {
public:
    TextPlaybackBench() : INHERITED("drawText") { }
protected:
    void recordCanvas(SkCanvas* canvas) override {
        SkPaint paint;
        paint.setTextSize(fTextSize);
        paint.setColor(SK_ColorBLACK);

        const char* text = "Hamburgefons";
        size_t len = strlen(text);
        const SkScalar textWidth = paint.measureText(text, len);

        for (SkScalar x = 0; x < fPictureWidth; x += textWidth) {
            for (SkScalar y = 0; y < fPictureHeight; y += fTextSize) {
                canvas->drawText(text, len, x, y, paint);
            }
        }
    }
private:
    typedef PicturePlaybackBench INHERITED;
};

class PosTextPlaybackBench : public PicturePlaybackBench {
public:
    PosTextPlaybackBench(bool drawPosH)
        : INHERITED(drawPosH ? "drawPosTextH" : "drawPosText")
        , fDrawPosH(drawPosH) { }
protected:
    void recordCanvas(SkCanvas* canvas) override {
        SkPaint paint;
        paint.setTextSize(fTextSize);
        paint.setColor(SK_ColorBLACK);

        const char* text = "Hamburgefons";
        size_t len = strlen(text);
        const SkScalar textWidth = paint.measureText(text, len);

        SkScalar* adv = new SkScalar[len];
        paint.getTextWidths(text, len, adv);

        for (SkScalar x = 0; x < fPictureWidth; x += textWidth) {
            for (SkScalar y = 0; y < fPictureHeight; y += fTextSize) {

                SkPoint* pos = new SkPoint[len];
                SkScalar advX = 0;

                for (size_t i = 0; i < len; i++) {
                    if (fDrawPosH)
                        pos[i].set(x + advX, y);
                    else
                        pos[i].set(x + advX, y + i);
                    advX += adv[i];
                }

                canvas->drawPosText(text, len, pos, paint);
                delete[] pos;
            }
        }
        delete[] adv;
    }
private:
    bool fDrawPosH;
    typedef PicturePlaybackBench INHERITED;
};


///////////////////////////////////////////////////////////////////////////////

DEF_BENCH( return new TextPlaybackBench(); )
DEF_BENCH( return new PosTextPlaybackBench(true); )
DEF_BENCH( return new PosTextPlaybackBench(false); )

// Chrome draws into small tiles with impl-side painting.
// This benchmark measures the relative performance of our bounding-box hierarchies,
// both when querying tiles perfectly and when not.
enum BBH  { kNone, kRTree };
enum Mode { kTiled, kRandom };
class TiledPlaybackBench : public Benchmark {
public:
    TiledPlaybackBench(BBH bbh, Mode mode) : fBBH(bbh), fMode(mode), fName("tiled_playback") {
        switch (fBBH) {
            case kNone:     fName.append("_none"    ); break;
            case kRTree:    fName.append("_rtree"   ); break;
        }
        switch (fMode) {
            case kTiled:  fName.append("_tiled" ); break;
            case kRandom: fName.append("_random"); break;
        }
    }

    const char* onGetName() override { return fName.c_str(); }
    SkIPoint onGetSize() override { return SkIPoint::Make(1024,1024); }

    void onDelayedSetup() override {
        SkAutoTDelete<SkBBHFactory> factory;
        switch (fBBH) {
            case kNone:                                                 break;
            case kRTree:    factory.reset(new SkRTreeFactory);          break;
        }

        SkPictureRecorder recorder;
        SkCanvas* canvas = recorder.beginRecording(1024, 1024, factory);
            SkRandom rand;
            for (int i = 0; i < 10000; i++) {
                SkScalar x = rand.nextRangeScalar(0, 1024),
                         y = rand.nextRangeScalar(0, 1024),
                         w = rand.nextRangeScalar(0, 128),
                         h = rand.nextRangeScalar(0, 128);
                SkPaint paint;
                paint.setColor(rand.nextU());
                paint.setAlpha(0xFF);
                canvas->drawRect(SkRect::MakeXYWH(x,y,w,h), paint);
            }
        fPic.reset(recorder.endRecording());
    }

    void onDraw(int loops, SkCanvas* canvas) override {
        for (int i = 0; i < loops; i++) {
            // This inner loop guarantees we make the same choices for all bench variants.
            SkRandom rand;
            for (int j = 0; j < 10; j++) {
                SkScalar x = 0, y = 0;
                switch (fMode) {
                    case kTiled:  x = SkScalar(256 * rand.nextULessThan(4));
                                  y = SkScalar(256 * rand.nextULessThan(4));
                                  break;
                    case kRandom: x = rand.nextRangeScalar(0, 768);
                                  y = rand.nextRangeScalar(0, 768);
                                  break;
                }
                SkAutoCanvasRestore ar(canvas, true/*save now*/);
                canvas->clipRect(SkRect::MakeXYWH(x,y,256,256));
                fPic->playback(canvas);
            }
        }
    }

private:
    BBH                     fBBH;
    Mode                    fMode;
    SkString                fName;
    SkAutoTUnref<SkPicture> fPic;
};

DEF_BENCH( return new TiledPlaybackBench(kNone,     kRandom); )
DEF_BENCH( return new TiledPlaybackBench(kNone,     kTiled ); )
DEF_BENCH( return new TiledPlaybackBench(kRTree,    kRandom); )
DEF_BENCH( return new TiledPlaybackBench(kRTree,    kTiled ); )