/*
 * This file is part of libbluray
 * Copyright (C) 2010      William Hahne
 * Copyright (C) 2012-2014 Petri Hintukainen <phintuka@users.sourceforge.net>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library 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
 * Lesser General Public License for more details.s
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library. If not, see
 * <http://www.gnu.org/licenses/>.
 */

#if HAVE_CONFIG_H
#include "config.h"
#endif

#include "bdjo.h"
#include "util.h"

#include "bdj/bdjo_parse.h"

#include "bluray.h"
#include "bluray_internal.h"
#include "decoders/overlay.h"
#include "disc/disc.h"

#include "file/file.h"
#include "util/logging.h"
#include "util/macro.h"

#include <stdlib.h>
#include <string.h>
#include <inttypes.h>

/* this automatically generated header is included to cross-check native function signatures */
#include "org_videolan_Libbluray.h"

/* Disable some warnings */
#if defined __GNUC__
#pragma GCC diagnostic ignored "-Wunused-parameter"
#endif

#ifdef __cplusplus
#define CPP_EXTERN extern
#else
#define CPP_EXTERN
#endif

/*
 * build org.videolan.TitleInfo
 */

static jobject _make_title_info(JNIEnv* env, const BLURAY_TITLE *title, int title_number)
{
    jobject ti = NULL;
    if (title) {
        int title_type = title->bdj ? 2 : 1;
        int playback_type = (!!title->interactive) + ((!!title->bdj) << 1);
        ti = bdj_make_object(env, "org/videolan/TitleInfo",
                             "(IIII)V",
                             (jint)title_number, (jint)title_type, (jint)playback_type, (jint)title->id_ref);
    }
    return ti;
}

static jobjectArray _make_title_infos(JNIEnv * env, const BLURAY_DISC_INFO *disc_info)
{
    jobjectArray titleArr = bdj_make_array(env, "org/videolan/TitleInfo", disc_info->num_titles + 2);
    jobject titleInfo;

    if (!titleArr) {
        return NULL;
    }

    for (unsigned i = 0; i <= disc_info->num_titles; i++) {
        titleInfo = _make_title_info(env, disc_info->titles[i], i);
        (*env)->SetObjectArrayElement(env, titleArr, i, titleInfo);
    }

    titleInfo = _make_title_info(env, disc_info->first_play, 65535);
    (*env)->SetObjectArrayElement(env, titleArr, disc_info->num_titles + 1, titleInfo);

    return titleArr;
}

/*
 * build org.videolan.PlaylistInfo
 */

static jobjectArray _make_stream_array(JNIEnv* env, int count, BLURAY_STREAM_INFO* streams)
{
    jobjectArray streamArr = bdj_make_array(env,
                    "org/videolan/StreamInfo", count);

    if (!streamArr) {
        return NULL;
    }

    for (int i = 0; i < count; i++) {
        BLURAY_STREAM_INFO s = streams[i];
        jstring lang = (*env)->NewStringUTF(env, (char*)s.lang);
        jobject streamObj = bdj_make_object(env,
                "org/videolan/StreamInfo",
                "(BBBCLjava/lang/String;BB)V",
                (jbyte)s.coding_type, (jbyte)s.format,
                (jbyte)s.rate, (jchar)s.char_code,
                lang,
                (jbyte)s.aspect, (jbyte)s.subpath_id);
        (*env)->SetObjectArrayElement(env, streamArr, i, streamObj);
    }

    return streamArr;
}

