/* ZipFile_wce.c
   Copyright (C) 2005 Free Software Foundation, Inc.

This file is part of GNU Classpath.

GNU Classpath is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2, or (at your option)
any later version.
 
GNU Classpath 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
General Public License for more details.

You should have received a copy of the GNU General Public License
along with GNU Classpath; see the file COPYING.  If not, write to the
Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
02111-1307 USA.

Linking this library statically or dynamically with other modules is
making a combined work based on this library.  Thus, the terms and
conditions of the GNU General Public License cover the whole
combination.

As a special exception, the copyright holders of this library give you
permission to link this library with independent modules to produce an
executable, regardless of the license terms of these independent
modules, and to copy and distribute the resulting executable under
terms of your choice, provided that you also meet, for each linked
independent module, the terms and conditions of the license of that
module.  An independent module is a module which is not derived from
or based on this library.  If you modify this library, you may extend
this exception to your version of the library, but you are not
obligated to do so.  If you do not wish to do so, delete this
exception statement from your version. */

#include "config.h"

#include <windows.h>
#include <assert.h>
#include "java_util_zip_ZipFile.h"

/**
 * The structure object used for the data exchange with a Java class.
 */
typedef struct zipfile_handle_t {
	HANDLE hFile;	 // Return value of CreateFileForMapping()
	HANDLE hMapping; // Return value of CreateFileMapping()
	char* data;		 // Return value of MapViewOfFile()

	unsigned short entry_count;		// Number of entries in "Central Directory"
	unsigned int central_directory_size;
	unsigned int central_directory_offset;
	
	char** entry_positions;

} zipfile_handle;

/**
 * Read little endian int.
 */
#define READ_INT32(p) \
	(((__int32) (p)[0] & 0x000000ff) \
			| ((__int32) (p)[1] << 8 & 0x0000ff00) \
			| ((__int32) (p)[2] << 16 & 0x00ff0000) \
			| ((__int32) (p)[3] << 24 & 0xff000000))

/**
 * Read little endian short.
 */
#define READ_INT16(p) \
	((__int16) (p)[0] & 0x00ff) \
			| ((__int16) (p)[1] << 8 & 0xff00)

/**
 * Checks locak header signature.
 */
#define IS_VALID_LOCAL_HEADER_SIGNATURE(p) (p[0] == 0x50 && p[1] == 0x4b && p[2] == 0x03 && p[3] == 0x04)

#define ZIP_EXCEPTION	"java/util/zip/ZipException"

/**
 Structur of "Central Directory"
name	size	offset
central file header signature	4	0
version made by	2	4
version needed to extract	2	6
general purpose bit flag	2	8
compression method	2	10
last mod file time	2	12
last mod file date	2	14
crc-32	4	16
compressed size	4	20
uncompressed size	4	24
file name length	2	28
extra field length	2	30
file comment length	2	32
disk number start	2	34
internal file attributes	2	36
external file attributes	4	38
relative offset of local header	4	42
		
file name (variable size)		
extra field (variable size)		
file comment (variable size)		
*/

/**
 * Load the offset value of all entries. 
 */
static int load_entry_positions(zipfile_handle* zfile) {
	char* p = zfile->data + zfile->central_directory_offset;
	char* end = p + zfile->central_directory_size;
	int i;
	int count = zfile->entry_count;
	for (i = 0; i < count; ++i) {
		int file_name_length	= READ_INT16(p+28) & 0xffff;
		int extra_field_length	= READ_INT16(p+30) & 0xffff;
		int file_comment_length	= READ_INT16(p+32) & 0xffff;
		
		zfile->entry_positions[i] = p;
		p += 46	// Length of header
			 + file_name_length
			 + extra_field_length
			 + file_comment_length;
	}
	return 1;
}

/**
 * Close zipfile_handle and release native resource.
 */
static void close(zipfile_handle* zfile) {
	// Unmap and close all handles.
	UnmapViewOfFile(zfile->data);
	CloseHandle(zfile->hMapping);
	CloseHandle(zfile->hFile);

	// free allocated memory.
	free(zfile->entry_positions);
	free(zfile);
}

