
/*
 * Copyright (C) NGINX, Inc.
 */

#include <nxt_auto_config.h>

#include <jni.h>
#include <nxt_unit.h>
#include <string.h>

#include "nxt_jni.h"
#include "nxt_jni_InputStream.h"
#include "nxt_jni_URLClassLoader.h"


static jint JNICALL nxt_java_InputStream_readLine(JNIEnv *env, jclass cls,
    jlong req_info_ptr, jarray b, jint off, jint len);
static jboolean JNICALL nxt_java_InputStream_isFinished(JNIEnv *env, jclass cls,
    jlong req_info_ptr);
static jint JNICALL nxt_java_InputStream_readByte(JNIEnv *env, jclass cls,
    jlong req_info_ptr);
static jint JNICALL nxt_java_InputStream_read(JNIEnv *env, jclass cls,
    jlong req_info_ptr, jarray b, jint off, jint len);
static jlong JNICALL nxt_java_InputStream_skip(JNIEnv *env, jclass cls,
    jlong req_info_ptr, jlong n);
static jint JNICALL nxt_java_InputStream_available(JNIEnv *env, jclass cls,
    jlong req_info_ptr);


static jclass  nxt_java_InputStream_class;


int
nxt_java_initInputStream(JNIEnv *env, jobject cl)
{
    int     res;
    jclass  cls;

    cls = nxt_java_loadClass(env, cl, "nginx.unit.InputStream");
    if (cls == NULL) {
        return NXT_UNIT_ERROR;
    }

    nxt_java_InputStream_class = (*env)->NewGlobalRef(env, cls);
    (*env)->DeleteLocalRef(env, cls);

    JNINativeMethod is_methods[] = {
        { (char *) "readLine",
          (char *) "(J[BII)I",
          nxt_java_InputStream_readLine },

        { (char *) "isFinished",
          (char *) "(J)Z",
          nxt_java_InputStream_isFinished },

        { (char *) "read",
          (char *) "(J)I",
          nxt_java_InputStream_readByte },

        { (char *) "read",
          (char *) "(J[BII)I",
          nxt_java_InputStream_read },

        { (char *) "skip",
          (char *) "(JJ)J",
          nxt_java_InputStream_skip },

        { (char *) "available",
          (char *) "(J)I",
          nxt_java_InputStream_available },
    };

    res = (*env)->RegisterNatives(env, nxt_java_InputStream_class,
                                  is_methods,
                                  sizeof(is_methods) / sizeof(is_methods[0]));

    nxt_unit_debug(NULL, "registered InputStream methods: %d", res);

    if (res != 0) {
        (*env)->DeleteGlobalRef(env, cls);
        return NXT_UNIT_ERROR;
    }

    return NXT_UNIT_OK;
}


static jint JNICALL
nxt_java_InputStream_readLine(JNIEnv *env, jclass cls,
    jlong req_info_ptr, jarray out, jint off, jint len)
{
    uint8_t                  *data;
    ssize_t                  res;
    nxt_unit_request_info_t  *req;

    req = nxt_jlong2ptr(req_info_ptr);

    data = (*env)->GetPrimitiveArrayCritical(env, out, NULL);

    res = nxt_unit_request_readline_size(req, len);

    if (res > 0) {
        res = nxt_unit_request_read(req, data + off, res);
    }

    nxt_unit_req_debug(req, "readLine '%.*s'", (int) res, (char *) data + off);

    (*env)->ReleasePrimitiveArrayCritical(env, out, data, 0);

    return res > 0 ? res : -1;
}


static jboolean JNICALL
nxt_java_InputStream_isFinished(JNIEnv *env, jclass cls, jlong req_info_ptr)
{
    nxt_unit_request_info_t  *req;

    req = nxt_jlong2ptr(req_info_ptr);

    return req->content_length == 0;
}


static jint JNICALL
nxt_java_InputStream_readByte(JNIEnv *env, jclass cls, jlong req_info_ptr)
{
    uint8_t                  b;
    ssize_t                  size;
    nxt_unit_request_info_t  *req;

    req = nxt_jlong2ptr(req_info_ptr);

    size = nxt_unit_request_read(req, &b, 1);

    return size == 1 ? b : -1;
}


static jint JNICALL
nxt_java_InputStream_read(JNIEnv *env, jclass cls, jlong req_info_ptr,
    jarray b, jint off, jint len)
{
    uint8_t                  *data;
    ssize_t                  res;
    nxt_unit_request_info_t  *req;

    req = nxt_jlong2ptr(req_info_ptr);

    data = (*env)->GetPrimitiveArrayCritical(env, b, NULL);

    res = nxt_unit_request_read(req, data + off, len);

    nxt_unit_req_debug(req, "read '%.*s'", (int) res, (char *) data + off);

    (*env)->ReleasePrimitiveArrayCritical(env, b, data, 0);

    return res > 0 ? res : -1;
}


static jlong JNICALL
nxt_java_InputStream_skip(JNIEnv *env, jclass cls, jlong req_info_ptr, jlong n)
{
    size_t                   rest, b_size;
    nxt_unit_buf_t           *buf;
    nxt_unit_request_info_t  *req;

    req = nxt_jlong2ptr(req_info_ptr);

    rest = n;

    buf = req->content_buf;

    while (buf != NULL) {
        b_size = buf->end - buf->free;
        b_size = rest < b_size ? rest : b_size;

        buf->free += b_size;
        rest -= b_size;

        if (rest == 0) {
            if (buf->end == buf->free) {
                buf = nxt_unit_buf_next(buf);
            }

            break;
        }

        buf = nxt_unit_buf_next(buf);
    }

    n = n < (jlong) req->content_length ? n : (jlong) req->content_length;

    req->content_length -= n;

    return n;
}


static jint JNICALL
nxt_java_InputStream_available(JNIEnv *env, jclass cls, jlong req_info_ptr)
{
    nxt_unit_request_info_t  *req;

    req = nxt_jlong2ptr(req_info_ptr);

    return req->content_length;
}
