
/*
 * 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 <jni.h>
#include <sys/time.h>
#include <time.h>
#include <android/log.h>
#include <stdint.h>

#include "GrContext.h"
#include "SkGpuCanvas.h"
#include "SkPaint.h"
#include "SkString.h"
#include "SkTime.h"

#include "GrGLConfig.h"

static GrContext* make_context() {
    SkDebugf("---- before create\n");
    GrContext* ctx = GrContext::Create(GrGpu::kOpenGL_Shaders_Engine, 0);
//    GrContext* ctx = GrContext::Create(GrGpu::kOpenGL_Fixed_Engine, 0);
    SkDebugf("---- after create %p\n", ctx);
    return ctx;
}

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

void gr_run_unittests() {}

#include "FlingState.h"
#include "SkTouchGesture.h"
#include "SkView.h"

typedef SkView* (*SkViewFactory)();

// these values must match those in Ganesh.java
enum TouchState {
    kUnknown_TouchState,
    kDown_TouchState,
    kMoved_TouchState,
    kUp_TouchState
};

struct State {
    State();
    ~State();

    int countSlides() const { return fFactory.count(); }
    const char* getSlideTitle(int index) const;
    void chooseSlide(int index) {
        SkDebugf("----- index %d\n", index);
        if (index < fFactory.count()) {
            this->setView(fFactory[index]());
        }
    }

    void setViewport(int w, int h);
    int getWidth() const { return fViewport.fX; }
    int getHeight() const { return fViewport.fY; }
    
    void handleTouch(void*, TouchState, float x, float y);
    void applyMatrix(SkCanvas*);

    SkView* getView() const { return fView; }

private:
    SkView*     fView;
    SkIPoint    fViewport;
    
    SkTouchGesture fGesture;

    SkTDArray<SkViewFactory> fFactory;

    void setView(SkView* view) {
        SkSafeUnref(fView);
        fView = view;

        view->setVisibleP(true);
        view->setClipToBounds(false);
        view->setSize(SkIntToScalar(fViewport.fX),
                      SkIntToScalar(fViewport.fY));
    }
};

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

#include "SampleCode.h"

SkViewRegister* SkViewRegister::gHead;
SkViewRegister::SkViewRegister(SkViewFactory fact) : fFact(fact) {
    static bool gOnce;
    if (!gOnce) {
        gHead = NULL;
        gOnce = true;
    }
    
    fChain = gHead;
    gHead = this;
}

static const char gCharEvtName[] = "SampleCode_Char_Event";
static const char gKeyEvtName[] = "SampleCode_Key_Event";
static const char gTitleEvtName[] = "SampleCode_Title_Event";
static const char gPrefSizeEvtName[] = "SampleCode_PrefSize_Event";
static const char gFastTextEvtName[] = "SampleCode_FastText_Event";

bool SampleCode::CharQ(const SkEvent& evt, SkUnichar* outUni) {
    if (evt.isType(gCharEvtName, sizeof(gCharEvtName) - 1)) {
        if (outUni) {
            *outUni = evt.getFast32();
        }
        return true;
    }
    return false;
}

bool SampleCode::KeyQ(const SkEvent& evt, SkKey* outKey) {
    if (evt.isType(gKeyEvtName, sizeof(gKeyEvtName) - 1)) {
        if (outKey) {
            *outKey = (SkKey)evt.getFast32();
        }
        return true;
    }
    return false;
}

bool SampleCode::TitleQ(const SkEvent& evt) {
    return evt.isType(gTitleEvtName, sizeof(gTitleEvtName) - 1);
}

void SampleCode::TitleR(SkEvent* evt, const char title[]) {
    GrAssert(evt && TitleQ(*evt));
    evt->setString(gTitleEvtName, title);
}

bool SampleCode::PrefSizeQ(const SkEvent& evt) {
    return evt.isType(gPrefSizeEvtName, sizeof(gPrefSizeEvtName) - 1);
}

void SampleCode::PrefSizeR(SkEvent* evt, SkScalar width, SkScalar height) {
    GrAssert(evt && PrefSizeQ(*evt));
    SkScalar size[2];
    size[0] = width;
    size[1] = height;
    evt->setScalars(gPrefSizeEvtName, 2, size);
}

bool SampleCode::FastTextQ(const SkEvent& evt) {
    return evt.isType(gFastTextEvtName, sizeof(gFastTextEvtName) - 1);
}

static SkMSec gAnimTime;
static SkMSec gAnimTimePrev;

SkMSec SampleCode::GetAnimTime() { return gAnimTime; }
SkMSec SampleCode::GetAnimTimeDelta() { return gAnimTime - gAnimTimePrev; }
SkScalar SampleCode::GetAnimSecondsDelta() {
    return SkDoubleToScalar(GetAnimTimeDelta() / 1000.0);
}

SkScalar SampleCode::GetAnimScalar(SkScalar speed, SkScalar period) {
    // since gAnimTime can be up to 32 bits, we can't convert it to a float
    // or we'll lose the low bits. Hence we use doubles for the intermediate
    // calculations
    double seconds = (double)gAnimTime / 1000.0;
    double value = SkScalarToDouble(speed) * seconds;
    if (period) {
        value = ::fmod(value, SkScalarToDouble(period));
    }
    return SkDoubleToScalar(value);
}

static void drawIntoCanvas(State* state, SkCanvas* canvas) {
    gAnimTime = SkTime::GetMSecs();
    SkView* view = state->getView();
    view->draw(canvas);
}

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

static void resetGpuState();

State::State() {
    fViewport.set(0, 0);

    const SkViewRegister* reg = SkViewRegister::Head();
    while (reg) {
        *fFactory.append() = reg->factory();
        reg = reg->next();
    }

    SkDebugf("----- %d slides\n", fFactory.count());
    fView = NULL;
    this->chooseSlide(0);
}

State::~State() {
    SkSafeUnref(fView);
}

void State::setViewport(int w, int h) {
    fViewport.set(w, h);
    if (fView) {
        fView->setSize(SkIntToScalar(w), SkIntToScalar(h));
    }
    resetGpuState();
}

const char* State::getSlideTitle(int index) const {
    SkEvent evt(gTitleEvtName);
    evt.setFast32(index);
    {
        SkView* view = fFactory[index]();
        view->doQuery(&evt);
        view->unref();
    }
    return evt.findString(gTitleEvtName);
}

void State::handleTouch(void* owner, TouchState state, float x, float y) {
    switch (state) {
        case kDown_TouchState:
            fGesture.touchBegin(owner, x, y);
            break;
        case kMoved_TouchState:
            fGesture.touchMoved(owner, x, y);
            break;
        case kUp_TouchState:
            fGesture.touchEnd(owner);
            break;
    }
}

void State::applyMatrix(SkCanvas* canvas) {
    const SkMatrix& localM = fGesture.localM();
    if (localM.getType() & SkMatrix::kScale_Mask) {
        canvas->setExternalMatrix(&localM);
    }
    canvas->concat(localM);
    canvas->concat(fGesture.globalM());
}

static State* get_state() {
    static State* gState;
    if (NULL == gState) {
        gState = new State;
    }
    return gState;
}

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

static GrContext* gContext;
static int gWidth;
static int gHeight;
static float gX, gY;

static void resetGpuState() {
    if (NULL == gContext) {
        SkDebugf("creating context for first time\n");
        gContext = make_context();
    } else {
        SkDebugf("------ gContext refcnt=%d\n", gContext->refcnt());
        gContext->abandonAllTextures();
        gContext->unref();
        gContext = make_context();
    }
}

static void doDraw() {
    if (NULL == gContext) {
        gContext = make_context();
    }

    State* state = get_state();
    SkBitmap viewport;
    viewport.setConfig(SkBitmap::kARGB_8888_Config,
                       state->getWidth(), state->getHeight());

    SkGpuCanvas canvas(gContext);

    canvas.setBitmapDevice(viewport);
    state->applyMatrix(&canvas);

    drawIntoCanvas(state, &canvas);

    GrGLCheckErr();
    GrGLClearErr();
//    gContext->checkError();
//    gContext->clearError();

    if (true) {
        static const int FRAME_COUNT = 32;
        static SkMSec gDuration;

        static SkMSec gNow;
        static int gFrameCounter;
        if (++gFrameCounter == FRAME_COUNT) {
            gFrameCounter = 0;
            SkMSec now = SkTime::GetMSecs();

            gDuration = now - gNow;
            gNow = now;
        }

        int fps = (FRAME_COUNT * 1000) / gDuration;
        SkString str;
        str.printf("FPS=%3d MS=%3d", fps, gDuration / FRAME_COUNT);
        
        SkGpuCanvas c(gContext);
        c.setBitmapDevice(viewport);

        SkPaint p;
        p.setAntiAlias(true);
        SkRect r = { 0, 0, 110, 16 };
        p.setColor(SK_ColorWHITE);
        c.drawRect(r, p);
        p.setColor(SK_ColorBLACK);
        c.drawText(str.c_str(), str.size(), 4, 12, p);
    }
}

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

extern "C" {
    JNIEXPORT void JNICALL Java_com_tetrark_ganesh_MyRenderer_nativeSurfaceCreated(
                                                           JNIEnv*, jobject);
    JNIEXPORT void JNICALL Java_com_tetrark_ganesh_MyRenderer_nativeViewport(JNIEnv*, jobject,
                                                                             jint w, jint h);
    JNIEXPORT void JNICALL Java_com_tetrark_ganesh_MyRenderer_nativeDrawFrame(JNIEnv*, jobject);
    JNIEXPORT void JNICALL Java_com_tetrark_ganesh_MyRenderer_nativeTouch(JNIEnv*, jobject,
                                        jint id, jint type, jfloat x, jfloat y);

    JNIEXPORT int JNICALL Java_com_tetrark_ganesh_MyRenderer_nativeCountSlides(JNIEnv*, jobject);
    JNIEXPORT jobject JNICALL Java_com_tetrark_ganesh_MyRenderer_nativeGetSlideTitle(JNIEnv*, jobject, jint index);
    JNIEXPORT void JNICALL Java_com_tetrark_ganesh_MyRenderer_nativeChooseSlide(JNIEnv*, jobject, jint index);
}

JNIEXPORT void JNICALL Java_com_tetrark_ganesh_MyRenderer_nativeSurfaceCreated(
                                                            JNIEnv*, jobject) {
    SkDebugf("------ nativeSurfaceCreated\n");
    resetGpuState();
    SkDebugf("------ end nativeSurfaceCreated\n");
}

JNIEXPORT void JNICALL Java_com_tetrark_ganesh_MyRenderer_nativeViewport(JNIEnv*, jobject,
                                                       jint w, jint h) {
    State* state = get_state();
    SkDebugf("---- state.setviewport %p %d %d\n", state, w, h);
    state->setViewport(w, h);
    SkDebugf("---- end setviewport\n");
}

JNIEXPORT void JNICALL Java_com_tetrark_ganesh_MyRenderer_nativeDrawFrame(JNIEnv*, jobject) {
    doDraw();
}

union IntPtr {
    jint    fInt;
    void*   fPtr;
};
static void* int2ptr(jint n) {
    IntPtr data;
    data.fInt = n;
    return data.fPtr;
}

JNIEXPORT void JNICALL Java_com_tetrark_ganesh_MyRenderer_nativeTouch(JNIEnv*, jobject,
                                      jint id, jint type, jfloat x, jfloat y) {
    get_state()->handleTouch(int2ptr(id), (TouchState)type, x, y);
}

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

JNIEXPORT int JNICALL Java_com_tetrark_ganesh_MyRenderer_nativeCountSlides(JNIEnv*, jobject) {
    return get_state()->countSlides();
}

JNIEXPORT jobject JNICALL Java_com_tetrark_ganesh_MyRenderer_nativeGetSlideTitle(JNIEnv* env, jobject, jint index) {
    return env->NewStringUTF(get_state()->getSlideTitle(index));
}

JNIEXPORT void JNICALL Java_com_tetrark_ganesh_MyRenderer_nativeChooseSlide(JNIEnv*, jobject, jint index) {
    get_state()->chooseSlide(index);
}





