/*
 * Copyright (c) 2001, 2004 IBM Corp and others.  All rights reserved.
 * This file is made available under the terms of the Common Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/cpl-v10.html
 * 
 * Contributors: 
 * 	  Andre Weinand (OTI Labs)
 */
#include <jni.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <utime.h>
#include <stdlib.h>
#include <string.h>
#include "core.h"

#include <CoreServices/CoreServices.h>

/*
 * Get a null-terminated byte array from a java byte array.
 * The returned bytearray needs to be freed when not used
 * anymore. Use free(result) to do that.
 */
static jbyte* getByteArray(JNIEnv *env, jbyteArray target) {
	jsize n;
	jbyte *temp, *result;
	
	temp = (*env)->GetByteArrayElements(env, target, 0);
	n = (*env)->GetArrayLength(env, target);
	result = calloc(n+1, sizeof(jbyte));
	memcpy(result, temp, n);
	(*env)->ReleaseByteArrayElements(env, target, temp, 0);
	return result;
}

/*
 * Get a null-terminated byte array from a java char array.
 * The byte array contains UTF8 encoding.
 * The returned bytearray needs to be freed when not used
 * anymore. Use free(result) to do that.
 */
static jbyte* getUTF8ByteArray(JNIEnv *env, jcharArray target) {

	jchar *temp = (*env)->GetCharArrayElements(env, target, 0);
	jsize n = (*env)->GetArrayLength(env, target);
	
	CFStringRef sr = CFStringCreateWithCharacters(kCFAllocatorDefault, temp, n); 
	CFIndex argStringSize = CFStringGetMaximumSizeForEncoding(n, kCFStringEncodingUTF8) + 1;
	char *result= (char*) calloc(argStringSize, sizeof(char));
	CFStringGetCString(sr, result, argStringSize, kCFStringEncodingUTF8);
	CFRelease(sr);
	
	(*env)->ReleaseCharArrayElements(env, target, temp, 0);
	
	return result;
}

/*
 * Argument 'name' is expected to be in UTF8 encoding.
 * Argument 'name' is released with 'free()'.
 * Returns 0 on error.
 */
static jlong internalGetStat(char *name) {

	struct stat info;
	jlong result;
	jint code;

	/* get stat */
	code = stat(name, &info);

	/* test if an error occurred */
	if (code == -1) {
		free(name);
		return 0;
	}

	/* filter interesting bits */
	/* lastModified */
	result = ((jlong) info.st_mtime) * 1000; /* lower bits */
	/* valid stat */
	result |= STAT_VALID;
	/* is folder? */
	if ((info.st_mode & S_IFDIR) == S_IFDIR)
		result |= STAT_FOLDER;
	/* is read-only? */
	if ((info.st_mode & S_IWRITE) != S_IWRITE)
		result |= STAT_READ_ONLY;
	else if ((info.st_flags & (UF_IMMUTABLE | SF_IMMUTABLE)) != 0)
		result |= STAT_READ_ONLY;

	free(name);

	return result;
}

/*
 * Argument 'name' is expected to be in UTF8 encoding.
 * Argument 'name' is released with 'free()'.
 * Returns true on success.
 */
static jboolean internalSetReadOnly(char *name, jboolean readOnly) {
   
	struct stat info;

	if (stat(name, &info) != 0) {
		free(name);
		return JNI_FALSE;
	}
		
	int oldmask, mask, flags;

	oldmask= mask = info.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO);
	
	flags= info.st_flags;
	
	if (readOnly) {
		mask &= ~(S_IWUSR | S_IWGRP | S_IWOTH);	// clear all write permission bits
		if (mask != oldmask)
			chmod(name, mask);
			
		flags |= UF_IMMUTABLE;					// set immutable flag for usr
		if (flags != info.st_flags)
			chflags(name, flags);
	} else {
		if (flags & UF_IMMUTABLE)
			chflags(name, flags & ~UF_IMMUTABLE);	// try to clear immutable flags for usr
			
		mask |= (S_IRUSR | S_IWUSR);				// set read and write permission for usr
		if (mask != oldmask)
			chmod(name, mask);
	}
		
	jboolean ok= (readOnly != 0) == ((internalGetStat(name) & STAT_READ_ONLY) != 0);
	return ok;
}

/*
 * Copies the file's 'mode', 'flags', and optionally the access and modification dates 
 * from source to destination.
 * Arguments 'sourceFile' and 'destinationFile' are expected to be in UTF8 encoding.
 * Arguments 'sourceFile' and 'destinationFile' are released with 'free()'.
 * Returns true on success.
 */
