/*
 * Burn.c
 *
 * Copyright (c) 2007  Vreixo Formoso
 *
 * This library 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 of the License, or
 * (at your option) any later version.
 * See COPYING file for details.
 */

#include "org_pykix_libburnia_libburn_Burn.h"
#include "BindingsUtil.h"

#include "libburn.h"

static struct burn_drive_info *drive_infos = NULL;

static int java_libburn_signal_handler(void *handle, int signum, int flag);
static int java_libburn_pacifier_func(void *handle, int patience, int elapsed);

/*
 * Class:     org_pykix_libburnia_libburn_Burn
 * Method:    burn_initialize
 * Signature: ()I
 */
JNIEXPORT jint JNICALL 
Java_org_pykix_libburnia_libburn_Burn_burn_1initialize
(
	JNIEnv *env, jclass cls
)
{
	return burn_initialize();
}

/*
 * Class:     org_pykix_libburnia_libburn_Burn
 * Method:    burn_finish
 * Signature: ()V
 */
JNIEXPORT void JNICALL 
Java_org_pykix_libburnia_libburn_Burn_burn_1finish
(
	JNIEnv *env, jclass cls
)
{
	if ( drive_infos != NULL ) {
		/* if we have a drive array, it must be freed before finish */
		burn_drive_info_free(drive_infos);
	}
	burn_finish();
}

/*
 * Class:     org_pykix_libburnia_libburn_Burn
 * Method:    burn_preset_device_open
 * Signature: (III)V
 */
JNIEXPORT void JNICALL 
Java_org_pykix_libburnia_libburn_Burn_burn_1preset_1device_1open
(	JNIEnv *env, jclass cls, jint exclusive, jint blocking, jint abort)
{
	burn_preset_device_open(exclusive, blocking, abort);
}

/*
 * Class:     org_pykix_libburnia_libburn_Burn
 * Method:    burn_allow_untested_profiles
 * Signature: (Z)V
 */
JNIEXPORT void JNICALL 
Java_org_pykix_libburnia_libburn_Burn_burn_1allow_1untested_1profiles
(
	JNIEnv *env, jclass cls, jboolean yes
)
{
	burn_allow_untested_profiles(yes);
}

/*
 * Class:     org_pykix_libburnia_libburn_Burn
 * Method:    burn_drive_scan
 * Signature: ()[Lorg/pykix/libburnia/libburn/DriveInfo;
 */
JNIEXPORT jobjectArray JNICALL 
Java_org_pykix_libburnia_libburn_Burn_burn_1drive_1scan
(
	JNIEnv *env, jclass cls
)
{
	unsigned int n_drives;
	static jclass driveInfoCls = NULL;
	static jmethodID cid = NULL;
	jobjectArray result;
	int i;

	if ( drive_infos != NULL ) {
		/* if we already have a drive array, it must be freed before scan again */
		burn_drive_info_free(drive_infos);
	}

	while ( !burn_drive_scan(&drive_infos, &n_drives) ) {
		usleep(1002);
	}

	if ( n_drives <= 0 ) {
		return NULL;
	}

	if ( driveInfoCls == NULL ) {
		driveInfoCls = java_libburn_find_class(env, "org/pykix/libburnia/libburn/DriveInfo");
		if ( driveInfoCls == NULL ) {
			return NULL;
		}
	}

	if ( cid == NULL ) {
		cid = (*env)->GetMethodID(env, driveInfoCls, "<init>", 
		"(JLjava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ZZZZZZZZZZZIIIIIJ)V");
		if (cid == NULL) {
		    return NULL; /* exception thrown */
		}
	}

	result = (*env)->NewObjectArray(env, n_drives, driveInfoCls, NULL);

	for ( i = 0; i < n_drives; ++i ) {

		struct burn_drive_info* drive = &(drive_infos[i]);

		jstring vendor = (*env)->NewStringUTF(env, drive->vendor);
		jstring product = (*env)->NewStringUTF(env, drive->product);
		jstring revision = (*env)->NewStringUTF(env, drive->revision);
		jstring location = (*env)->NewStringUTF(env, drive->location);
		
		jobject jdrive = (*env)->NewObject(env, driveInfoCls, cid, (jlong) drive, vendor, product, 
			revision, location, drive->read_dvdram, drive->read_dvdr, drive->read_dvdrom, 
			drive->read_cdr, drive->read_cdrw, drive->write_dvdram, 
			drive->write_dvdr, drive->write_cdr, drive->write_cdrw, 
			drive->write_simulate, drive->c2_errors, drive->buffer_size, 
			drive->tao_block_types, drive->sao_block_types, drive->raw_block_types, 
			drive->packet_block_types, drive->drive);

		if (jdrive == NULL) {
		    return NULL; /* out of memory error thrown */
		}

		(*env)->SetObjectArrayElement(env, result, i, jdrive);
		(*env)->DeleteLocalRef(env, vendor);
		(*env)->DeleteLocalRef(env, product);
		(*env)->DeleteLocalRef(env, revision);
		(*env)->DeleteLocalRef(env, location);
	}

	return result;
}

