/*
 * BRLTTY - A background process providing access to the console screen (when in
 *          text mode) for a blind person using a refreshable braille display.
 *
 * Copyright (C) 1995-2021 by The BRLTTY Developers.
 *
 * BRLTTY comes with ABSOLUTELY NO WARRANTY.
 *
 * This is free software, placed 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. Please see the file LICENSE-LGPL for details.
 *
 * Web Page: http://brltty.app/
 *
 * This software is maintained by Dave Mielke <dave@mielke.cc>.
 */

#include "prologue.h"

#include <string.h>

#include "log.h"
#include "pcm.h"
#include "system_java.h"

struct PcmDeviceStruct {
  JNIEnv *env;
  jobject device;
};

static jclass pcmDeviceClass = NULL;

static int
findPcmDeviceClass (JNIEnv *env) {
  return findJavaClass(env, &pcmDeviceClass, JAVA_OBJ_BRLTTY("PcmDevice"));
}

PcmDevice *
openPcmDevice (int errorLevel, const char *device) {
  PcmDevice *pcm = malloc(sizeof(*pcm));

  if (pcm) {
    memset(pcm, 0, sizeof(*pcm));
    pcm->env = getJavaNativeInterface();
    pcm->device = NULL;

    if (findPcmDeviceClass(pcm->env)) {
      static jmethodID constructor = 0;

      if (findJavaConstructor(pcm->env, &constructor, pcmDeviceClass,
                              JAVA_SIG_CONSTRUCTOR())) {
        jobject localReference = (*pcm->env)->NewObject(pcm->env, pcmDeviceClass, constructor);

        if (!clearJavaException(pcm->env, 1)) {
          jobject globalReference = (*pcm->env)->NewGlobalRef(pcm->env, localReference);

          (*pcm->env)->DeleteLocalRef(pcm->env, localReference);
          localReference = NULL;

          if (globalReference) {
            pcm->device = globalReference;
            return pcm;
          } else {
            logMallocError();
            clearJavaException(pcm->env, 0);
          }
        }
      }
    }

    free(pcm);
  } else {
    logMallocError();
  }

  return NULL;
}

void
closePcmDevice (PcmDevice *pcm) {
  if (pcm) {
    if (pcm->device) {
      if (findPcmDeviceClass(pcm->env)) {
        static jmethodID method = 0;

        if (findJavaInstanceMethod(pcm->env, &method, pcmDeviceClass, "close",
                                   JAVA_SIG_METHOD(JAVA_SIG_VOID,
                                                  ))) {
          (*pcm->env)->CallVoidMethod(pcm->env, pcm->device, method);
          clearJavaException(pcm->env, 1);
        }
      }

      (*pcm->env)->DeleteGlobalRef(pcm->env, pcm->device);
    }

    free(pcm);
  }
}

int
writePcmData (PcmDevice *pcm, const unsigned char *buffer, int count) {
  if (findPcmDeviceClass(pcm->env)) {
    static jmethodID method = 0;

    if (findJavaInstanceMethod(pcm->env, &method, pcmDeviceClass, "write",
                               JAVA_SIG_METHOD(JAVA_SIG_BOOLEAN,
                                               JAVA_SIG_ARRAY(JAVA_SIG_SHORT) // samples
                                              ))) {
      jint size = count / 2;
      jshortArray jSamples = (*pcm->env)->NewShortArray(pcm->env, size);

      if (jSamples) {
        typedef union {
          const unsigned char *bytes;
          const int16_t *actual;
        } Samples;

        Samples samples = {
          .bytes = buffer
        };

        (*pcm->env)->SetShortArrayRegion(
          pcm->env, jSamples, 0, size, samples.actual
        );

        if (!clearJavaException(pcm->env, 1)) {
          jboolean result = (*pcm->env)->CallBooleanMethod(
            pcm->env, pcm->device, method, jSamples
          );

          (*pcm->env)->DeleteLocalRef(pcm->env, jSamples);
          jSamples = NULL;

          if (!clearJavaException(pcm->env, 1)) {
            if (result == JNI_TRUE) {
              return 1;
            }
          }
        }
      } else {
        logMallocError();
        clearJavaException(pcm->env, 0);
      }
    }
  }

  return 0;
}

