/*
 * Copyright (C) 2023 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <Gainmap.h>

#ifdef __ANDROID__
#include <binder/Parcel.h>
#endif

#include "Bitmap.h"
#include "GraphicsJNI.h"
#include "ScopedParcel.h"
#include "graphics_jni_helpers.h"

namespace android {

static jclass gGainmap_class;
static jmethodID gGainmap_constructorMethodID;

using namespace uirenderer;

static Gainmap* fromJava(jlong gainmap) {
    return reinterpret_cast<Gainmap*>(gainmap);
}

static int getCreateFlags(const sk_sp<Bitmap>& bitmap) {
    int flags = 0;
    if (bitmap->info().alphaType() == kPremul_SkAlphaType) {
        flags |= android::bitmap::kBitmapCreateFlag_Premultiplied;
    }
    if (!bitmap->isImmutable()) {
        flags |= android::bitmap::kBitmapCreateFlag_Mutable;
    }
    return flags;
}

jobject Gainmap_extractFromBitmap(JNIEnv* env, const Bitmap& bitmap) {
    auto gainmap = bitmap.gainmap();
    jobject jGainmapImage;

    {
        // Scope to guard the release of nativeBitmap
        auto nativeBitmap = gainmap->bitmap;
        const int createFlags = getCreateFlags(nativeBitmap);
        jGainmapImage = bitmap::createBitmap(env, nativeBitmap.release(), createFlags);
    }

    // Grab a ref for the jobject
    gainmap->incStrong(0);
    jobject obj = env->NewObject(gGainmap_class, gGainmap_constructorMethodID, jGainmapImage,
                                 gainmap.get());

    if (env->ExceptionCheck() != 0) {
        // sadtrombone
        gainmap->decStrong(0);
        ALOGE("*** Uncaught exception returned from Java call!\n");
        env->ExceptionDescribe();
    }
    return obj;
}

static void Gainmap_destructor(Gainmap* gainmap) {
    gainmap->decStrong(0);
}

static jlong Gainmap_getNativeFinalizer(JNIEnv*, jobject) {
    return static_cast<jlong>(reinterpret_cast<uintptr_t>(&Gainmap_destructor));
}

jlong Gainmap_createEmpty(JNIEnv*, jobject) {
    Gainmap* gainmap = new Gainmap();
    gainmap->incStrong(0);
    return static_cast<jlong>(reinterpret_cast<uintptr_t>(gainmap));
}

jlong Gainmap_createCopy(JNIEnv*, jobject, jlong sourcePtr) {
    Gainmap* gainmap = new Gainmap();
    gainmap->incStrong(0);
    if (sourcePtr) {
        Gainmap* src = fromJava(sourcePtr);
        gainmap->info = src->info;
    }
    return static_cast<jlong>(reinterpret_cast<uintptr_t>(gainmap));
}

static void Gainmap_setBitmap(JNIEnv* env, jobject, jlong gainmapPtr, jobject jBitmap) {
    android::Bitmap* bitmap = GraphicsJNI::getNativeBitmap(env, jBitmap);
    fromJava(gainmapPtr)->bitmap = sk_ref_sp(bitmap);
}

static void Gainmap_setRatioMin(JNIEnv*, jobject, jlong gainmapPtr, jfloat r, jfloat g, jfloat b) {
    fromJava(gainmapPtr)->info.fGainmapRatioMin = {r, g, b, 1.f};
}

static void Gainmap_getRatioMin(JNIEnv* env, jobject, jlong gainmapPtr, jfloatArray components) {
    const auto value = fromJava(gainmapPtr)->info.fGainmapRatioMin;
    jfloat buf[3]{value.fR, value.fG, value.fB};
    env->SetFloatArrayRegion(components, 0, 3, buf);
}

static void Gainmap_setRatioMax(JNIEnv*, jobject, jlong gainmapPtr, jfloat r, jfloat g, jfloat b) {
    fromJava(gainmapPtr)->info.fGainmapRatioMax = {r, g, b, 1.f};
}

static void Gainmap_getRatioMax(JNIEnv* env, jobject, jlong gainmapPtr, jfloatArray components) {
    const auto value = fromJava(gainmapPtr)->info.fGainmapRatioMax;
    jfloat buf[3]{value.fR, value.fG, value.fB};
    env->SetFloatArrayRegion(components, 0, 3, buf);
}

static void Gainmap_setGamma(JNIEnv*, jobject, jlong gainmapPtr, jfloat r, jfloat g, jfloat b) {
    fromJava(gainmapPtr)->info.fGainmapGamma = {r, g, b, 1.f};
}

static void Gainmap_getGamma(JNIEnv* env, jobject, jlong gainmapPtr, jfloatArray components) {
    const auto value = fromJava(gainmapPtr)->info.fGainmapGamma;
    jfloat buf[3]{value.fR, value.fG, value.fB};
    env->SetFloatArrayRegion(components, 0, 3, buf);
}

static void Gainmap_setEpsilonSdr(JNIEnv*, jobject, jlong gainmapPtr, jfloat r, jfloat g,
                                  jfloat b) {
    fromJava(gainmapPtr)->info.fEpsilonSdr = {r, g, b, 1.f};
}

static void Gainmap_getEpsilonSdr(JNIEnv* env, jobject, jlong gainmapPtr, jfloatArray components) {
    const auto value = fromJava(gainmapPtr)->info.fEpsilonSdr;
    jfloat buf[3]{value.fR, value.fG, value.fB};
    env->SetFloatArrayRegion(components, 0, 3, buf);
}

static void Gainmap_setEpsilonHdr(JNIEnv*, jobject, jlong gainmapPtr, jfloat r, jfloat g,
                                  jfloat b) {
    fromJava(gainmapPtr)->info.fEpsilonHdr = {r, g, b, 1.f};
}

static void Gainmap_getEpsilonHdr(JNIEnv* env, jobject, jlong gainmapPtr, jfloatArray components) {
    const auto value = fromJava(gainmapPtr)->info.fEpsilonHdr;
    jfloat buf[3]{value.fR, value.fG, value.fB};
    env->SetFloatArrayRegion(components, 0, 3, buf);
}

static void Gainmap_setDisplayRatioHdr(JNIEnv*, jobject, jlong gainmapPtr, jfloat max) {
    fromJava(gainmapPtr)->info.fDisplayRatioHdr = max;
}

static jfloat Gainmap_getDisplayRatioHdr(JNIEnv*, jobject, jlong gainmapPtr) {
    return fromJava(gainmapPtr)->info.fDisplayRatioHdr;
}

static void Gainmap_setDisplayRatioSdr(JNIEnv*, jobject, jlong gainmapPtr, jfloat min) {
    fromJava(gainmapPtr)->info.fDisplayRatioSdr = min;
}

static jfloat Gainmap_getDisplayRatioSdr(JNIEnv*, jobject, jlong gainmapPtr) {
    return fromJava(gainmapPtr)->info.fDisplayRatioSdr;
}

// ----------------------------------------------------------------------------
// Serialization
// ----------------------------------------------------------------------------

static void Gainmap_writeToParcel(JNIEnv* env, jobject, jlong nativeObject, jobject parcel) {
#ifdef __ANDROID__  // Layoutlib does not support parcel
    if (parcel == NULL) {
        ALOGD("write null parcel\n");
        return;
    }
    ScopedParcel p(env, parcel);
    SkGainmapInfo info = fromJava(nativeObject)->info;
    // write gainmap to parcel
    // ratio min
    p.writeFloat(info.fGainmapRatioMin.fR);
    p.writeFloat(info.fGainmapRatioMin.fG);
    p.writeFloat(info.fGainmapRatioMin.fB);
    // ratio max
    p.writeFloat(info.fGainmapRatioMax.fR);
    p.writeFloat(info.fGainmapRatioMax.fG);
    p.writeFloat(info.fGainmapRatioMax.fB);
    // gamma
    p.writeFloat(info.fGainmapGamma.fR);
    p.writeFloat(info.fGainmapGamma.fG);
    p.writeFloat(info.fGainmapGamma.fB);
    // epsilonsdr
    p.writeFloat(info.fEpsilonSdr.fR);
    p.writeFloat(info.fEpsilonSdr.fG);
    p.writeFloat(info.fEpsilonSdr.fB);
    // epsilonhdr
    p.writeFloat(info.fEpsilonHdr.fR);
    p.writeFloat(info.fEpsilonHdr.fG);
    p.writeFloat(info.fEpsilonHdr.fB);
    // display ratio sdr
    p.writeFloat(info.fDisplayRatioSdr);
    // display ratio hdr
    p.writeFloat(info.fDisplayRatioHdr);
    // base image type
    p.writeInt32(static_cast<int32_t>(info.fBaseImageType));
#else
    doThrowRE(env, "Cannot use parcels outside of Android!");
#endif
}

static void Gainmap_readFromParcel(JNIEnv* env, jobject, jlong nativeObject, jobject parcel) {
#ifdef __ANDROID__  // Layoutlib does not support parcel
    if (parcel == NULL) {
        jniThrowNullPointerException(env, "parcel cannot be null");
        return;
    }
    ScopedParcel p(env, parcel);

    SkGainmapInfo info;
    info.fGainmapRatioMin = {p.readFloat(), p.readFloat(), p.readFloat(), 1.f};
    info.fGainmapRatioMax = {p.readFloat(), p.readFloat(), p.readFloat(), 1.f};
    info.fGainmapGamma = {p.readFloat(), p.readFloat(), p.readFloat(), 1.f};
    info.fEpsilonSdr = {p.readFloat(), p.readFloat(), p.readFloat(), 1.f};
    info.fEpsilonHdr = {p.readFloat(), p.readFloat(), p.readFloat(), 1.f};
    info.fDisplayRatioSdr = p.readFloat();
    info.fDisplayRatioHdr = p.readFloat();
    info.fBaseImageType = static_cast<SkGainmapInfo::BaseImageType>(p.readInt32());

    fromJava(nativeObject)->info = info;
#else
    jniThrowRuntimeException(env, "Cannot use parcels outside of Android");
#endif
}

// ----------------------------------------------------------------------------
// JNI Glue
// ----------------------------------------------------------------------------

static const JNINativeMethod gGainmapMethods[] = {
        {"nGetFinalizer", "()J", (void*)Gainmap_getNativeFinalizer},
        {"nCreateEmpty", "()J", (void*)Gainmap_createEmpty},
        {"nCreateCopy", "(J)J", (void*)Gainmap_createCopy},
        {"nSetBitmap", "(JLandroid/graphics/Bitmap;)V", (void*)Gainmap_setBitmap},
        {"nSetRatioMin", "(JFFF)V", (void*)Gainmap_setRatioMin},
        {"nGetRatioMin", "(J[F)V", (void*)Gainmap_getRatioMin},
        {"nSetRatioMax", "(JFFF)V", (void*)Gainmap_setRatioMax},
        {"nGetRatioMax", "(J[F)V", (void*)Gainmap_getRatioMax},
        {"nSetGamma", "(JFFF)V", (void*)Gainmap_setGamma},
        {"nGetGamma", "(J[F)V", (void*)Gainmap_getGamma},
        {"nSetEpsilonSdr", "(JFFF)V", (void*)Gainmap_setEpsilonSdr},
        {"nGetEpsilonSdr", "(J[F)V", (void*)Gainmap_getEpsilonSdr},
        {"nSetEpsilonHdr", "(JFFF)V", (void*)Gainmap_setEpsilonHdr},
        {"nGetEpsilonHdr", "(J[F)V", (void*)Gainmap_getEpsilonHdr},
        {"nSetDisplayRatioHdr", "(JF)V", (void*)Gainmap_setDisplayRatioHdr},
        {"nGetDisplayRatioHdr", "(J)F", (void*)Gainmap_getDisplayRatioHdr},
        {"nSetDisplayRatioSdr", "(JF)V", (void*)Gainmap_setDisplayRatioSdr},
        {"nGetDisplayRatioSdr", "(J)F", (void*)Gainmap_getDisplayRatioSdr},
        {"nWriteGainmapToParcel", "(JLandroid/os/Parcel;)V", (void*)Gainmap_writeToParcel},
        {"nReadGainmapFromParcel", "(JLandroid/os/Parcel;)V", (void*)Gainmap_readFromParcel},
};

int register_android_graphics_Gainmap(JNIEnv* env) {
    gGainmap_class = MakeGlobalRefOrDie(env, FindClassOrDie(env, "android/graphics/Gainmap"));
    gGainmap_constructorMethodID =
            GetMethodIDOrDie(env, gGainmap_class, "<init>", "(Landroid/graphics/Bitmap;J)V");
    return android::RegisterMethodsOrDie(env, "android/graphics/Gainmap", gGainmapMethods,
                                         NELEM(gGainmapMethods));
}

}  // namespace android