/*
 * Class:     org_pykix_libburnia_libburn_Burn
 * Method:    burn_drive_scan_and_grab
 * Signature: (Ljava/lang/String;Z)Lorg/pykix/libburnia/libburn/DriveInfo;
 */
JNIEXPORT jobject JNICALL 
Java_org_pykix_libburnia_libburn_Burn_burn_1drive_1scan_1and_1grab
(
	JNIEnv *env, jclass cls, jstring adr, jboolean load
)
{

	const char* cadr;
	static jclass driveInfoCls = NULL;
	static jmethodID cid = NULL;

	if ( drive_infos != NULL ) {
		/* if we already have a drive array, it must be freed before scan again */
		burn_drive_info_free(drive_infos);
	}

	cadr = (*env)->GetStringUTFChars(env, adr, NULL);
	if ( cadr == NULL ) {
		return NULL; /* OutOfMemoryError already thrown */
	}

	if ( burn_drive_scan_and_grab(&drive_infos, (char *)cadr, load) != 1 ) {
		(*env)->ReleaseStringUTFChars(env, adr, cadr);
		return NULL;
	}

	if ( driveInfoCls == NULL ) {
		driveInfoCls = java_libburn_find_class(env, "org/pykix/libburnia/libburn/DriveInfo");
		if ( driveInfoCls == NULL ) {
			return NULL;
		}
	}

	if ( cid == NULL ) {
		cid = (*env)->GetMethodID(env, driveInfoCls, "<init>", 
		"(JLjava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ZZZZZZZZZZZIIIIIJ)V");
		if (cid == NULL) {
		    return NULL; /* exception thrown */
		}
	}

	struct burn_drive_info* drive = &(drive_infos[0]);

	jstring vendor = (*env)->NewStringUTF(env, drive->vendor);
	jstring product = (*env)->NewStringUTF(env, drive->product);
	jstring revision = (*env)->NewStringUTF(env, drive->revision);
	jstring location = (*env)->NewStringUTF(env, drive->location);
	
	jobject jdrive = (*env)->NewObject(env, driveInfoCls, cid, (jlong) drive, vendor, product, 
		revision, location, drive->read_dvdram, drive->read_dvdr, drive->read_dvdrom, 
		drive->read_cdr, drive->read_cdrw, drive->write_dvdram, 
		drive->write_dvdr, drive->write_cdr, drive->write_cdrw, 
		drive->write_simulate, drive->c2_errors, drive->buffer_size, 
		drive->tao_block_types, drive->sao_block_types, drive->raw_block_types, 
		drive->packet_block_types, drive->drive);

	(*env)->DeleteLocalRef(env, vendor);
	(*env)->DeleteLocalRef(env, product);
	(*env)->DeleteLocalRef(env, revision);
	(*env)->DeleteLocalRef(env, location);
	(*env)->ReleaseStringUTFChars(env, adr, cadr);
	
	return jdrive;
}

/*
 * Class:     org_pykix_libburnia_libburn_Burn
 * Method:    burn_drive_add_whitelist
 * Signature: (Ljava/lang/String;)I
 */
JNIEXPORT jint JNICALL 
Java_org_pykix_libburnia_libburn_Burn_burn_1drive_1add_1whitelist
(
	JNIEnv *env, jclass cls, jstring adr
)
{
	const char* cadr;
	int res;

	cadr = (*env)->GetStringUTFChars(env, adr, NULL);
	if ( cadr == NULL ) {
		return -1; /* OutOfMemoryError already thrown */
	}

	res = burn_drive_add_whitelist( (char *) adr);
	
	(*env)->ReleaseStringUTFChars(env, adr, cadr);

	return res;
}

