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

// Wrapper to the native phone test signal processing library, which
// exposes an interface suitable for calling via JNI.

#include <stdlib.h>
#include <jni.h>

#include "GenerateSinusoid.h"
#include "MeasureRms.h"
#include "GlitchTest.h"
#include "OverflowCheck.h"
#include "CompareSpectra.h"
#include "LinearityTest.h"

typedef short *shortPtr;

extern "C" {
    JNIEXPORT jshortArray JNICALL
        Java_com_android_cts_verifier_audioquality_Native_generateSinusoid(
            JNIEnv *env, jobject obj,
            jfloat freq, jfloat duration,
            jfloat sampleRate, jfloat amplitude, jfloat ramp);
    JNIEXPORT jfloatArray JNICALL
        Java_com_android_cts_verifier_audioquality_Native_measureRms(
            JNIEnv *env, jobject obj,
            jshortArray jpcm, jfloat sampleRate, jfloat onsetThresh);
    JNIEXPORT jfloatArray JNICALL
        Java_com_android_cts_verifier_audioquality_Native_glitchTest(
            JNIEnv *env, jobject obj,
            jfloat sampleRate, jfloat stimFreq, jfloat onsetThresh,
            jfloat dbSnrThresh, jshortArray jpcm);
    JNIEXPORT jfloatArray JNICALL
        Java_com_android_cts_verifier_audioquality_Native_overflowCheck(
            JNIEnv *env, jobject obj,
            jshortArray jpcm, jfloat sampleRate);
    JNIEXPORT jfloatArray JNICALL
        Java_com_android_cts_verifier_audioquality_Native_compareSpectra(
            JNIEnv *env, jobject obj,
            jshortArray jpcm, jshortArray jrefPcm, jfloat sampleRate);
    JNIEXPORT jfloat JNICALL
        Java_com_android_cts_verifier_audioquality_Native_linearityTest(
            JNIEnv *env, jobject obj,
            jobjectArray jpcms,
            jfloat sampleRate, jfloat dbStepSize, jint referenceStim);
};

/* Returns an array of sinusoidal samples.
   If the arguments are invalid, returns an empty array. */
JNIEXPORT jshortArray JNICALL
    Java_com_android_cts_verifier_audioquality_Native_generateSinusoid(
        JNIEnv *env, jobject obj,
        jfloat freq, jfloat duration,
        jfloat sampleRate, jfloat amplitude, jfloat ramp) {
    short *wave = NULL;
    int numSamples = 0;

    generateSinusoid(freq, duration, sampleRate, amplitude, ramp,
            &numSamples, &wave);

    jshortArray ja;
    if (!numSamples) {
        ja = env->NewShortArray(0);
    } else {
        ja = env->NewShortArray(numSamples);
        env->SetShortArrayRegion(ja, 0, numSamples, wave);
        delete[] wave;
    }
    return ja;
}

/* Returns an array of four floats.
   ret[0] = RMS
   ret[1] = standard deviation of the RMS
   ret[2] = non-silent region duration
   ret[3] = mean value
*/
JNIEXPORT jfloatArray JNICALL
    Java_com_android_cts_verifier_audioquality_Native_measureRms(
        JNIEnv *env, jobject obj,
        jshortArray jpcm, jfloat sampleRate, jfloat onsetThresh) {
    float ret[4];
    ret[0] = ret[1] = ret[2] = ret[3] = -1.0;
    int numSamples = env->GetArrayLength(jpcm);
    short *pcm = new short[numSamples];
    env->GetShortArrayRegion(jpcm, 0, numSamples, pcm);

    measureRms(pcm, numSamples, sampleRate, onsetThresh, ret, ret + 1,
            ret + 3, ret + 2);

    jfloatArray ja = env->NewFloatArray(4);
    env->SetFloatArrayRegion(ja, 0, 4, ret);
    return ja;
}

/* Returns an array of three floats.
   ret[0] = #bad frames
   ret[1] = error code
   ret[2] = duration
   Error code = 1 for success,
               -1 if initialization failed,
               -2 if insufficient samples
               -3 if tone signal onset not found
               -4 if tone signal end not found
*/
JNIEXPORT jfloatArray JNICALL
    Java_com_android_cts_verifier_audioquality_Native_glitchTest(
        JNIEnv *env, jobject obj,
        jfloat sampleRate, jfloat stimFreq, jfloat onsetThresh,
        jfloat dbSnrThresh, jshortArray jpcm) {
    float ret[3];
    int numSamples = env->GetArrayLength(jpcm);
    short *pcm = new short[numSamples];
    env->GetShortArrayRegion(jpcm, 0, numSamples, pcm);

    GlitchTest gt;
    gt.init(sampleRate, stimFreq, onsetThresh, dbSnrThresh);
    float duration = -1.0;
    int badFrames = -1;
    int success = gt.checkToneSnr(pcm, numSamples, &duration, &badFrames);
    ret[0] = badFrames;
    ret[1] = success;
    ret[2] = duration;
    jfloatArray ja = env->NewFloatArray(3);
    env->SetFloatArrayRegion(ja, 0, 3, ret);
    return ja;
}