static jboolean internalCopyAttributes(char *sourceFile, char *destinationFile, jboolean copyLastModified) {

	struct stat info;
	struct utimbuf ut;
	int code;

	code = stat(sourceFile, &info);
	if (code == 0) {
    	code = chmod(destinationFile, info.st_mode);
    	if (code == 0) {
    		chflags(destinationFile, info.st_flags);	// ignore return code
    		if (copyLastModified) {
      			ut.actime = info.st_atime;
      			ut.modtime = info.st_mtime;
      			code = utime(destinationFile, &ut);
      		}
    	}
  	}
  	
	free(sourceFile);
	free(destinationFile);

	return code == 0;
}


/*
 * Class:     org_eclipse_core_internal_localstore_CoreFileSystemLibrary
 * Method:    internalIsUnicode
 * Signature: ()Z
 */
JNIEXPORT jboolean JNICALL Java_org_eclipse_core_internal_localstore_CoreFileSystemLibrary_internalIsUnicode
	(JNIEnv *env, jclass clazz) {

	return JNI_TRUE;	// MacOS X supports Unicode-based file names
}

/*
 * Class:     org_eclipse_core_internal_localstore_CoreFileSystemLibrary
 * Method:    internalGetStatW
 * Signature: ([C)J
 */
JNIEXPORT jlong JNICALL Java_org_eclipse_core_internal_localstore_CoreFileSystemLibrary_internalGetStatW
	(JNIEnv *env, jclass clazz, jcharArray target) {

	return internalGetStat(getUTF8ByteArray(env, target));
}   

/*
 * Class:     org_eclipse_core_internal_localstore_CoreFileSystemLibrary
 * Method:    internalGetStat
 * Signature: ([B)J
 */
JNIEXPORT jlong JNICALL Java_org_eclipse_core_internal_localstore_CoreFileSystemLibrary_internalGetStat
	(JNIEnv *env, jclass clazz, jbyteArray target) {

	return internalGetStat(getByteArray(env, target));
}

/*
 * Class:     org_eclipse_core_internal_localstore_CoreFileSystemLibrary
 * Method:    internalSetReadOnlyW
 * Signature: ([CZ)Z
 */
JNIEXPORT jboolean JNICALL Java_org_eclipse_core_internal_localstore_CoreFileSystemLibrary_internalSetReadOnlyW
	(JNIEnv *env, jclass clazz, jcharArray target, jboolean readOnly) {
   
	return internalSetReadOnly(getUTF8ByteArray(env, target), readOnly);
}

/*
 * Class:     org_eclipse_core_internal_localstore_CoreFileSystemLibrary
 * Method:    internalSetReadOnly
 * Signature: ([BZ)Z
 */
JNIEXPORT jboolean JNICALL Java_org_eclipse_core_internal_localstore_CoreFileSystemLibrary_internalSetReadOnly
	(JNIEnv *env, jclass clazz, jbyteArray target, jboolean readOnly) {

	return internalSetReadOnly(getByteArray(env, target), readOnly);
}

/*
 * Class:     org_eclipse_core_internal_localstore_CoreFileSystemLibrary
 * Method:    internalCopyAttributesW
 * Signature: ([C[CZ)Z
 */
JNIEXPORT jboolean JNICALL Java_org_eclipse_core_internal_localstore_CoreFileSystemLibrary_internalCopyAttributesW
  (JNIEnv *env, jclass clazz, jcharArray source, jcharArray destination, jboolean copyLastModified) {
  
	return internalCopyAttributes(getUTF8ByteArray(env, source), getUTF8ByteArray(env, destination), copyLastModified);
}

/*
 * Class:     org_eclipse_core_internal_localstore_CoreFileSystemLibrary
 * Method:    internalCopyAttributes
 * Signature: ([B[BZ)Z
 */
JNIEXPORT jboolean JNICALL Java_org_eclipse_core_internal_localstore_CoreFileSystemLibrary_internalCopyAttributes
(JNIEnv *env, jclass clazz, jbyteArray source, jbyteArray destination, jboolean copyLastModified) {

	return internalCopyAttributes(getByteArray(env, source), getByteArray(env, destination), copyLastModified);
}

/*
 * Class:     org_eclipse_ant_core_EclipseProject
 * Method:    internalCopyAttributes
 * Signature: ([B[BZ)Z
 */
JNIEXPORT jboolean JNICALL Java_org_eclipse_ant_core_EclipseFileUtils_internalCopyAttributes
   (JNIEnv *env, jclass clazz, jbyteArray source, jbyteArray destination, jboolean copyLastModified) {

	/* use the same implementation for both methods */
	return Java_org_eclipse_core_internal_localstore_CoreFileSystemLibrary_internalCopyAttributes
    			(env, clazz, source, destination, copyLastModified);
}