/*
 * Class:     org_pykix_libburnia_libburn_Burn
 * Method:    burn_drive_clear_whitelist
 * Signature: ()V
 */
JNIEXPORT void JNICALL 
Java_org_pykix_libburnia_libburn_Burn_burn_1drive_1clear_1whitelist
(
	JNIEnv *env, jclass cls
)
{
	burn_drive_clear_whitelist();
}

/*
 * Class:     org_pykix_libburnia_libburn_Burn
 * Method:    burn_set_verbosity
 * Signature: (I)V
 */
JNIEXPORT void JNICALL 
Java_org_pykix_libburnia_libburn_Burn_burn_1set_1verbosity
(
	JNIEnv *env, jclass cls, jint level
)
{
	burn_set_verbosity(level);
}

/*
 * Class:     org_pykix_libburnia_libburn_Burn
 * Method:    burn_drive_is_enumerable_adr
 * Signature: (Ljava/lang/String;)Z
 */
JNIEXPORT jboolean JNICALL 
Java_org_pykix_libburnia_libburn_Burn_burn_1drive_1is_1enumerable_1adr
(
	JNIEnv *env, jclass cls, jstring adr
)
{
	const char* cadr;
	int res;

	cadr = (*env)->GetStringUTFChars(env, adr, NULL);
	if ( cadr == NULL ) {
		return 0; /* OutOfMemoryError already thrown */
	}

	res = burn_drive_is_enumerable_adr( (char *) adr);
	
	(*env)->ReleaseStringUTFChars(env, adr, cadr);

	return res;
}

/*
 * Class:     org_pykix_libburnia_libburn_Burn
 * Method:    burn_drive_convert_fs_adr
 * Signature: (Ljava/lang/String;)Ljava/lang/String;
 */
JNIEXPORT jstring JNICALL 
Java_org_pykix_libburnia_libburn_Burn_burn_1drive_1convert_1fs_1adr
(
	JNIEnv *env, jclass cls, jstring path
)
{
	const char* cpath;
	char adr[BURN_DRIVE_ADR_LEN];

	cpath = (*env)->GetStringUTFChars(env, path, NULL);
	if ( cpath == NULL ) {
		return NULL; /* OutOfMemoryError already thrown */
	}

	if ( burn_drive_convert_fs_adr( (char *) cpath, adr) != 1) {
		(*env)->ReleaseStringUTFChars(env, path, cpath);
		return NULL;
	}

	(*env)->ReleaseStringUTFChars(env, path, cpath);
	
	return (*env)->NewStringUTF(env, adr);
}

/*
 * Class:     org_pykix_libburnia_libburn_Burn
 * Method:    burn_abort_def
 * Signature: (ILjava/lang/String;)Z
 */
JNIEXPORT jboolean JNICALL 
Java_org_pykix_libburnia_libburn_Burn_burn_1abort_1def
(
	JNIEnv *env, jclass cls, jint patience, jstring msg
)
{
	const char *message;
	int res;

	message = (*env)->GetStringUTFChars(env, msg, NULL);
	if ( message == NULL ) {
		return 0; /* OutOfMemoryError already thrown */
	}
	
	res = burn_abort(patience, burn_abort_pacifier, (void *) message);

	(*env)->ReleaseStringUTFChars(env, msg, message);

	if ( res < 0 ) {
		java_libburn_throw_burn_exception(env, "Can't abort");
	}
	return res;
}

/*
 * Class:     org_pykix_libburnia_libburn_Burn
 * Method:    burn_abort_java
 * Signature: (ILorg/pykix/libburnia/libburn/Burn$AbortHandler;)Z
 */
JNIEXPORT jboolean JNICALL 
Java_org_pykix_libburnia_libburn_Burn_burn_1abort_1java
(
	JNIEnv *env, jclass cls, jint patience, jobject handler
)
{

	int res = burn_abort(patience, java_libburn_pacifier_func, (void *) handler);

	if ( res < 0 ) {
		java_libburn_throw_burn_exception(env, "Can't abort");
	}
	return res;
}

