/*
 * Copyright 2014 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 "SkBlendModePriv.h"
#include "SkCanvas.h"
#include "SkPaint.h"

#include <ctype.h>

/** This benchmark tests rendering rotated rectangles. It can optionally apply AA and/or change the
    paint color between each rect in different ways using the ColorType enum. The xfermode used can
    be specified as well.
  */

enum ColorType {
    kConstantOpaque_ColorType,
    kConstantTransparent_ColorType,
    kChangingOpaque_ColorType,
    kChangingTransparent_ColorType,
    kAlternatingOpaqueAndTransparent_ColorType,
};

static inline SkColor start_color(ColorType ct) {
    switch (ct) {
        case kConstantOpaque_ColorType:
        case kChangingOpaque_ColorType:
        case kAlternatingOpaqueAndTransparent_ColorType:
            return 0xFFA07040;
        case kConstantTransparent_ColorType:
        case kChangingTransparent_ColorType:
            return 0x80A07040;
    }
    SK_ABORT("Shouldn't reach here.");
    return 0;
}

static inline SkColor advance_color(SkColor old, ColorType ct, int step) {
    if (kAlternatingOpaqueAndTransparent_ColorType == ct) {
        ct = (step & 0x1) ? kChangingOpaque_ColorType : kChangingTransparent_ColorType ;
    }
    switch (ct) {
        case kConstantOpaque_ColorType:
        case kConstantTransparent_ColorType:
            return old;
        case kChangingOpaque_ColorType:
            return 0xFF000000 | (old + 0x00010307);
        case kChangingTransparent_ColorType:
            return (0x00FFFFFF & (old + 0x00010307)) | 0x80000000;
        case kAlternatingOpaqueAndTransparent_ColorType:
            SK_ABORT("Can't get here");
    }
    SK_ABORT("Shouldn't reach here.");
    return 0;
}

static SkString to_lower(const char* str) {
    SkString lower(str);
    for (size_t i = 0; i < lower.size(); i++) {
        lower[i] = tolower(lower[i]);
    }
    return lower;
}

class RotRectBench: public Benchmark {
public:
    RotRectBench(bool aa, ColorType ct, SkBlendMode mode)
        : fAA(aa)
        , fColorType(ct)
        , fMode(mode) {
        this->makeName();
    }

protected:
    const char* onGetName() override { return fName.c_str(); }

    void onDraw(int loops, SkCanvas* canvas) override {
        SkPaint paint;
        paint.setAntiAlias(fAA);
        paint.setBlendMode(fMode);
        SkColor color = start_color(fColorType);

        int w = this->getSize().x();
        int h = this->getSize().y();

        static const SkScalar kRectW = 25.1f;
        static const SkScalar kRectH = 25.9f;

        SkMatrix rotate;
        // This value was chosen so that we frequently hit the axis-aligned case.
        rotate.setRotate(30.f, kRectW / 2, kRectH / 2);
        SkMatrix m = rotate;

        SkScalar tx = 0, ty = 0;

        for (int i = 0; i < loops; ++i) {
            canvas->save();
            canvas->translate(tx, ty);
            canvas->concat(m);
            paint.setColor(color);
            color = advance_color(color, fColorType, i);

            canvas->drawRect(SkRect::MakeWH(kRectW, kRectH), paint);
            canvas->restore();

            tx += kRectW + 2;
            if (tx > w) {
                tx = 0;
                ty += kRectH + 2;
                if (ty > h) {
                    ty = 0;
                }
            }

            m.postConcat(rotate);
        }
    }

private:
    void makeName() {
        fName = "rotated_rects";
        if (fAA) {
            fName.append("_aa");
        } else {
            fName.append("_bw");
        }
        switch (fColorType) {
            case kConstantOpaque_ColorType:
                fName.append("_same_opaque");
                break;
            case kConstantTransparent_ColorType:
                fName.append("_same_transparent");
                break;
            case kChangingOpaque_ColorType:
                fName.append("_changing_opaque");
                break;
            case kChangingTransparent_ColorType:
                fName.append("_changing_transparent");
                break;
            case kAlternatingOpaqueAndTransparent_ColorType:
                fName.append("_alternating_transparent_and_opaque");
                break;
        }
        fName.appendf("_%s", to_lower(SkBlendMode_Name(fMode)).c_str());
    }