/**
 * Decode UTF-8 string, and create String object.
 */
static jstring create_string(JNIEnv* env, const char* buff, int length) {
	jstring result = NULL;
	char* work = (char*) malloc(length + 1);
	if (! work) {
		return NULL;
	}
	strncpy(work, buff, length);
	work[length] = '\0';
	result = (*env)->NewStringUTF(env, work);
	free(work);

	return result;
}

/**
 * Load data from specified position and create ZipEntry object.
 * This function does not check the signature.
 */
static jobject get_entry(JNIEnv* env, zipfile_handle* zfile, const char* p)
{
	short flags;
	short method;
	int dostime;
	int crc32;
	int compressed_size;
	int uncompressed_size;
	int file_name_length;
	int extra_field_length;
	int file_comment_length;
	int offset;
	const char* file_name;
	const char* extra_field;
	const char* file_comment;
	jclass clazz;
	jmethodID constructor,
		      setDOSTime,
			  setSize,
			  setCompressedSize,
			  setCrc,
			  setMethod,
			  setExtra,
			  setComment;
	jfieldID  offsetID;

	jobject entry, strobj;
	flags				= READ_INT16(p+8);
	method				= READ_INT16(p+10);
	dostime				= READ_INT32(p+12);
	crc32				= READ_INT32(p+16);
	compressed_size		= READ_INT32(p+20);
	uncompressed_size	= READ_INT32(p+24);
	file_name_length	= READ_INT16(p+28) & 0xffff;
	extra_field_length	= READ_INT16(p+30) & 0xffff;
	file_comment_length	= READ_INT16(p+32) & 0xffff;
	offset				= READ_INT32(p+42);
	
	file_name			= p + 46;
	extra_field			= file_name + file_name_length;
	file_comment		= extra_field + extra_field_length;

	// Create instance of ZipEntry.
	// ToDo:Cache the class object etc.
	clazz = (*env)->FindClass(env, "java/util/zip/ZipEntry");
	assert(clazz);
	constructor = (*env)->GetMethodID(env,
									  clazz,
									  "<init>",
									  "(Ljava/lang/String;)V");
	setDOSTime = (*env)->GetMethodID(env,
									 clazz,
									 "setDOSTime",
									 "(I)V");
	setSize = (*env)->GetMethodID(env,
									 clazz,
									 "setSize",
									 "(J)V");
	setCompressedSize = (*env)->GetMethodID(env,
									 clazz,
									 "setCompressedSize",
									 "(J)V");
	setCrc = (*env)->GetMethodID(env,
									 clazz,
									 "setCrc",
									 "(J)V");
	setMethod = (*env)->GetMethodID(env,
									 clazz,
									 "setMethod",
									 "(I)V");
	setExtra = (*env)->GetMethodID(env,
									 clazz,
									 "setExtra",
									 "([B)V");
	setComment = (*env)->GetMethodID(env,
									 clazz,
									 "setComment",
									 "(Ljava/lang/String;)V");
	offsetID = (*env)->GetFieldID(env,
								clazz,
								"offset",
								"I");
	assert(constructor
			&& setDOSTime
			&& setSize
			&& setCompressedSize
			&& setCrc
			&& setMethod
			&& setExtra
			&& setComment
			&& offsetID);

	// Create string object.
	strobj = create_string(env, file_name, file_name_length);
	if (! strobj) {
		return NULL;
	}

	// Call constructor.
	entry = (*env)->NewObject(env,
							  clazz,
							  constructor,
							  strobj);

	// Set value to the ZipEntry object.
	// setDosTime(int)
	(*env)->CallVoidMethod(env, entry, setDOSTime, dostime);
	// setSize(long)
	(*env)->CallVoidMethod(env, entry, setSize, (jlong) uncompressed_size & 0xffffffffi64);
	// setCompressedSize(long)
	(*env)->CallVoidMethod(env, entry, setCompressedSize, (jlong) compressed_size & 0xffffffffi64);
	
	// setCrc(long)
	if (flags & 0x0008) {
		(*env)->CallVoidMethod(env, entry, setCrc, (jlong) crc32 & 0xffffffffi64);
	}
	// setMethod(int)
	(*env)->CallVoidMethod(env, entry, setMethod, (jint) method);

	// setExtra(byte[])
	if (extra_field_length) {
		jbyte* data;
		jbyteArray array = (*env)->NewByteArray(env, extra_field_length);
		if (!array) {
			return NULL;
		}
		data = (*env)->GetByteArrayElements(env, array, NULL);
		if (! data) {
			return NULL;
		}
		memcpy(data, extra_field, extra_field_length);
		(*env)->ReleaseByteArrayElements(env, array, data, 0);

		(*env)->CallVoidMethod(env, entry, setExtra, array);
	}
	// setComment(String)
	if (file_comment_length) {
		jstring comment = create_string(env, file_comment, file_comment_length);
		if (! comment) {
			return NULL;
		}
		(*env)->CallVoidMethod(env, entry, setComment, comment);
	}
	
	// Set "offset" field.
	(*env)->SetIntField(env, entry, offsetID, offset);
	return entry;
}

