/*
 * Copyright (C) 2018 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.
 */

//#define LOG_NDEBUG 0

#define LOG_TAG "AudioAttributes-JNI"

#include <inttypes.h>
#include <jni.h>
#include <nativehelper/JNIHelp.h>
#include "core_jni_helpers.h"

#include <utils/Log.h>
#include <vector>

#include <nativehelper/ScopedLocalRef.h>
#include <nativehelper/ScopedUtfChars.h>

#include "android_media_AudioAttributes.h"
#include "android_media_AudioErrors.h"

// ----------------------------------------------------------------------------

using namespace android;

// ----------------------------------------------------------------------------
static const char* const kClassPathName = "android/media/AudioAttributes";

static jclass gAudioAttributesClass;
static struct {
    jfieldID    mUsage;         // AudioAttributes.mUsage
    jfieldID    mSource;        // AudioAttributes.mSource
    jfieldID    mContentType;   // AudioAttributes.mContentType
    jfieldID    mFlags;         // AudioAttributes.mFlags
    jfieldID    mFormattedTags; // AudioAttributes.mFormattedTags
} gAudioAttributesFields;

static struct { jmethodID isSystemUsage; } gAudioAttributesClassMethods;

static jclass gAudioAttributesBuilderClass;
static jmethodID gAudioAttributesBuilderCstor;
static struct {
    jmethodID build;
    jmethodID setUsage;
    jmethodID setSystemUsage;
    jmethodID setInternalCapturePreset;
    jmethodID setInternalContentType;
    jmethodID replaceFlags;
    jmethodID addTag;
} gAudioAttributesBuilderMethods;


static jint nativeAudioAttributesFromJavaAudioAttributes(
        JNIEnv* env, jobject jAudioAttributes, audio_attributes_t *aa)
{
    if (env == nullptr) {
        return AUDIO_JAVA_DEAD_OBJECT;
    }
    if (jAudioAttributes == nullptr) {
        ALOGE("Invalid AudioAttributes java object");
        return (jint)AUDIO_JAVA_BAD_VALUE;
    }
    if (!env->IsInstanceOf(jAudioAttributes, gAudioAttributesClass)) {
        return (jint)AUDIO_JAVA_BAD_VALUE;
    }
    const jstring jtags =
            (jstring) env->GetObjectField(jAudioAttributes, gAudioAttributesFields.mFormattedTags);
    if (jtags == nullptr) {
        return AUDIO_JAVA_NO_INIT;
    }
    const char* tags = env->GetStringUTFChars(jtags, NULL);
    // copying array size -1, char array for tags was calloc'd, no need to NULL-terminate it
    strncpy(aa->tags, tags, AUDIO_ATTRIBUTES_TAGS_MAX_SIZE - 1);
    env->ReleaseStringUTFChars(jtags, tags);

    // Record ?
    aa->source = (audio_source_t) env->GetIntField(jAudioAttributes,
                                                    gAudioAttributesFields.mSource);
    // Track ?
    aa->usage = (audio_usage_t) env->GetIntField(jAudioAttributes, gAudioAttributesFields.mUsage);

    aa->content_type =
            (audio_content_type_t) env->GetIntField(jAudioAttributes,
                                                    gAudioAttributesFields.mContentType);

    aa->flags = (audio_flags_mask_t)env->GetIntField(jAudioAttributes,
                                                      gAudioAttributesFields.mFlags);

    ALOGV("AudioAttributes for usage=%d content=%d source=%d tags=%s flags=%08x tags=%s",
          aa->usage, aa->content_type, aa->source, aa->tags, aa->flags, aa->tags);
    return (jint)AUDIO_JAVA_SUCCESS;
}

static jint nativeAudioAttributesToJavaAudioAttributes(
        JNIEnv* env, jobject *jAudioAttributes, const audio_attributes_t &attributes)
{
    ScopedLocalRef<jobject> jAttributeBuilder(env, env->NewObject(gAudioAttributesBuilderClass,
                                                                  gAudioAttributesBuilderCstor));
    if (jAttributeBuilder.get() == nullptr) {
        return (jint)AUDIO_JAVA_ERROR;
    }

    const bool isSystemUsage = env->CallStaticBooleanMethod(gAudioAttributesClass,
                                                      gAudioAttributesClassMethods.isSystemUsage,
                                                      attributes.usage);
    if (isSystemUsage) {
        env->CallObjectMethod(jAttributeBuilder.get(),
                              gAudioAttributesBuilderMethods.setSystemUsage, attributes.usage);
    } else {
        env->CallObjectMethod(jAttributeBuilder.get(), gAudioAttributesBuilderMethods.setUsage,
                              attributes.usage);
    }
    env->CallObjectMethod(jAttributeBuilder.get(),
                          gAudioAttributesBuilderMethods.setInternalCapturePreset,
                          attributes.source);
    env->CallObjectMethod(jAttributeBuilder.get(),
                          gAudioAttributesBuilderMethods.setInternalContentType,
                          attributes.content_type);
    env->CallObjectMethod(jAttributeBuilder.get(),
                          gAudioAttributesBuilderMethods.replaceFlags,
                          attributes.flags);
    env->CallObjectMethod(jAttributeBuilder.get(),
                          gAudioAttributesBuilderMethods.addTag,
                          env->NewStringUTF(attributes.tags));

    *jAudioAttributes = env->CallObjectMethod(jAttributeBuilder.get(),
                                              gAudioAttributesBuilderMethods.build);
    return (jint)AUDIO_JAVA_SUCCESS;
}