static jobject _make_playlist_info(JNIEnv* env, BLURAY_TITLE_INFO* ti)
{
    jobjectArray marks = bdj_make_array(env, "org/videolan/TIMark",
            ti->mark_count);

    if (marks) {
    for (uint32_t i = 0; i < ti->mark_count; i++) {
        BLURAY_TITLE_MARK m = ti->marks[i];
        jobject mark = bdj_make_object(env,
                "org/videolan/TIMark",
                "(IIJ)V",
                (jint)m.idx, (jint)m.type, (jlong)m.start);
        (*env)->SetObjectArrayElement(env, marks, i, mark);
    }
    }

    jobjectArray clips = bdj_make_array(env, "org/videolan/TIClip",
            ti->clip_count);

    if (clips) {
    for (uint32_t i = 0; i < ti->clip_count; i++) {
        BLURAY_CLIP_INFO info = ti->clips[i];

        jobjectArray videoStreams = _make_stream_array(env, info.video_stream_count,
                info.video_streams);

        jobjectArray audioStreams = _make_stream_array(env, info.audio_stream_count,
                info.audio_streams);

        jobjectArray pgStreams = _make_stream_array(env, info.pg_stream_count,
                info.pg_streams);

        jobjectArray igStreams = _make_stream_array(env, info.ig_stream_count,
                info.ig_streams);

        jobjectArray secVideoStreams = _make_stream_array(env, info.sec_video_stream_count,
                info.sec_video_streams);

        jobjectArray secAudioStreams = _make_stream_array(env, info.sec_audio_stream_count,
                info.sec_audio_streams);

        jobject clip = bdj_make_object(env,
                "org/videolan/TIClip",
                "(I[Lorg/videolan/StreamInfo;[Lorg/videolan/StreamInfo;[Lorg/videolan/StreamInfo;[Lorg/videolan/StreamInfo;[Lorg/videolan/StreamInfo;[Lorg/videolan/StreamInfo;)V",
                (jint)i, videoStreams, audioStreams, pgStreams, igStreams, secVideoStreams, secAudioStreams);

        (*env)->SetObjectArrayElement(env, clips, i, clip);
    }
    }

    return bdj_make_object(env,
             "org/videolan/PlaylistInfo",
             "(IJI[Lorg/videolan/TIMark;[Lorg/videolan/TIClip;)V",
             (jint)ti->playlist, (jlong)ti->duration, (jint)ti->angle_count,
             marks, clips);
}

/*
 *
 */

JNIEXPORT jobjectArray JNICALL Java_org_videolan_Libbluray_getTitleInfosN
  (JNIEnv * env, jclass cls, jlong np)
 {
    BLURAY* bd = (BLURAY*)(intptr_t)np;
    const BLURAY_DISC_INFO *disc_info;

    BD_DEBUG(DBG_JNI, "getTitleInfosN()\n");

    disc_info = bd_get_disc_info(bd);

    return  _make_title_infos(env, disc_info);
}

JNIEXPORT jobject JNICALL Java_org_videolan_Libbluray_getPlaylistInfoN
  (JNIEnv * env, jclass cls, jlong np, jint playlist)
{
    BLURAY *bd = (BLURAY*)(intptr_t)np;
    BLURAY_TITLE_INFO* ti;

    BD_DEBUG(DBG_JNI, "getPlaylistInfoN(%d)\n", (int)playlist);

    ti = bd_get_playlist_info(bd, playlist, 0);
    if (!ti)
        return NULL;

    jobject titleInfo = _make_playlist_info(env, ti);

    bd_free_title_info(ti);

    return titleInfo;
}

JNIEXPORT jbyteArray JNICALL Java_org_videolan_Libbluray_getAacsDataN
  (JNIEnv * env, jclass cls, jlong np, jint type)
{
    BLURAY* bd = (BLURAY*)(intptr_t)np;
    const uint8_t *data = disc_get_data(bdpriv_get_disc(bd), type);
    size_t data_size = 16;

    BD_DEBUG(DBG_JNI, "getAacsDataN(%d) -> %p\n", (int)type, (const void *)data);

    if (!data) {
        return NULL;
    }
    if (type == 1/*BD_AACS_DISC_ID*/) {
        data_size = 20;
    }
    if (type == 7/*BD_AACS_CONTENT_CERT_ID*/) {
        data_size = 6;
    }
    if (type == 8/*BD_AACS_BDJ_ROOT_CERT_HASH*/) {
        data_size = 20;
    }
    if (type == 0x1000/*BD_BDPLUS_TYPE*/) {
        data_size = strlen((const char*)data);
    }

    jbyteArray array = (*env)->NewByteArray(env, data_size);
    (*env)->SetByteArrayRegion(env, array, 0, data_size, (const jbyte *)data);
    return array;
}

