#include <jni.h>
#include "JCL_buffer.h"
#include "jcl.h"

static jfieldID address_fid;
static jmethodID get_position_mid;
static jmethodID set_position_mid;
static jmethodID get_limit_mid;
static jmethodID set_limit_mid;
static jmethodID has_array_mid;
static jmethodID array_mid;
static jmethodID array_offset_mid;

static bool g_initialized;

int
JCL_init_buffer(JNIEnv *env, struct JCL_buffer *buf, jobject bbuf)
{
  if (! g_initialized)
  {
    jclass bufferClass = JCL_FindClass(env, "java/nio/Buffer");
    jclass byteBufferClass = JCL_FindClass(env, "java/nio/ByteBuffer");
    address_fid = env->GetFieldID(bufferClass, "address", 
                                     "Lgnu/classpath/Pointer;");
    if (address_fid == NULL)
      {
  	    JCL_ThrowException(env, "java/lang/InternalError", 
  	  	  "Unable to find internal field");
        return -1;
      }
    
    get_position_mid = env->GetMethodID(bufferClass, "position", "()I");
    set_position_mid = env->GetMethodID(bufferClass, "position", 
                                     "(I)Ljava/nio/Buffer;");
    get_limit_mid = env->GetMethodID(bufferClass, "limit", "()I");
    set_limit_mid = env->GetMethodID(bufferClass, "limit", 
                                  "(I)Ljava/nio/Buffer;");
    has_array_mid = env->GetMethodID(byteBufferClass, "hasArray", "()Z");
    array_mid = env->GetMethodID(byteBufferClass, "array", "()[B");
    array_offset_mid = env->GetMethodID(byteBufferClass, "arrayOffset", "()I");
  }

  void *addr = env->GetDirectBufferAddress (bbuf);

/*   NIODBG("buf: %p; bbuf: %p; addr: %p", (void *) buf, bbuf, addr); */
  
  buf->position = env->CallIntMethod(bbuf, get_position_mid);
  buf->limit = env->CallIntMethod(bbuf, get_limit_mid);
  buf->offset = 0;
  buf->count = 0;
  buf->type = UNKNOWN;
    
  if (addr != NULL)
    {
      buf->ptr = (jbyte *) addr;
      buf->type = DIRECT;
    }
  else
    {
      jboolean has_array;
      has_array = env->CallBooleanMethod(bbuf, has_array_mid);
      
      if (has_array == (jboolean) JNI_TRUE)
        {
          jbyteArray arr;
          buf->offset = env->CallIntMethod(bbuf, array_offset_mid);
          arr = static_cast<jbyteArray>(env->CallObjectMethod(bbuf, array_mid));
          buf->ptr = env->GetByteArrayElements(arr, 0);
          buf->type = ARRAY;
          env->DeleteLocalRef(arr);
        }
      else
        {
          jobject address = env->GetObjectField (bbuf, address_fid);
          if (address == NULL)
            return -1; /* XXX handle non-array, non-native buffers? */
          buf->ptr = (jbyte *) JCL_GetRawData(env, address);
          buf->type = HEAP;
          env->DeleteLocalRef(address);
        }
    }
      
  return 0;
}

void
JCL_release_buffer(JNIEnv *env, struct JCL_buffer *buf, jobject bbuf, 
    jint action)
{
  jbyteArray arr;

/*   NIODBG("buf: %p; bbuf: %p; action: %x", (void *) buf, bbuf, action); */
  
  /* Set the position to the appropriate value */
  if (buf->count > 0)
    {
      jobject bbufTemp;
      bbufTemp = env->CallObjectMethod(bbuf, set_position_mid, 
                                          buf->position + buf->count);
      env->DeleteLocalRef(bbufTemp);
    }
    
  switch (buf->type)
    {
    case DIRECT:
    case HEAP:
      break;
    case ARRAY:
      arr = static_cast<jbyteArray>(env->CallObjectMethod(bbuf, array_mid));
      env->ReleaseByteArrayElements(arr, buf->ptr, action);
      env->DeleteLocalRef(arr);
      break;
    case UNKNOWN:
      /* TODO: Handle buffers that are not direct or array backed */
      break;
    }
}

void
JCL_cleanup_buffers(JNIEnv *env, 
                    struct JCL_buffer *bi_list, 
                    jint vec_len, 
                    jobjectArray bbufs, 
                    jint offset,
                    jlong num_bytes)
{
  jint i;

/*   NIODBG("bi_list: %p; vec_len: %d; bbufs: %p; offset: %d; num_bytes: %lld", */
/*       (void *) bi_list, vec_len, bbufs, offset, num_bytes); */
  
  /* Update all of the bbufs with the approriate information */
  for (i = 0; i < vec_len; i++)
    {
      struct JCL_buffer* buf;
      jobject bbuf;
      
      buf = &bi_list[i];
      bbuf = env->GetObjectArrayElement(bbufs, offset + i);

      if (num_bytes > (buf->limit - buf->position))
        buf->count = (buf->limit - buf->position);
      else
        buf->count = static_cast<jint>(num_bytes);
        
      num_bytes -= buf->count;
      
      JCL_release_buffer(env, buf, bbuf, JNI_ABORT);
      env->DeleteLocalRef(bbuf);
    }
}