/*
 * Class:     java_util_zip_ZipFile
 * Method:    openNative
 * Signature: (Ljava/lang/String;)I
 */
JNIEXPORT jint JNICALL Java_java_util_zip_ZipFile_openNative
  (JNIEnv *env, jobject obj, jstring path)
{
	jint len;
	const _TCHAR* tmpfilename;
	_TCHAR* filename;
	zipfile_handle* zfile;
	HANDLE hFile, hMapping;
	char* data;
	DWORD size;
	char* p;

	len = (*env)->GetStringLength(env, path);
	tmpfilename = (*env)->GetStringChars(env, path, NULL);
	if (! tmpfilename) {
		jclass clazz = (*env)->FindClass(env, ZIP_EXCEPTION);
		(*env)->ThrowNew(env, clazz, "Failed to get the pathname.");
		return 0;
	}
	filename = (_TCHAR*) malloc(sizeof(_TCHAR) * (len + 1));
	if (! filename) {
		jclass clazz = (*env)->FindClass(env, "java/lang/OutOfMemoryError");
		(*env)->ReleaseStringChars(env, path, filename);
		(*env)->ThrowNew(env, clazz, NULL);
		return 0;
	}
	_tcsncpy(filename, tmpfilename, len);
	filename[len] = _T('\0');
	(*env)->ReleaseStringChars(env, path, filename);
	
	hFile = CreateFileForMapping(filename,
		                         GENERIC_READ,
								 FILE_SHARE_READ,
								 NULL,
								 OPEN_EXISTING,
								 FILE_FLAG_RANDOM_ACCESS,
								 NULL);
	// Free file name buffer.
	free(filename);

	if (hFile == INVALID_HANDLE_VALUE) {
		// Failed to open the file.
		jclass clazz = (*env)->FindClass(env, ZIP_EXCEPTION);
		const char* utf8 = (*env)->GetStringUTFChars(env, path, NULL);
		char* message = (char*) malloc(strlen(utf8) + 256);
		if (message) {
			sprintf(message, "Failed to open file: %s", utf8);
			(*env)->ThrowNew(env, clazz, message);
		}
		free(message);
		(*env)->ReleaseStringUTFChars(env, path, utf8);
		return 0;
	}

	hMapping = CreateFileMapping(hFile,
								 NULL,
								 PAGE_READONLY,
								 0,				// No limit
								 0,				// 
								 NULL);			// No name
	if (hMapping == INVALID_HANDLE_VALUE) {
		// Failed to create mapping object.
		jclass clazz = (*env)->FindClass(env, ZIP_EXCEPTION);
		(*env)->ThrowNew(env, clazz, "Failed to CreateFileMapping()");
		CloseHandle(hFile);
		return 0;
	}

	data = MapViewOfFile(hMapping,
						 FILE_MAP_READ,
						 0,					// Map from head of the file.
						 0,					// 
						 0);				// Map all of the file.
	if (! data) {
		// Failed to create the view.
		jclass clazz = (*env)->FindClass(env, ZIP_EXCEPTION);
		(*env)->ThrowNew(env, clazz, "Failed to MapViewOfFile()");
		CloseHandle(hMapping);
		CloseHandle(hFile);
		return 0;
	}
	
	// Check local header.
	if (! IS_VALID_LOCAL_HEADER_SIGNATURE(data)) {
		// Invalid header.
		jclass clazz = (*env)->FindClass(env, ZIP_EXCEPTION);
		(*env)->ThrowNew(env, clazz, "Not a zip file");

		// Unmap and close all handles.
		UnmapViewOfFile(data);
		CloseHandle(hMapping);
		CloseHandle(hFile);
		return 0;
	}

	// Read "End of central directory record" from the tail of the file - 20 bytes.
	size = GetFileSize(hFile, NULL);
	for (p = data + (size - 1 - 20); p > data; --p) {
		// 最後尾からシグニチャに一致する数値を探す
		if (p[0] == 0x50
				&& p[1] == 0x4b
				&& p[2] == 0x05
				&& p[3] == 0x06) {
			// Initialize zip_handle.
			zfile = (zipfile_handle*) malloc(sizeof(zipfile_handle));
			if (! zfile) {
				jclass clazz = (*env)->FindClass(env, "java/lang/OutOfMemoryError");
				(*env)->ThrowNew(env, clazz, NULL);
				UnmapViewOfFile(data);
				CloseHandle(hMapping);
				CloseHandle(hFile);
				return 0;
			}
			zfile->hFile = hFile;
			zfile->hMapping = hMapping;
			zfile->data = data;
			zfile->entry_count				= READ_INT16(p+10);
			zfile->central_directory_size	= READ_INT32(p+12);
			zfile->central_directory_offset = READ_INT32(p+16);
			break;
		}
	}
	if (zfile == NULL) {
		// Invalid Zip file
		jclass clazz = (*env)->FindClass(env, ZIP_EXCEPTION);
		(*env)->ThrowNew(env, clazz, "Failed to find a central directory");
		UnmapViewOfFile(data);
		CloseHandle(hMapping);
		CloseHandle(hFile);
		return 0;
	}
	
	// Read all entries in central directory.
	zfile->entry_positions = (char**) malloc(sizeof(char*) * zfile->entry_count);
	if (! zfile->entry_positions) {
		jclass clazz = (*env)->FindClass(env, "java/lang/OutOfMemoryError");
		(*env)->ThrowNew(env, clazz, NULL);
		// close
		close(zfile);
	}
	load_entry_positions(zfile);

	return (jint) zfile;
}

