/*
 *  Copyright (C) 2011-2012 Dmitry Moskalchuk <dm@crystax.net>
 *  Copyright (C) 2013 Team Kodi
 *  This file is part of Kodi - https://kodi.tv
 *
 *  SPDX-License-Identifier: BSD-2-Clause-Views WITH GPL-2.0-or-later
 *  See LICENSES/README.md for more information.
 */

#pragma once
#define HAVE_INTTYPES_H 1

#include <jni.h>
#include <string>
#include "jutils.hpp"
namespace jni
{

namespace details
{

template <typename T, typename U>
struct jcast_helper;

template <>
struct jcast_helper<std::string, jstring>
{
    static std::string cast(jstring const &v);
};

template <>
struct jcast_helper<std::vector<std::string>, jobjectArray>
{
    static std::vector<std::string> cast(jobjectArray const &v);
};

template <typename T>
struct jcast_helper<std::vector<T>, jobjectArray>
{
    static std::vector<T> cast(jobjectArray const &v);
};

template <>
struct jcast_helper<std::vector<char>, jbyteArray>
{
    static std::vector<char> cast(jbyteArray const &v)
    {
        JNIEnv *env = xbmc_jnienv();
        jsize size = 0;
        std::vector<char> vec;
        if(v)
        {
          size = env->GetArrayLength(v);

          vec.reserve(size);

          jbyte *elements = env->GetByteArrayElements(v, NULL);
          for (int i = 0; i < size; i++)
          {
            vec.emplace_back(static_cast<char>(elements[i]));
          }
          env->ReleaseByteArrayElements(v, elements, JNI_ABORT);
        }
        return vec;
    }
};

template<>
struct jcast_helper<std::vector<uint8_t>, jbyteArray>
{
    static std::vector<uint8_t> cast(jbyteArray const& v)
    {
        JNIEnv* env = xbmc_jnienv();
        jsize size = 0;
        std::vector<uint8_t> vec;
        if (v)
        {
          size = env->GetArrayLength(v);

          vec.reserve(size);

          jbyte* elements = env->GetByteArrayElements(v, NULL);
          for (int i = 0; i < size; i++)
          {
            vec.emplace_back(static_cast<uint8_t>(elements[i]));
          }
          env->ReleaseByteArrayElements(v, elements, JNI_ABORT);
        }
        return vec;
    }
};

template <>
struct jcast_helper<std::vector<int>, jintArray>
{
    static std::vector<int> cast(jintArray const &v)
    {
        JNIEnv *env = xbmc_jnienv();
        jsize size = 0;
        if(v)
            size = env->GetArrayLength(v);

        std::vector<int> vec;
        vec.reserve(size);

        int *elements = env->GetIntArrayElements(v, NULL);
        for (int i = 0; i < size; i++)
        {
            vec.emplace_back(elements[i]);
        }
        env->ReleaseIntArrayElements(v, elements, JNI_ABORT);
        return vec;
    }
};

template <>
struct jcast_helper<std::vector<int16_t>, jshortArray>
{
    static std::vector<int16_t> cast(jshortArray const &v)
    {
        JNIEnv *env = xbmc_jnienv();
        jsize size = 0;
        if(v)
            size = env->GetArrayLength(v);

        std::vector<int16_t> vec;
        vec.reserve(size);

        jshort *elements = env->GetShortArrayElements(v, NULL);
        for (int i = 0; i < size; i++)
        {
            vec.emplace_back(elements[i]);
        }
        env->ReleaseShortArrayElements(v, elements, JNI_ABORT);
        return vec;
    }
};

template <>
struct jcast_helper<std::vector<int64_t>, jlongArray>
{
    static std::vector<int64_t> cast(jlongArray const &v)
    {
        JNIEnv *env = xbmc_jnienv();
        jsize size = 0;
        if(v)
            size = env->GetArrayLength(v);

        std::vector<int64_t> vec;
        vec.reserve(size);

        int64_t *elements = env->GetLongArrayElements(v, NULL);
        for (int i = 0; i < size; i++)
        {
            vec.emplace_back(elements[i]);
        }
        env->ReleaseLongArrayElements(v, elements, JNI_ABORT);
        return vec;
    }
};

template <>
struct jcast_helper<std::vector<float>, jfloatArray>
{
    static std::vector<float> cast(jfloatArray const &v)
    {
        JNIEnv *env = xbmc_jnienv();
        jsize size = 0;
        if(v)
            size = env->GetArrayLength(v);

        std::vector<float> vec;
        vec.reserve(size);

        float *elements = env->GetFloatArrayElements(v, NULL);
        for (int i = 0; i < size; i++)
        {
            vec.emplace_back(elements[i]);
        }
        env->ReleaseFloatArrayElements(v, elements, JNI_ABORT);
        return vec;
    }
};

template <>
struct jcast_helper<jhstring, std::string>
{
    static jhstring cast(const std::string &v);
};

template<>
struct jcast_helper<jhbyteArray, std::vector<uint8_t>>
{
    static jhbyteArray cast(const std::vector<uint8_t>& v);
};

template <>
struct jcast_helper<jhbyteArray, std::vector<char> >
{
    static jhbyteArray cast(const std::vector<char> &v);
};

template <>
struct jcast_helper<jhshortArray, std::vector<int16_t> >
{
    static jhshortArray cast(const std::vector<int16_t> &v);
};

template <>
struct jcast_helper<jhfloatArray, std::vector<float> >
{
    static jhfloatArray cast(const std::vector<float> &v);
};

template <>
struct jcast_helper<jhobjectArray, std::vector<std::string> >
{
    static jhobjectArray cast(const std::vector<std::string> &v);
};

template <>
struct jcast_helper<std::string, jhstring>
{
    static std::string cast(jhstring const &v)
    {
        return jcast_helper<std::string, jstring>::cast(v.get());
    }
};

template <>
struct jcast_helper<std::vector<std::string>, jhobjectArray>
{
    static std::vector<std::string> cast(jhobjectArray const &v)
    {
        return jcast_helper<std::vector<std::string>, jobjectArray>::cast(v.get());
    }
};

template <typename T>
struct jcast_helper<std::vector<T>, jhobjectArray>
{
    static std::vector<T> cast(jhobjectArray const &v)
    {
        return jcast_helper<std::vector<T>, jobjectArray>::cast(v.get());
    }
};

template <>
struct jcast_helper<std::vector<char>, jhbyteArray>
{
    static std::vector<char> cast(jhbyteArray const &v)
    {
        return jcast_helper<std::vector<char>, jbyteArray>::cast(v.get());
    }
};

template<>
struct jcast_helper<std::vector<uint8_t>, jhbyteArray>
{
    static std::vector<uint8_t> cast(jhbyteArray const& v)
    {
        return jcast_helper<std::vector<uint8_t>, jbyteArray>::cast(v.get());
    }
};

template <>
struct jcast_helper<std::vector<int>, jhintArray>
{
    static std::vector<int> cast(jhintArray const &v)
    {
        return jcast_helper<std::vector<int>, jintArray>::cast(v.get());
    }
};

template <>
struct jcast_helper<std::vector<int16_t>, jhshortArray>
{
    static std::vector<int16_t> cast(jhshortArray const &v)
    {
        return jcast_helper<std::vector<int16_t>, jshortArray>::cast(v.get());
    }
};

template <>
struct jcast_helper<std::vector<int64_t>, jhlongArray>
{
    static std::vector<int64_t> cast(jhlongArray const &v)
    {
        return jcast_helper<std::vector<int64_t>, jlongArray>::cast(v.get());
    }
};

template <>
struct jcast_helper<std::vector<float>, jhfloatArray>
{
    static std::vector<float> cast(jhfloatArray const &v)
    {
        return jcast_helper<std::vector<float>, jfloatArray>::cast(v.get());
    }
};

template <typename T>
std::vector<T> jcast_helper<std::vector<T>, jobjectArray>::cast(jobjectArray const &v)
{
  JNIEnv *env = xbmc_jnienv();
  jsize size = 0;
  if(v)
    size = env->GetArrayLength(v);

  std::vector<T> vec;
  vec.reserve(size);

  for (int i = 0; i < size; i++)
  {
    T element((jhobject)env->GetObjectArrayElement(v, i));
    vec.emplace_back(element);
  }
  return vec;
}


} // namespace details

template <typename T, typename U>
T jcast(U const &v)
{
    return details::jcast_helper<T, U>::cast(v);
}

inline
jhclass find_class(JNIEnv *env, const char *clsname)
{
  return jhclass((jclass)env->FindClass(clsname));
}

inline
jhclass find_class(const char *clsname)
{
    return find_class(xbmc_jnienv(), clsname);
}

inline
jhclass get_class(JNIEnv *env, jhobject const &obj)
{
    return jhclass(env->GetObjectClass(obj.get()));
}

inline
jhclass get_class(jhobject const &obj)
{
    return get_class(xbmc_jnienv(), obj);
}

inline
jmethodID get_method_id(JNIEnv *env, jhclass const &cls, const char *name, const char *signature)
{
    return env->GetMethodID(cls.get(), name, signature);
}

inline
jmethodID get_method_id(jhclass const &cls, const char *name, const char *signature)
{
    return get_method_id(xbmc_jnienv(), cls, name, signature);
}

inline
jmethodID get_method_id(JNIEnv *env, jhobject const &obj, const char *name, const char *signature)
{
    return get_method_id(env, get_class(env, obj), name, signature);
}

template <typename T>
jmethodID get_method_id(jholder<T> const &obj, const char *name, const char *signature)
{
    return get_method_id(xbmc_jnienv(), obj, name, signature);
}

inline
jmethodID get_static_method_id(JNIEnv *env, jhclass const &cls, const char *name, const char *signature)
{
    return env->GetStaticMethodID(cls.get(), name, signature);
}

inline
jmethodID get_static_method_id(jhclass const &cls, const char *name, const char *signature)
{
    return get_static_method_id(xbmc_jnienv(), cls, name, signature);
}

template <typename T>
jmethodID get_static_method_id(JNIEnv *env, jholder<T> const &obj, const char *name, const char *signature)
{
    return get_static_method_id(env, get_class(env, obj), name, signature);
}

template <typename T>
jmethodID get_static_method_id(jholder<T> const &obj, const char *name, const char *signature)
{
    return get_static_method_id(xbmc_jnienv(), obj, name, signature);
}

inline
jfieldID get_field_id(JNIEnv *env, jhclass const &cls, const char *name, const char *signature)
{
    return env->GetFieldID(cls.get(), name, signature);
}

inline
jfieldID get_field_id(jhclass const &cls, const char *name, const char *signature)
{
    return get_field_id(xbmc_jnienv(), cls, name, signature);
}

template <typename T>
jfieldID get_field_id(JNIEnv *env, jholder<T> const &obj, const char *name, const char *signature)
{
    return get_field_id(env, get_class(env, obj), name, signature);
}

template <typename T>
jfieldID get_field_id(jholder<T> const &obj, const char *name, const char *signature)
{
    return get_field_id(xbmc_jnienv(), obj, name, signature);
}

inline
jfieldID get_static_field_id(JNIEnv *env, jhclass const &cls, const char *name, const char *signature)
{
    return env->GetStaticFieldID(cls.get(), name, signature);
}

inline
jfieldID get_static_field_id(jhclass const &cls, const char *name, const char *signature)
{
    return get_static_field_id(xbmc_jnienv(), cls, name, signature);
}

template <typename T>
jfieldID get_static_field_id(JNIEnv *env, jholder<T> const &obj, const char *name, const char *signature)
{
    return get_static_field_id(env, get_class(env, obj), name, signature);
}

template <typename T>
jfieldID get_static_field_id(jholder<T> const &obj, const char *name, const char *signature)
{
    return get_static_field_id(xbmc_jnienv(), obj, name, signature);
}

namespace details
{

void call_void_method(JNIEnv *env, jclass cls, jmethodID mid, ...);
void call_void_method(JNIEnv *env, jobject obj, jmethodID mid, ...);
jhobject new_object(JNIEnv *env, jclass cls, jmethodID mid, ...);
#define CRYSTAX_PP_STEP(type) \
    type get_ ## type ## _field(JNIEnv *env, jobject obj, jfieldID fid); \
    type get_ ## type ## _field(JNIEnv *env, jclass cls, jfieldID fid); \
    type get_static_ ## type ## _field(JNIEnv *env, jobject obj, jfieldID fid); \
    type get_static_ ## type ## _field(JNIEnv *env, jclass cls, jfieldID fid); \
    void set_ ## type ## _field(JNIEnv *env, jobject obj, jfieldID fid, type const &arg); \
    void set_ ## type ## _field(JNIEnv *env, jclass cls, jfieldID fid, type const &arg); \
    type call_ ## type ## _method(JNIEnv *env, jclass cls, jmethodID mid, ...); \
    type call_ ## type ## _method(JNIEnv *env, jobject obj, jmethodID mid, ...);
#include "jni.inc"
#undef CRYSTAX_PP_STEP

#define CRYSTAX_PP_STEP(type) inline type raw_arg(type arg) {return arg;}
CRYSTAX_PP_STEP(jboolean)
CRYSTAX_PP_STEP(jbyte)
CRYSTAX_PP_STEP(jchar)
CRYSTAX_PP_STEP(jshort)
CRYSTAX_PP_STEP(jint)
CRYSTAX_PP_STEP(jlong)
CRYSTAX_PP_STEP(jfloat)
CRYSTAX_PP_STEP(jdouble)
CRYSTAX_PP_STEP(jobject)
CRYSTAX_PP_STEP(jclass)
CRYSTAX_PP_STEP(jstring)
CRYSTAX_PP_STEP(jthrowable)
CRYSTAX_PP_STEP(jarray)
CRYSTAX_PP_STEP(jbooleanArray)
CRYSTAX_PP_STEP(jbyteArray)
CRYSTAX_PP_STEP(jshortArray)
CRYSTAX_PP_STEP(jintArray)
CRYSTAX_PP_STEP(jlongArray)
CRYSTAX_PP_STEP(jfloatArray)
CRYSTAX_PP_STEP(jdoubleArray)
CRYSTAX_PP_STEP(jobjectArray)
#undef CRYSTAX_PP_STEP

template <typename T>
T raw_arg(jholder<T> const &arg)
{
    return arg.get();
}

template <typename Ret>
struct jni_helper;

template <>
struct jni_helper<void>
{
    template <typename T, typename... Args>
    static void call_method(JNIEnv *env, T const &obj, jmethodID mid, Args&&... args)
    {
        details::call_void_method(env, raw_arg(obj), mid, raw_arg(args)...);
    }
};

struct jni_stripper
{
    template <typename... Args>
    static jhobject new_object(JNIEnv *env, const jhclass &cls, jmethodID mid, Args&&... args)
    {
        return details::new_object(env, raw_arg(cls), mid, raw_arg(args)...);
    }
};

#define CRYSTAX_PP_STEP(type) \
template <> \
struct jni_helper<type> \
{ \
    template <typename T> \
    static type get_field(JNIEnv *env, T const &obj, jfieldID fid) \
    { \
        return details::get_ ## type ## _field(env, raw_arg(obj), fid); \
    } \
    template <typename T> \
    static type get_static_field(JNIEnv *env, T const &obj, jfieldID fid) \
    { \
        return details::get_static_ ## type ## _field(env, raw_arg(obj), fid); \
    } \
    template <typename T> \
    static void set_field(JNIEnv *env, T const &obj, jfieldID fid, type const &arg) \
    { \
        details::set_ ## type ## _field(env, raw_arg(obj), fid, arg); \
    } \
    template <typename T, typename... Args> \
    static type call_method(JNIEnv *env, T const &obj, jmethodID mid, Args&&... args) \
    { \
        return details::call_ ## type ## _method(env, raw_arg(obj), mid, raw_arg(args)...); \
    } \
};
#include "jni.inc"
#undef CRYSTAX_PP_STEP

template <typename T>
struct jni_signature
{
    static const char *signature;
};

} // namespace details

// Get field

template <typename Ret, typename T>
Ret get_field(JNIEnv *env, jholder<T> const &obj, jfieldID fid)
{
    return details::jni_helper<Ret>::get_field(env, obj, fid);
}

template <typename Ret, typename T>
Ret get_field(jholder<T> const &obj, jfieldID fid)
{
    return get_field<Ret>(xbmc_jnienv(), obj, fid);
}

template <typename Ret>
Ret get_field(JNIEnv *env, const char *clsname, jfieldID fid)
{
    return get_field<Ret>(env, find_class(env, clsname), fid);
}

template <typename Ret>
Ret get_field(const char *clsname, jfieldID fid)
{
    return get_field<Ret>(xbmc_jnienv(), clsname, fid);
}

template <typename Ret, typename T>
Ret get_field(JNIEnv *env, jholder<T> const &obj, const char *name, const char *signature)
{
    return get_field<Ret>(env, obj, get_field_id(env, obj, name, signature));
}

template <typename Ret, typename T>
Ret get_field(jholder<T> const &obj, const char *name, const char *signature)
{
    return get_field<Ret>(xbmc_jnienv(), obj, name, signature);
}
/*
template <typename Ret>
Ret get_field(JNIEnv *env, const char *clsname, const char *name, const char *signature)
{
    return get_field<Ret>(env, find_class(clsname), name, signature);
}

template <typename Ret>
Ret get_field(const char *clsname, const char *name, const char *signature)
{
    return get_field<Ret>(xbmc_jnienv(), clsname, name, signature);
}
*/
template <typename Ret, typename T>
Ret get_field(JNIEnv *env, jholder<T> const &obj, const char *name)
{
    return get_field<Ret>(env, obj, name, details::jni_signature<Ret>::signature);
}

template <typename Ret, typename T>
Ret get_field(jholder<T> const &obj, const char *name)
{
    return get_field<Ret>(xbmc_jnienv(), obj, name);
}
/*
template <typename Ret>
Ret get_field(JNIEnv *env, const char *clsname, const char *name)
{
    return get_field<Ret>(env, find_class(env, clsname), name);
}

template <typename Ret>
Ret get_field(const char *clsname, const char *name)
{
    return get_field<Ret>(xbmc_jnienv(), clsname, name);
}
*/
// Get static field

template <typename Ret>
Ret get_static_field(JNIEnv *env, jhclass const &cls, jfieldID fid)
{
    return details::jni_helper<Ret>::get_static_field(env, cls, fid);
}

template <typename Ret>
Ret get_static_field(JNIEnv *env, jhobject const &obj, const char *name, const char *signature)
{
    return details::jni_helper<Ret>::get_static_field(env, get_class(env, obj), get_static_field_id(env, obj, name, signature));
}

template <typename Ret>
Ret get_static_field(JNIEnv *env, jhclass const &cls, const char *name, const char *signature)
{
    return get_static_field<Ret>(env, cls, get_static_field_id(env, cls, name, signature));
}

template <typename Ret, typename T>
Ret get_static_field(jholder<T> const &obj, const char *name, const char *signature)
{
    return get_static_field<Ret>(xbmc_jnienv(), obj, name, signature);
}

template <typename Ret>
Ret get_static_field(JNIEnv *env, const char *clsname, const char *name, const char *signature)
{
    return get_static_field<Ret>(env, find_class(env, clsname), name, signature);
}

template <typename Ret>
Ret get_static_field(const char *clsname, const char *name, const char *signature)
{
    return get_static_field<Ret>(xbmc_jnienv(), clsname, name, signature);
}

template <typename Ret, typename T>
Ret get_static_field(JNIEnv *env, jholder<T> const &obj, const char *name)
{
    return get_static_field<Ret>(env, obj, name, details::jni_signature<Ret>::signature);
}

template <typename Ret, typename T>
Ret get_static_field(jholder<T> const &obj, const char *name)
{
    return get_static_field<Ret>(xbmc_jnienv(), obj, name);
}

template <typename Ret>
Ret get_static_field(JNIEnv *env, const char *clsname, const char *name)
{
    return get_static_field<Ret>(env, find_class(env, clsname), name);
}

template <typename Ret>
Ret get_static_field(const char *clsname, const char *name)
{
    return get_static_field<Ret>(xbmc_jnienv(), clsname, name);
}

template <typename Ret>
Ret get_static_field(jhclass const &cls, jfieldID fid)
{
    return details::jni_helper<Ret>::get_static_field(xbmc_jnienv(), cls, fid);
}


// Set field

template <typename T, typename Arg>
void set_field(JNIEnv *env, jholder<T> const &obj, jfieldID fid, Arg const &arg)
{
    details::jni_helper<Arg>::set_field(env, obj, fid, arg);
}

template <typename T, typename Arg>
void set_field(jholder<T> const &obj, jfieldID fid, Arg const &arg)
{
    set_field(xbmc_jnienv(), obj, fid, arg);
}

template <typename Arg>
void set_field(JNIEnv *env, const char *clsname, jfieldID fid, Arg const &arg)
{
    set_field(env, find_class(env, clsname), fid, arg);
}

template <typename Arg>
void set_field(const char *clsname, jfieldID fid, Arg const &arg)
{
    set_field(xbmc_jnienv(), clsname, fid, arg);
}

template <typename T, typename Arg>
void set_field(JNIEnv *env, jholder<T> const &obj, const char *name, const char *signature, Arg const &arg)
{
    set_field(env, obj, get_field_id(env, obj, name, signature), arg);
}

template <typename T, typename Arg>
void set_field(jholder<T> const &obj, const char *name, const char *signature, Arg const &arg)
{
    set_field(xbmc_jnienv(), obj, name, signature, arg);
}

template <typename Arg>
void set_field(JNIEnv *env, const char *clsname, const char *name, const char *signature, Arg const &arg)
{
    set_field(env, find_class(env, clsname), name, signature, arg);
}

template <typename Arg>
void set_field(const char *clsname, const char *name, const char *signature, Arg const &arg)
{
    set_field(xbmc_jnienv(), clsname, name, signature, arg);
}

template <typename T, typename Arg>
void set_field(JNIEnv *env, jholder<T> const &obj, const char *name, Arg const &arg)
{
    set_field(env, obj, name, details::jni_signature<Arg>::signature, arg);
}

template <typename T, typename Arg>
void set_field(jholder<T> const &obj, const char *name, Arg const &arg)
{
    set_field(xbmc_jnienv(), obj, name, arg);
}

template <typename Arg>
void set_field(JNIEnv *env, const char *clsname, const char *name, Arg const &arg)
{
    set_field(env, find_class(env, clsname), name, arg);
}

template <typename Arg>
void set_field(const char *clsname, const char *name, Arg const &arg)
{
    set_field(xbmc_jnienv(), clsname, name, arg);
}

// Set static field

template <typename T, typename Arg>
void set_static_field(JNIEnv *env, jholder<T> const &obj, const char *name, const char *signature, Arg const &arg)
{
    set_field(env, obj, get_static_field_id(env, obj, name, signature), arg);
}

template <typename T, typename Arg>
void set_static_field(jholder<T> const &obj, const char *name, const char *signature, Arg const &arg)
{
    set_static_field(xbmc_jnienv(), obj, name, signature, arg);
}

template <typename Arg>
void set_static_field(JNIEnv *env, const char *clsname, const char *name, const char *signature, Arg const &arg)
{
    set_static_field(env, find_class(env, clsname), name, signature, arg);
}

template <typename Arg>
void set_static_field(const char *clsname, const char *name, const char *signature, Arg const &arg)
{
    set_static_field(xbmc_jnienv(), clsname, name, signature, arg);
}

template <typename T, typename Arg>
void set_static_field(JNIEnv *env, jholder<T> const &obj, const char *name, Arg const &arg)
{
    set_static_field(env, obj, name, details::jni_signature<Arg>::signature, arg);
}

template <typename T, typename Arg>
void set_static_field(jholder<T> const &obj, const char *name, Arg const &arg)
{
    set_static_field(xbmc_jnienv(), obj, name, arg);
}

template <typename Arg>
void set_static_field(JNIEnv *env, const char *clsname, const char *name, Arg const &arg)
{
    set_static_field(env, find_class(env, clsname), name, arg);
}

template <typename Arg>
void set_static_field(const char *clsname, const char *name, Arg const &arg)
{
    set_static_field(xbmc_jnienv(), clsname, name, arg);
}

// Warning!!! C++11 magic below: variadic templates and perfect forwarding!
// We need to define own implementation of std::forward because we don't have
// standard C++ library when building libcrystax.

namespace details
{

#if __GNUC__ == 4 && __GNUC_MINOR__ == 4

template <typename T>
struct identity {typedef T type;};

template <typename T>
inline T && forward(typename identity<T>::type && arg)
{
    return arg;
}

#else // __GNUC__ == 4 && __GNUC_MINOR__ == 4

template <typename T>
struct remove_reference
{
    typedef T type;
};

template <typename T>
struct remove_reference<T &>
{
    typedef T type;
};

template <typename T>
struct remove_reference<T &&>
{
    typedef T type;
};

template <typename T>
struct is_lvalue_reference
{
    enum {value = false};
};

template <typename T>
struct is_lvalue_reference<T &>
{
    enum {value = true};
};

template <typename T>
inline T && forward(typename remove_reference<T>::type &arg)
{
    return static_cast<T&&>(arg);
}

template <typename T>
inline T && forward(typename remove_reference<T>::type &&arg)
{
    static_assert(!is_lvalue_reference<T>::value, "template argument is an lvalue reference type");
    return static_cast<T&&>(arg);
}
#endif // __GNUC__ == 4 && __GNUC_MINOR__ == 4

} // namespace details

template <typename Ret, typename T, typename... Args>
Ret call_method(JNIEnv *env, T const &obj, jmethodID mid, Args&&... args)
{
    return details::jni_helper<Ret>::call_method(env, obj, mid, details::forward<Args>(args)...);
}

template <typename Ret, typename... Args>
Ret call_static_method(JNIEnv *env, const jhclass &cls, const jmethodID mid, Args&&... args)
{
    return details::jni_helper<Ret>::call_static_method(env, cls, mid, details::forward<Args>(args)...);
}

template <typename... Args>
jhobject new_object(JNIEnv *env, const jhclass &cls, jmethodID mid, Args&&... args)
{
    return details::jni_stripper::new_object(env, cls, mid, details::forward<Args>(args)...);
}

template <typename Ret, typename T, typename... Args>
Ret call_method(T const &obj, jmethodID mid, Args&&... args)
{
    return call_method<Ret>(xbmc_jnienv(), obj, mid, details::forward<Args>(args)...);
}

template <typename Ret, typename... Args>
Ret call_method(JNIEnv *env, const char *clsname, jmethodID mid, Args&&... args)
{
    return call_method<Ret>(env, find_class(env, clsname), mid, details::forward<Args>(args)...);
}

template <typename Ret, typename... Args>
Ret call_method(const char *clsname, jmethodID mid, Args&&... args)
{
    return call_method<Ret>(xbmc_jnienv(), clsname, mid, details::forward<Args>(args)...);
}

template <typename Ret, typename T, typename... Args>
Ret call_method(JNIEnv *env, T const &obj, const char *name, const char *signature, Args&&... args)
{
    return call_method<Ret>(env, obj, get_method_id(env, obj, name, signature), details::forward<Args>(args)...);
}

template <typename Ret, typename T, typename... Args>
Ret call_method(T const &obj, const char *name, const char *signature, Args&&... args)
{
    return call_method<Ret>(xbmc_jnienv(), obj, name, signature, details::forward<Args>(args)...);
}

template <typename Ret, typename... Args>
Ret call_method(JNIEnv *env, const char *clsname, const char *name, const char *signature, Args&&... args)
{
    return call_method<Ret>(env, find_class(env, clsname), name, signature, details::forward<Args>(args)...);
}

template <typename Ret, typename... Args>
Ret call_method(const char *clsname, const char *name, const char *signature, Args&&... args)
{
    return call_method<Ret>(xbmc_jnienv(), clsname, name, signature, details::forward<Args>(args)...);
}

template <typename Ret, typename... Args>
Ret call_static_method(JNIEnv *env, const jhclass &clazz, const char *name, const char *signature, Args&&... args)
{
    return call_method<Ret>(env, clazz, get_static_method_id(env, clazz, name, signature), details::forward<Args>(args)...);
}

template <typename Ret, typename... Args>
Ret call_static_method(jhclass const &clazz, const char *name, const char *signature, Args&&... args)
{
    return call_static_method<Ret>(xbmc_jnienv(), clazz, name, signature, details::forward<Args>(args)...);
}

template <typename Ret, typename... Args>
Ret call_static_method(JNIEnv *env, const char *clsname, const char *name, const char *signature, Args&&... args)
{
    return call_static_method<Ret>(env, find_class(env, clsname), name, signature, details::forward<Args>(args)...);
}

template <typename Ret, typename... Args>
Ret call_static_method(const char *clsname, const char *name, const char *signature, Args&&... args)
{
    return call_static_method<Ret>(xbmc_jnienv(), clsname, name, signature, details::forward<Args>(args)...);
}

template <typename... Args>
jhobject new_object(JNIEnv *env, const jhclass &cls, const char *name, const char *signature, Args&&... args)
{
    return new_object(env, cls, get_method_id(env, cls, name, signature), details::forward<Args>(args)...);
}

template <typename... Args>
jhobject new_object(JNIEnv *env, const char *clsname, const char *name, const char *signature, Args&&... args)
{
    return new_object(env, find_class(env, clsname), name, signature, details::forward<Args>(args)...);
}

template <typename... Args>
jhobject new_object(const char *clsname, const char *name, const char *signature, Args&&... args)
{
    return new_object(xbmc_jnienv(), clsname, name, signature, details::forward<Args>(args)...);
}

template <typename... Args>
jhobject new_object(std::string clsname, const char *name, const char *signature, Args&&... args)
{
    return new_object(xbmc_jnienv(), clsname.c_str(), name, signature, details::forward<Args>(args)...);
}

template <typename... Args>
jhobject new_object(const char *clsname)
{
    return new_object(xbmc_jnienv(), clsname, "<init>", "()V");
}

template <typename... Args>
jhobject new_object(std::string clsname)
{
    return new_object(xbmc_jnienv(), clsname.c_str(), "<init>", "()V");
}

template <typename... Args>
jhobject new_object(const jhclass& cls, const char *name, const char *signature, Args&&... args)
{
    return new_object(xbmc_jnienv(), cls, name, signature, details::forward<Args>(args)...);
}

template <typename... Args>
jhobject new_object(const jhclass& cls)
{
    return new_object(xbmc_jnienv(), cls, "<init>", "()V");
}

inline void jthrow(JNIEnv *env, jhthrowable const &ex) {env->Throw(ex.get());}
inline void jthrow(jhthrowable const &ex) {jthrow(xbmc_jnienv(), ex);}

inline void jthrow(JNIEnv *env, jhclass const &cls, char const *what) {env->ThrowNew(cls.get(), what);}
inline void jthrow(jhclass const &cls, char const *what) {jthrow(xbmc_jnienv(), cls, what);}

inline void jthrow(JNIEnv *env, char const *clsname, char const *what) {jthrow(find_class(env, clsname), what);}
inline void jthrow(char const *clsname, char const *what) {jthrow(xbmc_jnienv(), clsname, what);}

bool jexcheck(JNIEnv *env);
inline bool jexcheck() {return jexcheck(xbmc_jnienv());}

} // namespace jni
