// Copyright 2014-2025 Jesper Larsson
//
// This file is part of Klipspringer, <https://klipspringer.avadeaux.net/>
//
// Klipspringer is free software: you can redistribute it and/or modify it under the terms of the
// GNU General Public License as published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// Klipspringer is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
// even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License along with Klipspringer. If
// not, see <https://www.gnu.org/licenses/>.

#include <stdlib.h>
#include "Library.h"
#include "klip_version.h"
#include "net_avadeaux_klipspringer_codec_Library.h" // generated by javac -h

static jobject errClass, ioexClass, ecodexClass, fnotfClass, pictargetClass, bigEnd, littleEnd, platformEnd;
static jmethodID ecodexMid, orderMid, clearMid, positionMid, limitMid, pictureMid;
static jint support = 0;        // actual value is set when init is called

void init_error(const char *message, ...) {
    va_list ap;
    va_start(ap, message);
    vfprintf(stderr, message, ap);
    fprintf(stderr, "\n");
    exit(1);
}

static jmethodID initMethodID(JNIEnv *env, jclass jClass, const char *name, const char *signature) {
    jmethodID mid = (*env)->GetMethodID(env, jClass, name, signature);
    if (mid == NULL) { init_error("Method not found: %s(%s)", name, signature); }
    return mid;
}

static jobject initGlobalObject(JNIEnv *env, jobject local) {
    jobject global = (*env)->NewGlobalRef(env, local);
    if (global == NULL) { init_error("Failed to get global reference"); }
    return global;
}

jobject platform_order() { return platformEnd; }

jobject wrapByteBuffer(JNIEnv *env, void *p, jlong capacity, jboolean bigend) {
    jobject bb = (*env)->NewDirectByteBuffer(env, p, capacity);
    if (bb == NULL) {
        raiseError(env, "Failed to wrap array as byte buffer");
        return NULL;
    }
    (*env)->CallObjectMethod(env, bb, orderMid, bigend ? bigEnd : littleEnd);
    if ((*env)->ExceptionCheck(env)) { return NULL; }
    return bb;
}

bool byteBufferClear(JNIEnv *env, jobject bb) {
    (*env)->CallObjectMethod(env, bb, clearMid);
    return !(*env)->ExceptionCheck(env);
}

bool byteBufferPosition(JNIEnv *env, jobject bb, jint pos) {
    (*env)->CallObjectMethod(env, bb, positionMid, pos);
    return !(*env)->ExceptionCheck(env);
}

bool byteBufferLimit(JNIEnv *env, jobject bb, jint lim) {
    (*env)->CallObjectMethod(env, bb, limitMid, lim);
    return !(*env)->ExceptionCheck(env);
}

// Throws if not already thrown.
const char *raiseException(JNIEnv *env, const jclass exClass, const char *message) {
    if (!(*env)->ExceptionCheck(env)) {
        (*env)->ThrowNew(env, exClass, message);
    }
    return message;
}
const char *raiseError(JNIEnv *env, const char *message) { return raiseException(env, errClass, message); }
const char *raiseIOException(JNIEnv *env, const char *message) { return raiseException(env, ioexClass, message); }
const char *raiseFileNotFound(JNIEnv *env, const char *message) { return raiseException(env, fnotfClass, message); }
const char *raiseErrorCodeException(JNIEnv *env, jint code, const char *message) {
    jstring m = (*env)->NewStringUTF(env, message);
    if (m == NULL) { return raiseError(env, "Failed to create message string"); }
    jobject ex = (*env)->NewObject(env, ecodexClass, ecodexMid, code, m);
    if (ex == NULL) { return raiseError(env, "Construction of error code exception failed"); }
    (*env)->Throw(env, ex);
    return message;
}

bool accepts_picture(JNIEnv *env, jobject pictarget) {
    return (*env)->IsInstanceOf(env, pictarget, pictargetClass);
}