int
getPcmBlockSize (PcmDevice *pcm) {
  if (findPcmDeviceClass(pcm->env)) {
    static jmethodID method = 0;

    if (findJavaInstanceMethod(pcm->env, &method, pcmDeviceClass, "getBufferSize",
                               JAVA_SIG_METHOD(JAVA_SIG_INT,
                                              ))) {
      jint result = (*pcm->env)->CallIntMethod(pcm->env, pcm->device, method);
      if (!clearJavaException(pcm->env, 1)) return result;
    }
  }

  return 0X100;
}

int
getPcmSampleRate (PcmDevice *pcm) {
  if (findPcmDeviceClass(pcm->env)) {
    static jmethodID method = 0;

    if (findJavaInstanceMethod(pcm->env, &method, pcmDeviceClass, "getSampleRate",
                               JAVA_SIG_METHOD(JAVA_SIG_INT,
                                              ))) {
      jint result = (*pcm->env)->CallIntMethod(pcm->env, pcm->device, method);
      if (!clearJavaException(pcm->env, 1)) return result;
    }
  }

  return 8000;
}

int
setPcmSampleRate (PcmDevice *pcm, int rate) {
  if (findPcmDeviceClass(pcm->env)) {
    static jmethodID method = 0;

    if (findJavaInstanceMethod(pcm->env, &method, pcmDeviceClass, "setSampleRate",
                               JAVA_SIG_METHOD(JAVA_SIG_VOID,
                                               JAVA_SIG_INT // rate
                                              ))) {
      (*pcm->env)->CallVoidMethod(pcm->env, pcm->device, method, rate);
      clearJavaException(pcm->env, 1);
    }
  }

  return getPcmSampleRate(pcm);
}

int
getPcmChannelCount (PcmDevice *pcm) {
  if (findPcmDeviceClass(pcm->env)) {
    static jmethodID method = 0;

    if (findJavaInstanceMethod(pcm->env, &method, pcmDeviceClass, "getChannelCount",
                               JAVA_SIG_METHOD(JAVA_SIG_INT,
                                              ))) {
      jint result = (*pcm->env)->CallIntMethod(pcm->env, pcm->device, method);
      if (!clearJavaException(pcm->env, 1)) return result;
    }
  }

  return 1;
}

int
setPcmChannelCount (PcmDevice *pcm, int channels) {
  if (findPcmDeviceClass(pcm->env)) {
    static jmethodID method = 0;

    if (findJavaInstanceMethod(pcm->env, &method, pcmDeviceClass, "setChannelCount",
                               JAVA_SIG_METHOD(JAVA_SIG_VOID,
                                               JAVA_SIG_INT // count
                                              ))) {
      (*pcm->env)->CallVoidMethod(pcm->env, pcm->device, method, channels);
      clearJavaException(pcm->env, 1);
    }
  }

  return getPcmChannelCount(pcm);
}

PcmAmplitudeFormat
getPcmAmplitudeFormat (PcmDevice *pcm) {
  return PCM_FMT_S16N;
}

PcmAmplitudeFormat
setPcmAmplitudeFormat (PcmDevice *pcm, PcmAmplitudeFormat format) {
  return getPcmAmplitudeFormat(pcm);
}

void
pushPcmOutput (PcmDevice *pcm) {
  if (findPcmDeviceClass(pcm->env)) {
    static jmethodID method = 0;

    if (findJavaInstanceMethod(pcm->env, &method, pcmDeviceClass, "push",
                               JAVA_SIG_METHOD(JAVA_SIG_VOID,
                                              ))) {
      (*pcm->env)->CallVoidMethod(pcm->env, pcm->device, method);
      clearJavaException(pcm->env, 1);
    }
  }
}

void
awaitPcmOutput (PcmDevice *pcm) {
  if (findPcmDeviceClass(pcm->env)) {
    static jmethodID method = 0;

    if (findJavaInstanceMethod(pcm->env, &method, pcmDeviceClass, "await",
                               JAVA_SIG_METHOD(JAVA_SIG_VOID,
                                              ))) {
      (*pcm->env)->CallVoidMethod(pcm->env, pcm->device, method);
      clearJavaException(pcm->env, 1);
    }
  }
}

void
cancelPcmOutput (PcmDevice *pcm) {
  if (findPcmDeviceClass(pcm->env)) {
    static jmethodID method = 0;

    if (findJavaInstanceMethod(pcm->env, &method, pcmDeviceClass, "cancel",
                               JAVA_SIG_METHOD(JAVA_SIG_VOID,
                                              ))) {
      (*pcm->env)->CallVoidMethod(pcm->env, pcm->device, method);
      clearJavaException(pcm->env, 1);
    }
  }
}