JNIEXPORT jlong JNICALL Java_org_videolan_Libbluray_getUOMaskN(JNIEnv * env,
        jclass cls, jlong np) {
    BLURAY* bd = (BLURAY*)(intptr_t)np;

    BD_DEBUG(DBG_JNI, "getUOMaskN()\n");
    return bdpriv_get_uo_mask(bd);
}

JNIEXPORT void JNICALL Java_org_videolan_Libbluray_setUOMaskN(JNIEnv * env,
        jclass cls, jlong np, jboolean menuCallMask, jboolean titleSearchMask) {
    BLURAY* bd = (BLURAY*)(intptr_t)np;

    BD_DEBUG(DBG_JNI, "setUOMaskN(%d,%d)\n", (int)menuCallMask, (int)titleSearchMask);
    bdpriv_set_bdj_uo_mask(bd, ((!!menuCallMask) * BDJ_MENU_CALL_MASK) | ((!!titleSearchMask) * BDJ_TITLE_SEARCH_MASK));
}

JNIEXPORT void JNICALL Java_org_videolan_Libbluray_setKeyInterestN(JNIEnv * env,
        jclass cls, jlong np, jint mask) {
    BLURAY* bd = (BLURAY*)(intptr_t)np;

    BD_DEBUG(DBG_JNI, "setKeyInterestN(0x%x)\n", (int)mask);
    bdpriv_set_bdj_kit(bd, mask);
}

JNIEXPORT jint JNICALL Java_org_videolan_Libbluray_setVirtualPackageN(JNIEnv * env,
        jclass cls, jlong np, jstring vpPath, jboolean psr_init_backup) {
    BLURAY* bd = (BLURAY*)(intptr_t)np;
    const char *path = NULL;
    jint result;

    if (vpPath) {
        path = (*env)->GetStringUTFChars(env, vpPath, NULL);
    }

    BD_DEBUG(DBG_JNI|DBG_CRIT, "setVirtualPackageN(%s,%d)\n", path ? path : "<null>", (int)psr_init_backup);

    result = bdpriv_set_virtual_package(bd, path, (int)psr_init_backup);

    if (vpPath) {
        (*env)->ReleaseStringUTFChars(env, vpPath, path);
    }

    return result;
}

JNIEXPORT jlong JNICALL Java_org_videolan_Libbluray_seekN(JNIEnv * env,
        jclass cls, jlong np, jint playitem, jint playmark, jlong tick) {
    BLURAY* bd = (BLURAY*)(intptr_t)np;

    BD_DEBUG(DBG_JNI, "seekN(tick=%" PRId64 ", mark=%d, playitem=%d)\n", (int64_t)tick, (int)playmark, (int)playitem);

    return bdpriv_seek(bd, playitem, playmark, tick);
}

JNIEXPORT jint JNICALL Java_org_videolan_Libbluray_selectPlaylistN(
        JNIEnv * env, jclass cls, jlong np, jint playlist, jint playitem, jint playmark, jlong time) {
    BLURAY* bd = (BLURAY*)(intptr_t)np;

    if (!bd) {
        return 0;
    }

    BD_DEBUG(DBG_JNI, "selectPlaylistN(pl=%d, pi=%d, pm=%d, time=%ld)\n",
             (int)playlist, (int)playitem, (int)playmark, (long)time);

    return bdpriv_play_playlist_at(bd, playlist, playitem, playmark, time);
}

JNIEXPORT jint JNICALL Java_org_videolan_Libbluray_selectTitleN(JNIEnv * env,
        jclass cls, jlong np, jint title) {
    BLURAY* bd = (BLURAY*)(intptr_t)np;

    BD_DEBUG(DBG_JNI, "selectTitleN(%d)\n", (int)title);

    return bdpriv_play_title_internal(bd, title);
}

