
/*
 * Copyright 2018 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 <math.h>
#include <string>
#include <utility>
#include <SkColor.h>
#include <SkCanvas.h>
#include <SkBitmap.h>
#include <SkSurface.h>
#include <SkTime.h>

#include <GrContextOptions.h>
#include <GrContext.h>
#include <gl/GrGLInterface.h>
#include <GrBackendSurface.h>
#include <gl/GrGLTypes.h>

#include <Skottie.h>

#include <GLES2/gl2.h>
#include <GLES2/gl2ext.h>

#include <GLES3/gl3.h>
#include <android/trace.h>
#include "JavaInputStreamAdaptor.h"

#define STENCIL_BUFFER_SIZE 8

/*#define ATRACE_NAME(name) ScopedTrace ___tracer(name)

// ATRACE_CALL is an ATRACE_NAME that uses the current function name.
#define ATRACE_CALL() ATRACE_NAME(__FUNCTION__)
namespace {
    class ScopedTrace {
    public:
        inline ScopedTrace(const char *name) {
            ATrace_beginSection(name);
        }

        inline ~ScopedTrace() {
            ATrace_endSection();
        }
    };

}*/

//disable atrace
#define ATRACE_NAME(name)
#define ATRACE_CALL()

struct SkottieRunner {
    sk_sp<GrContext> mGrContext;
};

extern "C" JNIEXPORT jlong
JNICALL
Java_org_skia_skottie_SkottieRunner_nCreateProxy(JNIEnv *env, jclass clazz) {
    sk_sp<const GrGLInterface> glInterface(GrGLCreateNativeInterface());
    if (!glInterface.get()) {
        return 0;
    }

    GrContextOptions options;
    options.fDisableDistanceFieldPaths = true;
    options.fDisableCoverageCountingPaths = true;
    sk_sp<GrContext> grContext = GrContext::MakeGL(std::move(glInterface), options);
    if (!grContext.get()) {
        return 0;
    }

    SkottieRunner* skottie = new SkottieRunner();
    skottie->mGrContext = grContext;

    return (jlong) skottie;
}

extern "C" JNIEXPORT void
JNICALL
Java_org_skia_skottie_SkottieRunner_nDeleteProxy(JNIEnv *env, jclass clazz, jlong nativeProxy) {
    if (!nativeProxy) {
        return;
    }
    SkottieRunner* skottie = reinterpret_cast<SkottieRunner*>(nativeProxy);
    if (skottie->mGrContext) {
        skottie->mGrContext->releaseResourcesAndAbandonContext();
        skottie->mGrContext.reset();
    }
    delete skottie;
}

struct SkottieAnimation {
    SkottieRunner *mRunner;
    std::unique_ptr<SkStream> mStream;
    sk_sp<skottie::Animation> mAnimation;
    long                      mTimeBase;
    float                     mDuration; //in milliseconds
};

extern "C" JNIEXPORT jlong
JNICALL
Java_org_skia_skottie_SkottieRunner_00024SkottieAnimation_nCreateProxy(JNIEnv *env, jobject clazz,
                                                                       jlong runner, jobject is,
                                                                       jbyteArray storage) {

    if (!runner) {
        return 0;
    }
    SkottieRunner *skottieRunner = reinterpret_cast<SkottieRunner*>(runner);
    std::unique_ptr<SkStream> stream(CopyJavaInputStream(env, is, storage));
    if (!stream.get()) {
        // Cannot create a stream
        return 0;
    }

    SkottieAnimation* skottieAnimation = new SkottieAnimation();
    skottieAnimation->mRunner = skottieRunner;
    skottieAnimation->mStream = std::move(stream);

    skottieAnimation->mAnimation = skottie::Animation::Make(skottieAnimation->mStream.get());
    skottieAnimation->mTimeBase  = 0.0f; // force a time reset
    skottieAnimation->mDuration = 1000 * skottieAnimation->mAnimation->duration();

    if (!skottieAnimation->mAnimation) {
        //failed to load Bodymovin animation
        delete skottieAnimation;
        return 0;
    }

    return (jlong) skottieAnimation;
}

extern "C" JNIEXPORT void
JNICALL
Java_org_skia_skottie_SkottieRunner_00024SkottieAnimation_nDeleteProxy(JNIEnv *env, jclass clazz,
                                                                       jlong nativeProxy) {
    if (!nativeProxy) {
        return;
    }
    SkottieAnimation* skottieAnimation = reinterpret_cast<SkottieAnimation*>(nativeProxy);
    delete skottieAnimation;
}

extern "C" JNIEXPORT void
JNICALL
Java_org_skia_skottie_SkottieRunner_00024SkottieAnimation_nDrawFrame(JNIEnv *env, jclass clazz,
                                                                     jlong nativeProxy, jint width,
                                                                     jint height,
                                                                     jboolean wideColorGamut,
                                                                     jlong frameTimeNanos) {
    ATRACE_NAME("SkottieDrawFrame");
    if (!nativeProxy) {
        return;
    }
    SkottieAnimation* skottieAnimation = reinterpret_cast<SkottieAnimation*>(nativeProxy);

    auto grContext = skottieAnimation->mRunner->mGrContext;

    if (!grContext) {
        return;
    }

    SkColorType colorType;
    // setup surface for fbo0
    GrGLFramebufferInfo fboInfo;
    fboInfo.fFBOID = 0;
    if (wideColorGamut) {
        fboInfo.fFormat = GL_RGBA16F;
        colorType = kRGBA_F16_SkColorType;
    } else {
        fboInfo.fFormat = GL_RGBA8;
        colorType = kN32_SkColorType;
    }
    GrBackendRenderTarget backendRT(width, height, 0, STENCIL_BUFFER_SIZE, fboInfo);

    SkSurfaceProps props(0, kUnknown_SkPixelGeometry);

    sk_sp<SkSurface> renderTarget(SkSurface::MakeFromBackendRenderTarget(
            grContext.get(), backendRT, kBottomLeft_GrSurfaceOrigin, colorType,
            nullptr, &props));

    auto canvas = renderTarget->getCanvas();
    canvas->clear(SK_ColorTRANSPARENT);
    if (skottieAnimation->mAnimation) {
        SkMSec t = 0;
        if (skottieAnimation->mTimeBase == 0.0f) {
            // Reset the animation time.
            skottieAnimation->mTimeBase = frameTimeNanos;
        } else {
            //convert from nanoseconds to milliseconds
            t = (frameTimeNanos - skottieAnimation->mTimeBase) / 1000000;
        }
        //TODO: control repeat count
        float intpart;
        float animState = modff(t / skottieAnimation->mDuration, &intpart);
        skottieAnimation->mAnimation->seek(animState);

        SkAutoCanvasRestore acr(canvas, true);
        SkRect bounds = SkRect::MakeWH(width, height);
        skottieAnimation->mAnimation->render(canvas, &bounds);
    }

    canvas->flush();
}