/* Returns an array of seven floats.
   ret[0] = num deltas
   ret[1] = error code
   ret[2] = duration
   ret[3] = onset
   ret[4] = offset
   ret[5] = max peak
   ret[6] = min peak
   Error code = 1 for success, -1 for failure. */
JNIEXPORT jfloatArray JNICALL
    Java_com_android_cts_verifier_audioquality_Native_overflowCheck(
        JNIEnv *env, jobject obj,
        jshortArray jpcm, jfloat sampleRate) {
    float ret[7];
    int numSamples = env->GetArrayLength(jpcm);
    short *pcm = new short[numSamples];
    env->GetShortArrayRegion(jpcm, 0, numSamples, pcm);

    float duration = -1.0;
    int numDeltas = -1, onset = -1, offset = -1;
    int maxPeak = 0, minPeak = 0;
    int success = overflowCheck(pcm, numSamples, sampleRate,
            &duration, &numDeltas, &onset, &offset, &maxPeak, &minPeak);
    ret[0] = numDeltas;
    ret[1] = success ? 1 : -1;
    ret[2] = duration;
    ret[3] = onset;
    ret[4] = offset;
    ret[5] = maxPeak;
    ret[6] = minPeak;
    jfloatArray ja = env->NewFloatArray(7);
    env->SetFloatArrayRegion(ja, 0, 7, ret);
    return ja;
}

/* Returns an array of three floats.
   ret[0] = max deviation,
   ret[1] = error code,
   ret[2] = rms deviation.
   Error code = 1 for success, -1 for failure. */
JNIEXPORT jfloatArray JNICALL
    Java_com_android_cts_verifier_audioquality_Native_compareSpectra(
        JNIEnv *env, jobject obj,
        jshortArray jpcm, jshortArray jrefPcm, jfloat sampleRate) {
    float ret[3];
    int numSamples = env->GetArrayLength(jpcm);
    short *pcm = new short[numSamples];
    env->GetShortArrayRegion(jpcm, 0, numSamples, pcm);
    int nRefSamples = env->GetArrayLength(jrefPcm);
    short *refPcm = new short[nRefSamples];
    env->GetShortArrayRegion(jrefPcm, 0, nRefSamples, refPcm);

    float maxDeviation = -1.0, rmsDeviation = -1.0;
    int success = compareSpectra(pcm, numSamples, refPcm, nRefSamples,
            sampleRate, &maxDeviation, &rmsDeviation);
    ret[1] = success ? 1 : -1;

    ret[0] = maxDeviation;
    ret[2] = rmsDeviation;
    jfloatArray ja = env->NewFloatArray(3);
    env->SetFloatArrayRegion(ja, 0, 3, ret);
    return ja;
}

/* Return maximum deviation from linearity in dB.
   On failure returns:
      -1.0 The input signals or sample counts are missing.
      -2.0 The number of input signals is < 2.
      -3.0 The specified sample rate is <= 4000.0
      -4.0 The dB step size for the increase in stimulus level is <= 0.0
      -5.0 The specified reverence stimulus number is out of range.
      -6.0 One or more of the stimuli is too short in duration.
*/
JNIEXPORT jfloat JNICALL
    Java_com_android_cts_verifier_audioquality_Native_linearityTest(
        JNIEnv *env, jobject obj,
        jobjectArray jpcms,
        jfloat sampleRate, jfloat dbStepSize, jint referenceStim) {
    int numSignals = env->GetArrayLength(jpcms);
    int *sampleCounts = new int[numSignals];
    short **pcms = new shortPtr[numSignals];
    jshortArray ja;
    for (int i = 0; i < numSignals; i++) {
        ja = (jshortArray) env->GetObjectArrayElement(jpcms, i);
        sampleCounts[i] = env->GetArrayLength(ja);
        pcms[i] = new short[sampleCounts[i]];
        env->GetShortArrayRegion(ja, 0, sampleCounts[i], pcms[i]);
    }

    float maxDeviation = -1.0;
    int ret = linearityTest(pcms, sampleCounts, numSignals,
            sampleRate, dbStepSize, referenceStim, &maxDeviation);
    delete[] sampleCounts;
    for (int i = 0; i < numSignals; i++) {
        delete[] pcms[i];
    }
    delete[] pcms;
    if (ret < 1) return ret;

    return maxDeviation;
}