/*
 * Copyright 1997-2005 Sun Microsystems, Inc.  All rights reserved.
 * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 */

/*
 * Native method support for java.util.zip.Deflater
 */

#include <stdio.h>
#include <stdlib.h>
#include "jlong.h"
#include "jni.h"
#include "jni_util.h"
#include "zlib.h"

#include "java_util_zip_Deflater.h"

#define DEF_MEM_LEVEL 8
#define STRIDE_BUF_SIZE 4096

static jfieldID strmID;
static jfieldID levelID;
static jfieldID strategyID;
static jfieldID setParamsID;
static jfieldID finishID;
static jfieldID finishedID;
static jfieldID bufID, offID, lenID;

typedef struct { 
    z_stream strm;
    jbyte in_buf[STRIDE_BUF_SIZE];
    jbyte out_buf[STRIDE_BUF_SIZE];
    jint stride_read_off;
    jint stride_read_len;
} def_data;

JNIEXPORT void JNICALL
Java_java_util_zip_Deflater_initIDs(JNIEnv *env, jclass cls)
{
    strmID = (*env)->GetFieldID(env, cls, "strm", "J");
    levelID = (*env)->GetFieldID(env, cls, "level", "I");
    strategyID = (*env)->GetFieldID(env, cls, "strategy", "I");
    setParamsID = (*env)->GetFieldID(env, cls, "setParams", "Z");
    finishID = (*env)->GetFieldID(env, cls, "finish", "Z");
    finishedID = (*env)->GetFieldID(env, cls, "finished", "Z");
    bufID = (*env)->GetFieldID(env, cls, "buf", "[B");
    offID = (*env)->GetFieldID(env, cls, "off", "I");
    lenID = (*env)->GetFieldID(env, cls, "len", "I");
}

JNIEXPORT jlong JNICALL
Java_java_util_zip_Deflater_init(JNIEnv *env, jclass cls, jint level,
				 jint strategy, jboolean nowrap)
{
    def_data *defptr = calloc(1, sizeof(def_data));

    if (defptr == 0) {
	JNU_ThrowOutOfMemoryError(env, 0);
	return jlong_zero;
    } else {
	char *msg;
	switch (deflateInit2(&defptr->strm, level, Z_DEFLATED,
			     nowrap ? -MAX_WBITS : MAX_WBITS,
			     DEF_MEM_LEVEL, strategy)) {
	  case Z_OK:
	    return ptr_to_jlong(defptr);
	  case Z_MEM_ERROR:
	    free(defptr);
	    JNU_ThrowOutOfMemoryError(env, 0);
	    return jlong_zero;
	  case Z_STREAM_ERROR:
	    free(defptr);
	    JNU_ThrowIllegalArgumentException(env, 0);
	    return jlong_zero;
	  default:
	    msg = defptr->strm.msg;
	    free(defptr);
	    JNU_ThrowInternalError(env, msg);
	    return jlong_zero;
	}
    }
}

JNIEXPORT void JNICALL
Java_java_util_zip_Deflater_setDictionary(JNIEnv *env, jclass cls, jlong defadr,
					  jarray b, jint off, jint len)
{
    Bytef *buf = (*env)->GetPrimitiveArrayCritical(env, b, 0);
    int res;
    if (buf == 0) {/* out of memory */
        return;
    }
    res = deflateSetDictionary(&(((def_data *)jlong_to_ptr(defadr))->strm), buf + off, len);
    (*env)->ReleasePrimitiveArrayCritical(env, b, buf, 0);
    switch (res) {
    case Z_OK:
	break;
    case Z_STREAM_ERROR:
	JNU_ThrowIllegalArgumentException(env, 0);
	break;
    default:
	JNU_ThrowInternalError(env, (((def_data *)jlong_to_ptr(defadr))->strm.msg));
	break;
    }
}