    bool        fAA;
    ColorType   fColorType;
    SkBlendMode fMode;
    SkString    fName;

    typedef Benchmark INHERITED;
};

// Choose kSrcOver because it always allows coverage and alpha to be conflated. kSrc only allows
// conflation when opaque, and kDarken because it isn't possilbe with standard GL blending.
DEF_BENCH(return new RotRectBench(true,  kConstantOpaque_ColorType,                  SkBlendMode::kSrcOver);)
DEF_BENCH(return new RotRectBench(true,  kConstantTransparent_ColorType,             SkBlendMode::kSrcOver);)
DEF_BENCH(return new RotRectBench(true,  kChangingOpaque_ColorType,                  SkBlendMode::kSrcOver);)
DEF_BENCH(return new RotRectBench(true,  kChangingTransparent_ColorType,             SkBlendMode::kSrcOver);)
DEF_BENCH(return new RotRectBench(true,  kAlternatingOpaqueAndTransparent_ColorType, SkBlendMode::kSrcOver);)

DEF_BENCH(return new RotRectBench(false, kConstantOpaque_ColorType,                  SkBlendMode::kSrcOver);)
DEF_BENCH(return new RotRectBench(false, kConstantTransparent_ColorType,             SkBlendMode::kSrcOver);)
DEF_BENCH(return new RotRectBench(false, kChangingOpaque_ColorType,                  SkBlendMode::kSrcOver);)
DEF_BENCH(return new RotRectBench(false, kChangingTransparent_ColorType,             SkBlendMode::kSrcOver);)
DEF_BENCH(return new RotRectBench(false, kAlternatingOpaqueAndTransparent_ColorType, SkBlendMode::kSrcOver);)

DEF_BENCH(return new RotRectBench(true,  kConstantOpaque_ColorType,                  SkBlendMode::kSrc);)
DEF_BENCH(return new RotRectBench(true,  kConstantTransparent_ColorType,             SkBlendMode::kSrc);)
DEF_BENCH(return new RotRectBench(true,  kChangingOpaque_ColorType,                  SkBlendMode::kSrc);)
DEF_BENCH(return new RotRectBench(true,  kChangingTransparent_ColorType,             SkBlendMode::kSrc);)
DEF_BENCH(return new RotRectBench(true,  kAlternatingOpaqueAndTransparent_ColorType, SkBlendMode::kSrc);)

DEF_BENCH(return new RotRectBench(false, kConstantOpaque_ColorType,                  SkBlendMode::kSrc);)
DEF_BENCH(return new RotRectBench(false, kConstantTransparent_ColorType,             SkBlendMode::kSrc);)
DEF_BENCH(return new RotRectBench(false, kChangingOpaque_ColorType,                  SkBlendMode::kSrc);)
DEF_BENCH(return new RotRectBench(false, kChangingTransparent_ColorType,             SkBlendMode::kSrc);)
DEF_BENCH(return new RotRectBench(false, kAlternatingOpaqueAndTransparent_ColorType, SkBlendMode::kSrc);)

DEF_BENCH(return new RotRectBench(true,  kConstantOpaque_ColorType,                  SkBlendMode::kDarken);)
DEF_BENCH(return new RotRectBench(true,  kConstantTransparent_ColorType,             SkBlendMode::kDarken);)
DEF_BENCH(return new RotRectBench(true,  kChangingOpaque_ColorType,                  SkBlendMode::kDarken);)
DEF_BENCH(return new RotRectBench(true,  kChangingTransparent_ColorType,             SkBlendMode::kDarken);)
DEF_BENCH(return new RotRectBench(true,  kAlternatingOpaqueAndTransparent_ColorType, SkBlendMode::kDarken);)

DEF_BENCH(return new RotRectBench(false, kConstantOpaque_ColorType,                  SkBlendMode::kDarken);)
DEF_BENCH(return new RotRectBench(false, kConstantTransparent_ColorType,             SkBlendMode::kDarken);)
DEF_BENCH(return new RotRectBench(false, kChangingOpaque_ColorType,                  SkBlendMode::kDarken);)
DEF_BENCH(return new RotRectBench(false, kChangingTransparent_ColorType,             SkBlendMode::kDarken);)
DEF_BENCH(return new RotRectBench(false, kAlternatingOpaqueAndTransparent_ColorType, SkBlendMode::kDarken);)