/* -*- Mode: C; c-file-style: "gnu" -*-
   inflater.c -- native methods for java/util/zip/Inflater
   Created: Chris Toshok <toshok@hungry.com>, 9-Nov-1997
 */
/*
  This file is part of Japhar, the GNU Virtual Machine for Java Bytecodes.
  Japhar is a project of The Hungry Programmers, GNU, and OryxSoft.

  Copyright (C) 1997, 1998, 1999 The Hungry Programmers

  This library is free software; you can redistribute it and/or
  modify it under the terms of the GNU Library General Public
  License as published by the Free Software Foundation; either
  version 2 of the License, or (at your option) any later version.

  This library is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  Library General Public License for more details.

  You should have received a copy of the GNU Library General Public
  License along with this library; if not, write to the Free Software
  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "jni.h"
#include "zlib.h"
#include "runtime.h"

#include <stdlib.h>
#include <assert.h>

/* wow...
   1.2 has completely raped this file. */

jfieldID strm_field;
jboolean strm_is_long;


static void
get_strm_field(JNIEnv *env, jclass cls)
{
  static jboolean done = JNI_FALSE;

  if (done)
    return;

  done = JNI_TRUE;

  strm_field = (*env)->GetFieldID(env, cls, "strm", "J");
  if ((*env)->ExceptionOccurred(env))
    {
      (*env)->ExceptionClear(env);
      strm_is_long = JNI_FALSE;
      strm_field = (*env)->GetFieldID(env, cls, "strm", "I");
    }
  else
    strm_is_long = JNI_TRUE;
}

static z_streamp
get_inflater_strm(JNIEnv *env,
		  jobject obj)
{
  jclass obj_class = (*env)->GetObjectClass(env, obj);

  get_strm_field(env, obj_class);

  if (strm_is_long)
    return (z_streamp)(jint)(*env)->GetLongField(env, obj, strm_field);
  else
    return (z_streamp)(*env)->GetIntField(env, obj, strm_field);
}

static void
set_inflater_strm(JNIEnv *env,
		  jobject obj,
		  z_streamp strm)
{
  jclass obj_class = (*env)->GetObjectClass(env, obj);

  get_strm_field(env, obj_class);

  if (strm_is_long)
    (*env)->SetLongField(env, obj, strm_field, (jlong)(jint)strm);
  else
    (*env)->SetIntField(env, obj, strm_field, (jint)strm);
}

JNIEXPORT void JNICALL
Java_java_util_zip_Inflater_setDictionary(JNIEnv *env,
					  jobject obj,
					  jbyteArray b,
					  jint off,
					  jint len)
{
  z_streamp strm = get_inflater_strm(env, obj);
  jbyte* dict;

  dict = (*env)->GetByteArrayElements(env, b, NULL);

  inflateSetDictionary(strm,
		       dict + off,
		       len);

  (*env)->ReleaseByteArrayElements(env, b, dict, 0);
}