JNIEXPORT jint JNICALL Java_org_videolan_Libbluray_selectAngleN(JNIEnv * env,
        jclass cls, jlong np, jint angle) {
    BLURAY* bd = (BLURAY*)(intptr_t)np;
    return bd_select_angle(bd, angle - 1);
}

JNIEXPORT jint JNICALL Java_org_videolan_Libbluray_soundEffectN(JNIEnv * env,
        jclass cls, jlong np, jint id) {
    BLURAY* bd = (BLURAY*)(intptr_t)np;
    return bdpriv_sound_effect(bd, id);
}

JNIEXPORT jlong JNICALL Java_org_videolan_Libbluray_tellTimeN(JNIEnv * env,
        jclass cls, jlong np) {
    BLURAY* bd = (BLURAY*)(intptr_t)np;
    return bd_tell_time(bd);
}

JNIEXPORT jint JNICALL Java_org_videolan_Libbluray_selectRateN(JNIEnv * env,
        jclass cls, jlong np, jfloat rate, jint reason) {
    BLURAY* bd = (BLURAY*)(intptr_t)np;

    BD_DEBUG(DBG_JNI, "selectRateN(%1.1f, %d)\n", (float)rate, (int)reason);

    bdpriv_select_rate(bd, (float)rate, reason);
    return 1;
}

JNIEXPORT jint JNICALL Java_org_videolan_Libbluray_readRegN(JNIEnv * env,
        jclass cls, jlong np, jint is_psr, jint num) {
    BLURAY* bd = (BLURAY*)(intptr_t)np;
    int value = bdpriv_reg_read(bd, is_psr, num);

    BD_DEBUG(DBG_JNI, "readRegN(%s_%d) -> %d\n", is_psr ? "PSR" : "GPR", (int)num, (int)value);

    return value;
}

JNIEXPORT jint JNICALL Java_org_videolan_Libbluray_writeRegN(JNIEnv * env,
        jclass cls, jlong np, jint is_psr, jint num, jint value, jint mask) {
    BLURAY* bd = (BLURAY*)(intptr_t)np;

    if ((uint32_t)mask == 0xffffffff) {
        BD_DEBUG(DBG_JNI, "writeRegN(%s_%d,%d)\n",
                 is_psr ? "PSR" : "GPR", (int)num, (int)value);
    } else {
        BD_DEBUG(DBG_JNI, "writeRegN(%s_%d,0x%x,0x%08x)\n",
                 is_psr ? "PSR" : "GPR", (int)num, (int)value, (int)mask);
    }

    return bdpriv_reg_write(bd, is_psr, num, value, mask);
}

JNIEXPORT jint JNICALL Java_org_videolan_Libbluray_cacheBdRomFileN(JNIEnv * env,
                                                                   jclass cls, jlong np,
                                                                   jstring jrel_path, jstring jcache_path) {

    BLURAY *bd = (BLURAY*)(intptr_t)np;
    BD_DISC *disc = bdpriv_get_disc(bd);
    int result = -1;

    const char *rel_path = (*env)->GetStringUTFChars(env, jrel_path, NULL);
    const char *cache_path = (*env)->GetStringUTFChars(env, jcache_path, NULL);
    if (!rel_path || !cache_path) {
        BD_DEBUG(DBG_JNI | DBG_CRIT, "cacheBdRomFile() failed: no path\n");
        goto out;
    }
    BD_DEBUG(DBG_JNI, "cacheBdRomFile(%s => %s)\n", rel_path, cache_path);

    result = disc_cache_bdrom_file(disc, rel_path, cache_path);

 out:
    if (rel_path) {
        (*env)->ReleaseStringUTFChars(env, jrel_path, rel_path);
    }
    if (cache_path) {
        (*env)->ReleaseStringUTFChars(env, jcache_path, cache_path);
    }

    return result;
}