static int java_libburn_pacifier_func
(
	void *handle, int patience, int elapsed
)
{
	/* call back to java */
	JNIEnv *env;
	jclass handlerCls;

	env = java_libburn_get_env();	

	if ( env == NULL ) {
		perror("Ups, can't call back to abort java pacifier.\n");
		return -1;
	}

	handlerCls = (*env)->FindClass(env, "org/pykix/libburnia/libburn/Burn$AbortHandler");
	if ( handlerCls == NULL ) {
		perror("Ups, java handler not found.\n");
		return -1;
	}

	jmethodID mid = (*env)->GetMethodID(env, handlerCls, "abortPacifier", "(II)I");
	if ( mid == NULL ) {
		perror("Ups, method not found.\n");
		return -1;
	}
	return (*env)->CallIntMethod(env, (jobject) handle, mid, patience, elapsed);
}

/*
 * Class:     org_pykix_libburnia_libburn_Burn
 * Method:    burn_set_signal_handling
 * Signature: (Lorg/pykix/libburnia/libburn/Burn$SignalHandler;I)V
 */
JNIEXPORT void JNICALL Java_org_pykix_libburnia_libburn_Burn_burn_1set_1signal_1handling
(
	JNIEnv *env, jclass cls, jobject handler, jint mode
)
{
	jobject sh =(*env)->NewGlobalRef(env, handler);
	burn_set_signal_handling( sh, java_libburn_signal_handler, mode);
}

/*
 * Class:     org_pykix_libburnia_libburn_Burn
 * Method:    burn_set_default_signal_handling
 * Signature: (Ljava/lang/String;I)V
 */
JNIEXPORT void JNICALL 
Java_org_pykix_libburnia_libburn_Burn_burn_1set_1default_1signal_1handling
(
	JNIEnv *env, jclass cls, jstring msg, jint mode
)
{
	const char *message = NULL;

	if (msg != NULL) {
		message = (*env)->GetStringUTFChars(env, msg, NULL);
		if ( message == NULL ) {
			return; /* OutOfMemoryError already thrown */
		}
	}

	burn_set_signal_handling((void *) message, NULL, mode);	

	(*env)->ReleaseStringUTFChars(env, msg, message);
}

static int java_libburn_signal_handler(void *handle, int signum, int flag)
{
	/* call back to java */
	JNIEnv *env;
	jclass handlerCls;

	env = java_libburn_get_env();	

	if ( env == NULL ) {
		perror("Ups, can't call back to java handler, just aborting... please wait\n");
		burn_abort(5, NULL, NULL);
		return -1;
	}

	handlerCls = (*env)->FindClass(env, "org/pykix/libburnia/libburn/Burn$SignalHandler");
	if ( handlerCls == NULL ) {
		perror("Ups, java handler not found, just aborting... please wait\n");
		burn_abort(5, NULL, NULL);
		return -1;
	}

	jmethodID mid = (*env)->GetMethodID(env, handlerCls, "handleSignal", "(II)I");
	if ( mid == NULL ) {
		perror("Ups, method not found, just aborting... please wait\n");
		burn_abort(5, NULL, NULL);
		return -1;
	}
	return (*env)->CallIntMethod(env, (jobject) handle, mid, signum, flag);
}

/*
 * Class:     org_pykix_libburnia_libburn_Burn
 * Method:    burn_version
 * Signature: ()Lorg/pykix/libburnia/libburn/Burn$Version;
 */
JNIEXPORT jobject JNICALL 
Java_org_pykix_libburnia_libburn_Burn_burn_1version
(
	JNIEnv *env, jclass cls
)
{
	int major;
	int minor;
	int micro;
	static jclass versionCls = NULL;
	static jmethodID cid = NULL;

	if ( versionCls == NULL ) {
		versionCls = java_libburn_find_class(env, "org/pykix/libburnia/libburn/Burn$Version");
		if ( versionCls == NULL ) {
			return NULL;
		}
	}

	if ( cid == NULL ) {
		cid = (*env)->GetMethodID(env, versionCls, "<init>", "(III)V");
		if (cid == NULL) {
		    return NULL; /* exception thrown */
		}
	}

	burn_version(&major, &minor, &micro);

	return (*env)->NewObject(env, versionCls, cid, major, minor, micro);
}