/*
 * Class:     java_util_zip_ZipFile
 * Method:    closeNative
 * Signature: (I)V
 */
JNIEXPORT void JNICALL Java_java_util_zip_ZipFile_closeNative
(JNIEnv *env, jobject obj, jint nativeDataAddress)
{
	if (nativeDataAddress) {
		close((zipfile_handle*) nativeDataAddress);
	}
}

/*
 * Class:     java_util_zip_ZipFile
 * Method:    getEntryByIndex
 * Signature: (II)Ljava/util/zip/ZipEntry;
 */
JNIEXPORT jobject JNICALL Java_java_util_zip_ZipFile_getEntryByIndex
  (JNIEnv *env, jobject obj, jint nativeAddress, jint index)
{
	zipfile_handle* zfile = (zipfile_handle*) nativeAddress;
	char* p = zfile->entry_positions[index];
	return get_entry(env, zfile, p);
}

/*
 * Class:     java_util_zip_ZipFile
 * Method:    findEntry
 * Signature: (ILjava/lang/String;)Ljava/util/zip/ZipEntry;
 */
JNIEXPORT jobject JNICALL Java_java_util_zip_ZipFile_findEntry
  (JNIEnv *env, jobject obj, jint nativeAddress, jstring name)
{
	int i;
	const char* rawdata;
	jobject result = NULL;
	int datalength;
	zipfile_handle* zfile = (zipfile_handle*) nativeAddress;

	// Get string data as UTF-8
	rawdata = (*env)->GetStringUTFChars(env, name, NULL);
	if (! rawdata) {
		return NULL;
	}
	datalength = strlen(rawdata);

	// Search in the central directory.
	for (i = 0; i < zfile->entry_count; ++i) {
		char* p = zfile->entry_positions[i];
		int file_name_length = READ_INT16(p+28) & 0xffff;
		char* file_name	= p + 46;
		if (datalength == file_name_length
				&& strncmp(file_name, rawdata, datalength) == 0) {
			// Found it.
			result = get_entry(env, zfile, p);
			break;
		}
	}
	(*env)->ReleaseStringUTFChars(env, name, rawdata);

	return result;
}