JNIEXPORT jobjectArray JNICALL Java_org_videolan_Libbluray_listBdFilesN(JNIEnv * env,
                                                                        jclass cls, jlong np, jstring jpath,
                                                                        jboolean onlyBdRom) {

    BLURAY *bd = (BLURAY*)(intptr_t)np;
    BD_DISC *disc = bdpriv_get_disc(bd);

    const char *path = (*env)->GetStringUTFChars(env, jpath, NULL);
    if (!path) {
        BD_DEBUG(DBG_JNI | DBG_CRIT, "listBdFilesN() failed: no path\n");
        return NULL;
    }
    BD_DEBUG(DBG_JNI, "listBdFilesN(%s)\n", path);

    /* open directory stream */
    BD_DIR_H *dp;
    if (onlyBdRom) {
        dp = disc_open_bdrom_dir(disc, path);
    } else {
        dp = disc_open_dir(disc, path);
    }
    if (!dp) {
        BD_DEBUG(DBG_JNI, "failed opening directory %s\n", path);
        (*env)->ReleaseStringUTFChars(env, jpath, path);
        return NULL;
    }
    (*env)->ReleaseStringUTFChars(env, jpath, path);

    /* count files and create java strings (java array size must be known when it is created) */
    jstring  *files = NULL;
    unsigned  count = 0;
    unsigned  allocated = 0;
    BD_DIRENT ent;
    while (!dir_read(dp, &ent)) {
        if (strcmp(ent.d_name, ".") && strcmp(ent.d_name, "..")) {
            if (allocated <= count) {
                allocated += 512;
                jstring *tmp = realloc(files, sizeof(*files) * allocated);
                if (!tmp) {
                    BD_DEBUG(DBG_JNI | DBG_CRIT, "failed allocating memory for %u directory entries\n", allocated);
                    break;
                }
                files = tmp;
            }
            files[count] = (*env)->NewStringUTF(env, ent.d_name);
            count++;
        }
    }
    dir_close(dp);

    /* allocate java array */
    jobjectArray arr = bdj_make_array(env, "java/lang/String", count);
    if (!arr) {
        BD_DEBUG(DBG_JNI | DBG_CRIT, "failed creating array [%d]\n", count);
    } else {
        /* populate files to array */
        unsigned ii;
        for (ii = 0; ii < count; ii++) {
            (*env)->SetObjectArrayElement(env, arr, ii, files[ii]);
        }
    }

    X_FREE(files);

    return arr;
}


JNIEXPORT jobject JNICALL Java_org_videolan_Libbluray_getBdjoN(JNIEnv * env,
                                                               jclass cls, jlong np, jstring jfile) {

    BLURAY           *bd = (BLURAY*)(intptr_t)np;
    struct bdjo_data *bdjo;
    jobject           jbdjo = NULL;

    const char *file = (*env)->GetStringUTFChars(env, jfile, NULL);
    if (!file) {
        BD_DEBUG(DBG_JNI | DBG_CRIT, "getBdjoN() failed: no path\n");
        return NULL;
    }
    BD_DEBUG(DBG_JNI, "getBdjoN(%s)\n", file);

    bdjo = bdjo_get(bdpriv_get_disc(bd), file);
    if (bdjo) {
        jbdjo = bdjo_make_jobj(env, bdjo);
        bdjo_free(&bdjo);
    } else {
        BD_DEBUG(DBG_JNI | DBG_CRIT, "getBdjoN(%s) failed\n", file);
    }

    (*env)->ReleaseStringUTFChars(env, jfile, file);

    return jbdjo;
}