bool picture(JNIEnv *env, jobject pictarget, const char *mime_type, const char *type, const char *desc, void *data, jlong data_length) {
    jstring jmime = (*env)->NewStringUTF(env, mime_type);
    if (jmime == NULL) { raiseError(env, "Cannot create MIME string"); return false; }
    jstring jtype = (*env)->NewStringUTF(env, type);
    if (jtype == NULL) { raiseError(env, "Cannot create type string"); return false; }
    jstring jdesc = (*env)->NewStringUTF(env, desc);
    if (jdesc == NULL) { raiseError(env, "Cannot create description string"); return false; }
    jobject bb = wrapByteBuffer(env, data, data_length, platform_bigend());
    if (bb == NULL) { return false; }
    (*env)->CallObjectMethod(env, pictarget, pictureMid, jmime, jtype, jdesc, bb);
    return !(*env)->ExceptionCheck(env);
}

// -------------------------------------------------------------------------------------------------
// Native Library methods

#define METHOD(name) JNICALL Java_net_avadeaux_klipspringer_codec_Library_ ## name

JNIEXPORT void
METHOD(init) (JNIEnv *env,
              jclass jthisClass,                // JniUtil class, because static method
              jint jflacbit,                    // mask bit for FLAC support
              jint jlamebit,                    // mask bit for LAME support
              jint jalsabit,                    // mask bit for ALSA support
              jint jvorbisbit,                  // mask bit for Vorbis support
              jint jopusbit,                    // mask bit for Opus support
              jclass jbbClass,                  // ByteBuffer class
              jclass jerrClass,                 // Error class
              jclass jioexClass,                // IOException class
              jclass jecodexClass,              // CodeIOException class
              jclass jfnotfClass,               // FileNotFoundException class
              jclass jpictargetClass,           // AudioDecoder.PictureTarget class
              jobject jbigEnd,                  // ByteOrder.BIG_ENDIAN
              jobject jlittleEnd)               // ByteOrder.LITTLE_ENDIAN
{
    support = 0;
#   ifndef FLAC_UNSUPPORTED
    support |= jflacbit;
#   endif
#   ifndef LAME_UNSUPPORTED
    support |= jlamebit;
#   endif
#   ifndef ALSA_UNSUPPORTED
    support |= jalsabit;
#   endif
#   ifndef VORBIS_UNSUPPORTED
    support |= jvorbisbit;
#   endif
#   ifndef OPUS_UNSUPPORTED
    support |= jopusbit;
#   endif

    errClass = initGlobalObject(env, jerrClass);
    ioexClass = initGlobalObject(env, jioexClass);
    ecodexClass = initGlobalObject(env, jecodexClass);
    fnotfClass = initGlobalObject(env, jfnotfClass);
    pictargetClass = initGlobalObject(env, jpictargetClass);
    bigEnd = initGlobalObject(env, jbigEnd);
    littleEnd = initGlobalObject(env, jlittleEnd);
    platformEnd = platform_bigend() ? bigEnd : littleEnd;

    ecodexMid = initMethodID(env, ecodexClass, "<init>", "(ILjava/lang/String;)V");
    orderMid = initMethodID(env, jbbClass, "order", "(Ljava/nio/ByteOrder;)Ljava/nio/ByteBuffer;");
    clearMid = initMethodID(env, jbbClass, "clear", "()Ljava/nio/ByteBuffer;");
    positionMid = initMethodID(env, jbbClass, "position", "(I)Ljava/nio/ByteBuffer;");
    limitMid = initMethodID(env, jbbClass, "limit", "(I)Ljava/nio/ByteBuffer;");
    pictureMid = initMethodID(env, pictargetClass, "picture", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/nio/ByteBuffer;)V");
}

JNIEXPORT jint
METHOD(support) (JNIEnv *env,
                 jclass jthisClass)
{
    return support;
}

JNIEXPORT jstring
METHOD(version) (JNIEnv *env,
                 jclass jthisClass)
{
    return (*env)->NewStringUTF(env, KLIP_VERSION);
}
