/*
 * Copyright 2015 Google Inc.
 *
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 */

#include "include/core/SkCanvas.h"
#include "include/core/SkFont.h"
#include "include/core/SkRSXform.h"
#include "include/core/SkSurface.h"
#include "src/core/SkPaintPriv.h"
#include "tools/DecodeUtils.h"
#include "tools/Resources.h"
#include "tools/timer/Timer.h"
#include "tools/viewer/Slide.h"

#include <stdio.h>

static const int kGrid = 100;
static const int kWidth = 960;
static const int kHeight = 640;

typedef void (*DrawAtlasProc)(SkCanvas*, SkImage*, SkSpan<const SkRSXform>, SkSpan<const SkRect>,
                              SkSpan<const SkColor>, const SkRect*, const SkSamplingOptions&,
                              const SkPaint*);

static void draw_atlas(SkCanvas* canvas, SkImage* atlas, SkSpan<const SkRSXform> xform,
                       SkSpan<const SkRect> tex,
                       SkSpan<const SkColor> colors, const SkRect* cull,
                       const SkSamplingOptions& sampling, const SkPaint* paint) {
    canvas->drawAtlas(atlas, xform, tex, colors, SkBlendMode::kModulate,
                      sampling, cull, paint);
}

static void draw_atlas_sim(SkCanvas* canvas, SkImage* atlas, SkSpan<const SkRSXform> xform,
                           SkSpan<const SkRect> tex,
                           SkSpan<const SkColor> colors, const SkRect* cull,
                           const SkSamplingOptions& sampling, const SkPaint* paint) {
    size_t N = std::min(xform.size(), tex.size());
    if (!colors.empty()) {
        N = std::min(N, colors.size());
    }
    for (size_t i = 0; i < N; ++i) {
        SkMatrix matrix;
        matrix.setRSXform(xform[i]);

        canvas->save();
        canvas->concat(matrix);
        canvas->drawImageRect(atlas, tex[i], tex[i].makeOffset(-tex[i].x(), -tex[i].y()),
                              sampling, paint, SkCanvas::kFast_SrcRectConstraint);
        canvas->restore();
    }
}

class DrawShipSlide : public Slide {
public:
    DrawShipSlide(const char name[], DrawAtlasProc proc) : fProc(proc) {
        fName = name;
    }

protected:
    void load(SkScalar, SkScalar) override {
        fAtlas = ToolUtils::GetResourceAsImage("images/ship.png");
        if (!fAtlas) {
            SkDebugf("\nCould not decode file ship.png. Falling back to penguin mode.\n");
            fAtlas = ToolUtils::GetResourceAsImage("images/baby_tux.png");
            if (!fAtlas) {
                SkDebugf("\nCould not decode file baby_tux.png. Did you forget"
                         " to set the resourcePath?\n");
                return;
            }
        }

        SkScalar anchorX = fAtlas->width()*0.5f;
        SkScalar anchorY = fAtlas->height()*0.5f;
        int currIndex = 0;
        for (int x = 0; x < kGrid; x++) {
            for (int y = 0; y < kGrid; y++) {
                float xPos = (x / (kGrid - 1.0f)) * kWidth;
                float yPos = (y / (kGrid - 1.0f)) * kWidth;

                fTex[currIndex] = SkRect::MakeLTRB(0.0f, 0.0f,
                                                   SkIntToScalar(fAtlas->width()),
                                                   SkIntToScalar(fAtlas->height()));
                fXform[currIndex] = SkRSXform::MakeFromRadians(0.1f, SK_ScalarPI*0.5f,
                                                               xPos, yPos, anchorX, anchorY);
                currIndex++;
            }
        }
        fTex[currIndex] = SkRect::MakeLTRB(0.0f, 0.0f,
                                           SkIntToScalar(fAtlas->width()),
                                           SkIntToScalar(fAtlas->height()));
        fXform[currIndex] = SkRSXform::MakeFromRadians(0.5f, SK_ScalarPI*0.5f,
                                                       kWidth*0.5f, kHeight*0.5f, anchorX, anchorY);
    }

    void unload() override {
        fAtlas = nullptr;
    }

    void draw(SkCanvas* canvas) override {
        const float kCosDiff = 0.99984769515f;
        const float kSinDiff = 0.01745240643f;

        if (!fAtlas) {
            return;
        }

        SkPaint paint;
        paint.setColor(SK_ColorWHITE);

        SkScalar anchorX = fAtlas->width()*0.5f;
        SkScalar anchorY = fAtlas->height()*0.5f;
        for (int i = 0; i < kGrid*kGrid+1; ++i) {
            SkScalar c = fXform[i].fSCos;
            SkScalar s = fXform[i].fSSin;

            SkScalar dx = c*anchorX - s*anchorY;
            SkScalar dy = s*anchorX + c*anchorY;

            fXform[i].fSCos = kCosDiff*c - kSinDiff*s;
            fXform[i].fSSin = kSinDiff*c + kCosDiff*s;

            dx -= fXform[i].fSCos*anchorX - fXform[i].fSSin*anchorY;
            dy -= fXform[i].fSSin*anchorX + fXform[i].fSCos*anchorY;
            fXform[i].fTx += dx;
            fXform[i].fTy += dy;
        }

        SkSpan<const SkRSXform> xforms = {&fXform[0], kGrid*kGrid+1};
        SkSpan<const SkRect>      texs = {&fTex[0],   kGrid*kGrid+1};
        fProc(canvas, fAtlas.get(), xforms, texs, {}, nullptr,
              SkSamplingOptions(SkFilterMode::kLinear), &paint);
    }

    bool animate(double nanos) override {
        //TODO: use nanos
        //SkScalar angle = SkDoubleToScalar(fmod(1e-9 * nanos * 360 / 24, 360));
        //fAnimatingDrawable->setSweep(angle);
        return true;
    }

private:
    DrawAtlasProc  fProc;

    sk_sp<SkImage> fAtlas;
    SkRSXform      fXform[kGrid*kGrid+1];
    SkRect         fTex[kGrid*kGrid+1];
};

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

DEF_SLIDE( return new DrawShipSlide("DrawShip", draw_atlas); )
DEF_SLIDE( return new DrawShipSlide("DrawShipSim", draw_atlas_sim); )