static void _updateGraphic(JNIEnv * env,
        BLURAY *bd, jint width, jint height, jintArray rgbArray,
        jint x0, jint y0, jint x1, jint y1,
        BD_ARGB_BUFFER *buf) {

    /* close ? */
    if (!rgbArray) {
        bdpriv_bdj_osd_cb(bd, NULL, (int)width, (int)height, 0, 0, 0, 0);
        return;
    }

    if (buf) {

        /* copy to application-allocated buffer */

        jint y, *dst;
        jsize offset;

        /* set dirty area before lock() */
        buf->dirty[BD_OVERLAY_IG].x0 = (uint16_t)x0;
        buf->dirty[BD_OVERLAY_IG].x1 = (uint16_t)x1;
        buf->dirty[BD_OVERLAY_IG].y0 = (uint16_t)y0;
        buf->dirty[BD_OVERLAY_IG].y1 = (uint16_t)y1;

        /* get buffer */
        if (buf->lock) {
            buf->lock(buf);
        }
        if (!buf->buf[BD_OVERLAY_IG]) {
            BD_DEBUG(DBG_BDJ | DBG_CRIT, "ARGB frame buffer missing\n");
            if (buf->unlock) {
                buf->unlock(buf);
            }
            return;
        }

        /* check buffer size */

        if (buf->width < width || buf->height < height) {
            /* assume buffer is only for the dirty arrea */
            BD_DEBUG(DBG_BDJ, "ARGB frame buffer size is smaller than BD-J frame buffer size (app: %dx%d BD-J: %ldx%ld)\n",
                     buf->width, buf->height, (long)width, (long)height);

            if (buf->width < (x1 - x0 + 1) || buf->height < (y1 - y0 + 1)) {
                BD_DEBUG(DBG_BDJ | DBG_CRIT, "ARGB frame buffer size is smaller than dirty area\n");
                if (buf->unlock) {
                    buf->unlock(buf);
                }
                return;
            }

            dst = (jint*)buf->buf[BD_OVERLAY_IG];

        } else {

            dst = (jint*)buf->buf[BD_OVERLAY_IG] + y0 * buf->width + x0;

            /* clip */
            if (y1 >= buf->height) {
                BD_DEBUG(DBG_BDJ | DBG_CRIT, "Cropping %ld rows from bottom\n", (long)(y1 - buf->height));
                y1 = buf->height - 1;
            }
            if (x1 >= buf->width) {
                BD_DEBUG(DBG_BDJ | DBG_CRIT, "Cropping %ld pixels from right\n", (long)(x1 - buf->width));
                x1 = buf->width - 1;
            }
        }

        /* copy */

        offset = y0 * width + x0;

        for (y = y0; y <= y1; y++) {
            (*env)->GetIntArrayRegion(env, rgbArray, offset, x1 - x0 + 1, dst);
            offset += width;
            dst += buf->width;
        }

        /* check for errors */
        if ((*env)->ExceptionOccurred(env)) {
            BD_DEBUG(DBG_BDJ | DBG_CRIT, "Array access error at %ld (+%ld)\n", (long)offset, (long)(x1 - x0 + 1));
            (*env)->ExceptionDescribe(env);
            (*env)->ExceptionClear(env);
        }

        if (buf->unlock) {
            buf->unlock(buf);
        }

        bdpriv_bdj_osd_cb(bd, buf->buf[BD_OVERLAY_IG], (int)width, (int)height,
                      x0, y0, x1, y1);

    } else {

        /* return java array */

        jint *image = (jint *)(*env)->GetPrimitiveArrayCritical(env, rgbArray, NULL);
        if (image) {
            bdpriv_bdj_osd_cb(bd, (const unsigned *)image, (int)width, (int)height,
                          x0, y0, x1, y1);
            (*env)->ReleasePrimitiveArrayCritical(env, rgbArray, image, JNI_ABORT);
        } else {
            BD_DEBUG(DBG_BDJ | DBG_CRIT, "GetPrimitiveArrayCritical() failed\n");
        }
    }
}

JNIEXPORT void JNICALL Java_org_videolan_Libbluray_updateGraphicN(JNIEnv * env,
        jclass cls, jlong np, jint width, jint height, jintArray rgbArray,
        jint x0, jint y0, jint x1, jint y1) {

    BLURAY* bd = (BLURAY*)(intptr_t)np;

    BD_DEBUG(DBG_JNI, "updateGraphicN(%ld,%ld-%ld,%ld)\n", (long)x0, (long)y0, (long)x1, (long)y1);

    /* app callback not initialized ? */
    if (!bd) {
        return;
    }

    /* nothing to draw ? */
    if (rgbArray && (x1 < x0 || y1 < y0 || (x1 | y1) < 0)) {
        return;
    }

    BD_ARGB_BUFFER *buf = bdpriv_lock_osd_buffer(bd);

    _updateGraphic(env, bd, width, height, rgbArray, x0, y0, x1, y1, buf);

    bdpriv_unlock_osd_buffer(bd);
}