/*
 * Class:     java_util_zip_ZipFile
 * Method:    getEntryCount
 * Signature: (I)I
 */
JNIEXPORT jint JNICALL Java_java_util_zip_ZipFile_getEntryCount
 (JNIEnv *env, jobject obj, jint nativeData)
{
	zipfile_handle* zfile = (zipfile_handle*) nativeData;
	return zfile->entry_count;
}

/*
 * Class:     java_util_zip_ZipFile
 * Method:    checkNativeLocalHeader
 * Signature: (IIII)J
 */
JNIEXPORT jlong JNICALL Java_java_util_zip_ZipFile_checkNativeLocalHeader
  (JNIEnv *env, jobject obj, jint nativeAddress, jint localHeaderPosition, jint method, jint nameLength)
{
	jlong result = 0;
	zipfile_handle* zfile = (zipfile_handle*) nativeAddress;
	char* p = zfile->data + localHeaderPosition;
	// ローカルヘッダシグニチャをチェックする
	if (IS_VALID_LOCAL_HEADER_SIGNATURE(p)) {
		// ToDo:他のヘッダもチェックする
		int file_name_length = READ_INT16(p + 26) & 0xffff;
		int extra_field_length = READ_INT16(p + 28) & 0xffff;
		result = (jlong) (localHeaderPosition
						  + 30	// 固定部分
						  + file_name_length
						  + extra_field_length);

	} else {
		jclass clazz = (*env)->FindClass(env, ZIP_EXCEPTION);
		(*env)->ThrowNew(env, clazz, "Invalid local header");
	}
	return result & 0xffffffff;
}

/*
 * Class:     java_util_zip_ZipFile
 * Method:    readNative
 * Signature: (IJ)I
 */
JNIEXPORT jint JNICALL Java_java_util_zip_ZipFile_readNative__IJ
(JNIEnv *env, jobject obj, jint nativeAddress, jlong position)
{
	zipfile_handle* zfile = (zipfile_handle*) nativeAddress;
	if (position < 0) {
		// Negative offset.
		jclass clazz = (*env)->FindClass(env, ZIP_EXCEPTION);
		(*env)->ThrowNew(env, clazz, "Negative position");
		return -1;
	}
	return (jint) zfile->data[(unsigned int) position] | 0xff;
}

/*
 * Class:     java_util_zip_ZipFile
 * Method:    readNative
 * Signature: (IJ[BII)I
 */
JNIEXPORT jint JNICALL Java_java_util_zip_ZipFile_readNative__IJ_3BII
(JNIEnv *env, jobject obj, jint nativeAddress, jlong position, jbyteArray buff, jint off, jint len)
{
	jbyte* dest;
	int bufflen = (*env)->GetArrayLength(env, buff);
	int copylen;
	zipfile_handle* zfile = (zipfile_handle*) nativeAddress;

	if (position < 0) {
		// Negative offset.
		jclass clazz = (*env)->FindClass(env, ZIP_EXCEPTION);
		(*env)->ThrowNew(env, clazz, "Negative position");
		return -1;
	}
	if (off >= bufflen) {
		jclass clazz = (*env)->FindClass(env, "java/lang/ArrayIndexOutOfBoundsException");
		char msg[256];
		sprintf(msg, "%d", sizeof(msg));
		(*env)->ThrowNew(env, clazz, msg);
	}
	// Adjust copy length.
	copylen = (off + len) > bufflen ? (bufflen - off) : len;

	dest = (*env)->GetByteArrayElements(env, buff, NULL);
	memcpy(dest, &zfile->data[(unsigned int) position], copylen);
	(*env)->ReleaseByteArrayElements(env, buff, dest, 0);
	return copylen;
}