// ----------------------------------------------------------------------------
JNIAudioAttributeHelper::UniqueAaPtr JNIAudioAttributeHelper::makeUnique()
{
    audio_attributes_t *aa = new (calloc(1, sizeof(audio_attributes_t)))
                audio_attributes_t{AUDIO_ATTRIBUTES_INITIALIZER};
    return UniqueAaPtr{aa};
}

jint JNIAudioAttributeHelper::nativeFromJava(JNIEnv* env, jobject jAudioAttributes,
                                             audio_attributes_t *paa)
{
    return nativeAudioAttributesFromJavaAudioAttributes(env, jAudioAttributes, paa);
}

jint JNIAudioAttributeHelper::nativeToJava(
        JNIEnv* env, jobject *jAudioAttributes, const audio_attributes_t &attributes)
{
    return nativeAudioAttributesToJavaAudioAttributes(env, jAudioAttributes, attributes);
}

jint JNIAudioAttributeHelper::getJavaArray(
        JNIEnv* env, jobjectArray *jAudioAttributeArray, jint numAudioAttributes)
{
    *jAudioAttributeArray = env->NewObjectArray(numAudioAttributes, gAudioAttributesClass, NULL);
    return *jAudioAttributeArray == NULL? (jint)AUDIO_JAVA_ERROR : (jint)AUDIO_JAVA_SUCCESS;
}

/*
 * JNI registration.
 */
int register_android_media_AudioAttributes(JNIEnv *env)
{
    jclass audioAttributesClass = FindClassOrDie(env, kClassPathName);
    gAudioAttributesClass = MakeGlobalRefOrDie(env, audioAttributesClass);
    gAudioAttributesClassMethods.isSystemUsage =
            GetStaticMethodIDOrDie(env, gAudioAttributesClass, "isSystemUsage", "(I)Z");

    gAudioAttributesFields.mUsage = GetFieldIDOrDie(env, audioAttributesClass, "mUsage", "I");
    gAudioAttributesFields.mSource = GetFieldIDOrDie(env, audioAttributesClass, "mSource", "I");
    gAudioAttributesFields.mContentType =
            GetFieldIDOrDie(env, audioAttributesClass, "mContentType", "I");
    gAudioAttributesFields.mFlags = GetFieldIDOrDie(env, audioAttributesClass, "mFlags", "I");
    gAudioAttributesFields.mFormattedTags =
            GetFieldIDOrDie(env, audioAttributesClass, "mFormattedTags", "Ljava/lang/String;");

    jclass audioAttributesBuilderClass = FindClassOrDie(
                env, "android/media/AudioAttributes$Builder");
    gAudioAttributesBuilderClass = MakeGlobalRefOrDie(env, audioAttributesBuilderClass);
    gAudioAttributesBuilderCstor = GetMethodIDOrDie(
                env, audioAttributesBuilderClass, "<init>", "()V");
    gAudioAttributesBuilderMethods.build = GetMethodIDOrDie(
                env, audioAttributesBuilderClass, "build", "()Landroid/media/AudioAttributes;");
    gAudioAttributesBuilderMethods.setUsage = GetMethodIDOrDie(
                env, audioAttributesBuilderClass, "setUsage",
                "(I)Landroid/media/AudioAttributes$Builder;");
    gAudioAttributesBuilderMethods.setSystemUsage =
            GetMethodIDOrDie(env, audioAttributesBuilderClass, "setSystemUsage",
                             "(I)Landroid/media/AudioAttributes$Builder;");
    gAudioAttributesBuilderMethods.setInternalCapturePreset = GetMethodIDOrDie(
                env, audioAttributesBuilderClass, "setInternalCapturePreset",
                "(I)Landroid/media/AudioAttributes$Builder;");
    gAudioAttributesBuilderMethods.setInternalContentType =
            GetMethodIDOrDie(env, audioAttributesBuilderClass, "setInternalContentType",
                             "(I)Landroid/media/AudioAttributes$Builder;");
    gAudioAttributesBuilderMethods.replaceFlags = GetMethodIDOrDie(
                env, audioAttributesBuilderClass, "replaceFlags",
                "(I)Landroid/media/AudioAttributes$Builder;");
    gAudioAttributesBuilderMethods.addTag = GetMethodIDOrDie(
                env, audioAttributesBuilderClass, "addTag",
                "(Ljava/lang/String;)Landroid/media/AudioAttributes$Builder;");

    env->DeleteLocalRef(audioAttributesClass);

    return 0;
}