JNIEXPORT jint JNICALL
Java_java_util_zip_Deflater_deflateBytes(JNIEnv *env, jobject this, jlong def_adr,
					 jbyteArray writebuf, jint writeoff, jint writelen, jbyteArray readbuf, jint readoff, jint readlen, jboolean setParams, jboolean finish)
{
    def_data *defptr = jlong_to_ptr(def_adr);
    if(defptr == 0) {
          JNU_ThrowNullPointerException(env, 0);
          return 0;
    }

    const jint writelen_start = writelen;
    jboolean finished = JNI_FALSE;
    z_stream *strm = &defptr->strm;

    do  {
        /*If read-stride is empty and input-bytes are left, fill it up*/
       if (defptr->stride_read_len == 0 && readlen > 0) { 
        defptr->stride_read_len = readlen > STRIDE_BUF_SIZE ? STRIDE_BUF_SIZE : readlen;
        defptr->stride_read_off = 0;
        (*env)->GetByteArrayRegion(env, readbuf, readoff, defptr->stride_read_len, &defptr->in_buf);
       }

       int availReadBytes =  writelen > STRIDE_BUF_SIZE ? STRIDE_BUF_SIZE : writelen;
       strm->next_in = (Bytef *) (&defptr->in_buf + defptr->stride_read_off);
       strm->next_out = (Bytef *) &defptr->out_buf;
       strm->avail_in = defptr->stride_read_len;
       strm->avail_out = availReadBytes;

       int zres;
       if(setParams)  {    
           int level = (*env)->GetIntField(env, this, levelID);
           int strategy = (*env)->GetIntField(env, this, strategyID);
            /*TODO: What happens if deflateParams was called once, but not all pending output data was written because of the stride limit? Is it o.k. to continue with deflate?*/
           zres = deflateParams(strm, level, strategy);
        }else {
           /*Only finish if all input data has been beed to zlib, otherwise we finish per stride which lowers compression ratio*/
           zres = deflate(strm, (readlen == 0 && finish) ? Z_FINISH : Z_NO_FLUSH);
        }

        switch (zres)  {
            case Z_STREAM_END:
            finished = JNI_TRUE;
            //fall through
            case Z_OK:
            case Z_BUF_ERROR:
               if(setParams) {
                    (*env)->SetBooleanField(env, this, setParamsID, JNI_FALSE);
                    setParams = JNI_FALSE;
                }

               int processed_out = availReadBytes - strm->avail_out;
               int processed_in =  defptr->stride_read_len - strm->avail_in;
   
               if(processed_out > 0) {
                    (*env)->SetByteArrayRegion(env, writebuf, writeoff, processed_out, &defptr->out_buf);
                    writeoff += processed_out;
                    writelen -= processed_out;       
                }
                readlen -= processed_in;
                readoff += processed_in;
                defptr->stride_read_off += processed_in;
                defptr->stride_read_len -= processed_in;
                break;
        
                default:
                JNU_ThrowInternalError(env, strm->msg); 
        }
    }while(writelen > 0 && readlen > 0 && finished==JNI_FALSE);

    if(finished)
    {
        (*env)->SetBooleanField(env, this, finishedID, JNI_TRUE);
    }   

   (*env)->SetIntField(env, this, offID, readoff);
   (*env)->SetIntField(env, this, lenID, readlen);
    
    return writelen_start - writelen;
}   

JNIEXPORT jint JNICALL
Java_java_util_zip_Deflater_getAdler(JNIEnv *env, jclass cls, jlong defadr)
{
    return ((def_data *)jlong_to_ptr(defadr))->strm.adler;
}

JNIEXPORT jlong JNICALL
Java_java_util_zip_Deflater_getBytesRead(JNIEnv *env, jclass cls, jlong defadr)
{
    return ((def_data *)jlong_to_ptr(defadr))->strm.total_in;
}

JNIEXPORT jlong JNICALL
Java_java_util_zip_Deflater_getBytesWritten(JNIEnv *env, jclass cls, jlong defadr)
{
    return ((def_data *)jlong_to_ptr(defadr))->strm.total_out;
}

JNIEXPORT void JNICALL
Java_java_util_zip_Deflater_reset(JNIEnv *env, jclass cls, jlong defadr)
{
    if (deflateReset(&((def_data *)jlong_to_ptr(defadr))->strm) != Z_OK) {
	JNU_ThrowInternalError(env, 0);
    }
}

JNIEXPORT void JNICALL
Java_java_util_zip_Deflater_end(JNIEnv *env, jclass cls, jlong defadr)
{
    def_data *defptr = (def_data *) jlong_to_ptr(defadr);
    if (deflateEnd(&defptr->strm) == Z_STREAM_ERROR) {
	JNU_ThrowInternalError(env, 0);
    } else {
	free(defptr);
    }
}