JNIEXPORT jint JNICALL
Java_java_util_zip_Inflater_inflate(JNIEnv *env,
				    jobject obj,
				    jbyteArray b,
				    jint off,
				    jint len)
{
  jclass obj_class = (*env)->GetObjectClass(env, obj);
  z_streamp strm = get_inflater_strm(env, obj);
  jfieldID buf_field;
  jfieldID len_field;
  jfieldID off_field;
  jbyteArray buf;
  jint in_length;
  jbyte* in_bytes;
  jint in_off;
  jbyte* out_bytes;
  int inflate_ret_val;
  jint ret_val = 0;
  int prev_total_out = strm->total_out;

  buf_field = (*env)->GetFieldID(env, obj_class, "buf", "[B");
  len_field = (*env)->GetFieldID(env, obj_class, "len", "I");
  off_field = (*env)->GetFieldID(env, obj_class, "off", "I");

  buf = (*env)->GetObjectField(env, obj, buf_field);
  in_length = (*env)->GetIntField(env, obj, len_field);
  in_off = (*env)->GetIntField(env, obj, off_field);

  in_bytes = (*env)->GetByteArrayElements(env, buf, NULL);
  out_bytes = (*env)->GetByteArrayElements(env, b, NULL);
  
  strm->next_in = (char*)in_bytes + in_off;
  strm->avail_in = in_length;
  strm->next_out = (char*)out_bytes + off;
  strm->avail_out = len;

  inflate_ret_val = inflate(strm, Z_PARTIAL_FLUSH);

  if (inflate_ret_val == Z_OK)
    ret_val = len - strm->avail_out;
  else
    {
      if (inflate_ret_val == Z_NEED_DICT
	  || inflate_ret_val == Z_BUF_ERROR)
	ret_val = 0;
      else if (inflate_ret_val == Z_DATA_ERROR)
	{
	  inflateSync(strm);
	  ret_val = 0;
	}
      else if (inflate_ret_val == Z_STREAM_END) /* we reached the end of the stream */
	{
	  ret_val = strm->total_out - prev_total_out;
	}
      else if (inflate_ret_val == Z_MEM_ERROR
	       || inflate_ret_val == Z_STREAM_ERROR)
	{
	  jclass cls = (*env)->FindClass(env, "java/util/zip/DataFormatException");

	  (*env)->ThrowNew(env, cls, "in inflateBytes");
	}
      else
	assert(0);
    }

  (*env)->ReleaseByteArrayElements(env, b, out_bytes, 0);
  (*env)->ReleaseByteArrayElements(env, buf, in_bytes, 0);

  return ret_val;
}

JNIEXPORT jint JNICALL
Java_java_util_zip_Inflater_inflateBytes(JNIEnv *env,
					 jobject obj,
					 jbyteArray b,
					 jint off,
					 jint len)
{
  return Java_java_util_zip_Inflater_inflate(env, obj, b, off, len);
}

JNIEXPORT jint JNICALL
Java_java_util_zip_Inflater_getAdler(JNIEnv *env,
				     jobject obj)
{
  z_streamp strm = get_inflater_strm(env, obj);

  return strm->adler;
}

JNIEXPORT jint JNICALL
Java_java_util_zip_Inflater_getTotalIn(JNIEnv *env,
				       jobject obj)
{
  z_streamp strm = get_inflater_strm(env, obj);

  return strm->total_in;
}

JNIEXPORT jint JNICALL
Java_java_util_zip_Inflater_getTotalOut(JNIEnv *env,
					jobject obj)
{
  z_streamp strm = get_inflater_strm(env, obj);

  return strm->total_out;
}

JNIEXPORT void JNICALL
Java_java_util_zip_Inflater_reset(JNIEnv *env,
				  jobject obj
#ifndef JDK1_1
				  , jlong strm
#endif
				  )

{
  /* yuck yuck yuck! */
  z_streamp zstrm;

#ifndef JDK1_1
  if (strm_is_long)
    zstrm = (z_streamp)(jint)strm;
  else
#endif
    zstrm = get_inflater_strm(env, obj);

  inflateReset(zstrm);
}

JNIEXPORT void JNICALL
Java_java_util_zip_Inflater_end(JNIEnv *env,
				jobject obj)
{
  z_streamp strm = get_inflater_strm(env, obj);

  inflateEnd(strm);
}

/* well, it had to happen sooner or later... in 1.1, this function is defined as:

   private native void init(boolean);

   but in 1.2, it's defined as:

   private static native long init(boolean);

   *sigh*....
*/

#ifdef JDK1_1
JNIEXPORT void JNICALL
#else
JNIEXPORT jlong JNICALL
#endif
Java_java_util_zip_Inflater_init(JNIEnv *env,
				 jobject obj,
				 jboolean nowrap)
{
  z_streamp strm = (z_streamp)calloc(1, sizeof(z_stream));
#define WSIZE 0x8000
  int i, windowBits;
  /* windowBits = log2(wsize) */
  for (i = WSIZE, windowBits = 0;  !(i & 1);  i >>= 1, ++windowBits);

  set_inflater_strm(env, obj, strm);

  inflateInit2(strm, -windowBits);

#ifndef JDK1_1
  return (jlong)(jint)strm;
#endif
}

/* JDK1.2 */
JNIEXPORT void JNICALL
Java_java_util_zip_Inflater_initIDs(JNIEnv *env,
				    jclass cls)
{
  get_strm_field(env, cls);
}