#define CC (char*)(uintptr_t)  /* cast a literal from (const char*) */
#define VC (void*)(uintptr_t)  /* cast function pointer to void* */

BD_PRIVATE CPP_EXTERN const JNINativeMethod
Java_org_videolan_Libbluray_methods[] =
{ /* AUTOMATICALLY GENERATED */
    {
        CC("getAacsDataN"),
        CC("(JI)[B"),
        VC(Java_org_videolan_Libbluray_getAacsDataN),
    },
    {
        CC("getUOMaskN"),
        CC("(J)J"),
        VC(Java_org_videolan_Libbluray_getUOMaskN),
    },
    {
        CC("setUOMaskN"),
        CC("(JZZ)V"),
        VC(Java_org_videolan_Libbluray_setUOMaskN),
    },
    {
        CC("setKeyInterestN"),
        CC("(JI)V"),
        VC(Java_org_videolan_Libbluray_setKeyInterestN),
    },
    {
        CC("getTitleInfosN"),
        CC("(J)[Lorg/videolan/TitleInfo;"),
        VC(Java_org_videolan_Libbluray_getTitleInfosN),
    },
    {
        CC("getPlaylistInfoN"),
        CC("(JI)Lorg/videolan/PlaylistInfo;"),
        VC(Java_org_videolan_Libbluray_getPlaylistInfoN),
    },
    {
        CC("seekN"),
        CC("(JIIJ)J"),
        VC(Java_org_videolan_Libbluray_seekN),
    },
    {
        CC("selectPlaylistN"),
        CC("(JIIIJ)I"),
        VC(Java_org_videolan_Libbluray_selectPlaylistN),
    },
    {
        CC("selectTitleN"),
        CC("(JI)I"),
        VC(Java_org_videolan_Libbluray_selectTitleN),
    },
    {
        CC("setVirtualPackageN"),
        CC("(JLjava/lang/String;Z)I"),
        VC(Java_org_videolan_Libbluray_setVirtualPackageN),
    },
    {
        CC("selectAngleN"),
        CC("(JI)I"),
        VC(Java_org_videolan_Libbluray_selectAngleN),
    },
    {
        CC("soundEffectN"),
        CC("(JI)I"),
        VC(Java_org_videolan_Libbluray_soundEffectN),
    },
    {
        CC("tellTimeN"),
        CC("(J)J"),
        VC(Java_org_videolan_Libbluray_tellTimeN),
    },
    {
        CC("selectRateN"),
        CC("(JFI)I"),
        VC(Java_org_videolan_Libbluray_selectRateN),
    },
    {
        CC("writeRegN"),
        CC("(JIIII)I"),
        VC(Java_org_videolan_Libbluray_writeRegN),
    },
    {
        CC("readRegN"),
        CC("(JII)I"),
        VC(Java_org_videolan_Libbluray_readRegN),
    },
    {
        CC("cacheBdRomFileN"),
        CC("(JLjava/lang/String;Ljava/lang/String;)I"),
        VC(Java_org_videolan_Libbluray_cacheBdRomFileN),
    },
    {
        CC("listBdFilesN"),
        CC("(JLjava/lang/String;Z)[Ljava/lang/String;"),
        VC(Java_org_videolan_Libbluray_listBdFilesN),
    },
    {
        CC("getBdjoN"),
        CC("(JLjava/lang/String;)Lorg/videolan/bdjo/Bdjo;"),
        VC(Java_org_videolan_Libbluray_getBdjoN),
    },
    {
        CC("updateGraphicN"),
        CC("(JII[IIIII)V"),
        VC(Java_org_videolan_Libbluray_updateGraphicN),
    },
};

BD_PRIVATE CPP_EXTERN const int
Java_org_videolan_Libbluray_methods_count =
    sizeof(Java_org_videolan_Libbluray_methods)/sizeof(Java_org_videolan_Libbluray_methods[0]);

