/*******************************************************************************
 * Copyright (c) 2008, 2009 Intel Corporation.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *    Intel - Initial API and Implementation
 *
 * $Id: nativeFileServer.c,v 1.9 2009/08/26 14:59:58 jwest Exp $
 *
 *******************************************************************************/ 

#include "nativeFileServer.h"
#include "tptp/TPTPUtils.h"

int readInt (SOCKET socket, int* value) {
	int bytesRead, bytesToRead, i, d, r;
	unsigned char* p;
	char buf[5];	// readFromSocket() appends terminating 0

	bytesToRead = 4;
	while(bytesToRead > 0) {
		if (readFromSocket(socket, buf+4-bytesToRead, bytesToRead, &bytesRead) <= 0) {
			return -1;
		}
		
		bytesToRead -= bytesRead;
	}
	
	for (i=0, p=(unsigned char*)buf, r=0; i<4; i++, p++) {
		d = *p;
		r = (r << 8) + d;
	}
	
	*value = r;

	return 0; 
}

char* readString(SOCKET socket) {
	int bytesRead;
	int stringLength, stringReadBytes;
	char* str;

	char *strResult = NULL;

	if (readInt(socket, &stringLength) < 0) {
		return NULL;
	}
	
	if (stringLength < 0) stringLength *= -1;	// negative means hyades dynamic command loading
	
	str = (char*) tptp_malloc(stringLength+1);
	if (str == NULL) return NULL;
	
	stringReadBytes = 0;
	
	while(stringReadBytes < stringLength) {
		if (readFromSocket(socket, str+stringReadBytes, stringLength-stringReadBytes, &bytesRead) <= 0) {
			tptp_free(str);
			return NULL;
		}
		
		stringReadBytes += bytesRead;
	}
	
	*(str + stringLength) = '\0';

	unicode2native(&strResult, str, strlen(str)+1);

	tptp_free(str);
	
	return strResult;
}

char* readStringIntoBuffer(fs_connection_block_t* con) {
	int bytesRead;
	int stringLength, stringReadBytes;
	char tempStr[TPTP_DEFAULT_BUFFER_LENGTH];
	char * tempStrPtr;

	if (readInt(con->socket, &stringLength) < 0) return NULL;

	if (stringLength < 0) stringLength *= -1;	// negative means hyades dynamic command loading

	if (stringLength > (con->bufferLength-1)) return NULL;		// including terminting 0
	
	stringReadBytes = 0;

	tempStr[0] = '\0';

	while(stringReadBytes < stringLength) {
		if (readFromSocket(con->socket, tempStr+stringReadBytes, stringLength-stringReadBytes, &bytesRead) < 0) {
			return NULL;
		}
		
		stringReadBytes += bytesRead;
	}

	*(tempStr + stringLength) = '\0';

	unicode2native(&tempStrPtr, tempStr, strlen(tempStr)+1);

	strcpy(con->buffer, tempStrPtr);
	tptp_free(tempStrPtr);


	return con->buffer;
}

int writeByte(SOCKET socket, int byte) {
	char c = (char) (byte & 0xff);
	
	return writeToSocket(socket, &c, 1);
}

int writeBoolean(SOCKET socket, int d) {
	d = (d != 0) ? FS_TRUE : FS_FALSE;
	return writeByte(socket, d);
}

int writeInt (SOCKET socket, int v) {
	writeByte (socket, (v >> 24));
	writeByte (socket, (v >> 16));
	writeByte (socket, (v >> 8));
	return writeByte(socket, v);
}

int writeString(SOCKET socket, char* buffer) {
	int len = 0;
	int rc = 0;

	#ifdef MVS
		char* nativeBuffer = 0;
		native2unicode(&nativeBuffer, buffer, strlen(buffer));
		len = strlen(nativeBuffer);
		writeInt(socket, len);
		rc = writeToSocket(socket, nativeBuffer, len);
		if(nativeBuffer != 0) {
			tptp_free(nativeBuffer);
		}
	#else
		len = strlen(buffer);
		writeInt(socket, len);
		rc = writeToSocket(socket, buffer, len);
	#endif
	return rc;
}

int writeStrings(SOCKET socket, char** array, int len) {
	int i;
	
	writeInt(socket, len);
	for (i=0; i<len; i++) {
		writeString(socket, array[i]);
	}
	
	return 0; 
}

int mkDirs(char* path) {
	char *p;
	
	p = path;
	while(1) {
		p = strchr(p, pathSeparatorCharacter);
		if (p == NULL) break;
		
		*p = '\0';
		
#ifdef _WIN32
		_mkdir(path);
#else
		mkdir(path, 0777);
#endif
		
		*p++ = pathSeparatorCharacter;
	}
	
	return 0;
}

int readFile(fs_connection_block_t* con, char* fileName) {
	int contentLen;
	FILE* fp;
	int num;
	
	mkDirs(fileName);	
	
	fp = fopen(fileName, "wb");
	if (fp == NULL) {
		TPTP_LOG_ERROR_MSG1(con->stateData, "NFS: write file open error: %s", fileName);
		return -1;
	}

	readInt(con->socket, &contentLen);	// high 4 bytes
	readInt(con->socket, &contentLen);	

	while (contentLen > 0) {
		int bytesToRead = contentLen;
		if (bytesToRead > con->bufferLength) bytesToRead = con->bufferLength;
		
		if (readFromSocket(con->socket, con->buffer, bytesToRead, &num) < 0) break;
		if (num <= 0) break;
		
		fwrite(con->buffer, num, 1, fp);
		contentLen -= num;
	}

	fclose(fp);

	writeInt(con->socket, 0);	// high 4 bytes
	writeInt(con->socket, getFileSize(fileName));	

	return 0; 
}

int writeFile(fs_connection_block_t* con, char* fileName) {
	int contentLen;
	int gotLen;
	FILE* fp;
	int n;
	
	contentLen = getFileSize(fileName);
	if (contentLen < 0) return -1;

	fp = fopen(fileName, "rb");
	if (fp == NULL) {
		TPTP_LOG_ERROR_MSG1(con->stateData, "NFS: read file open error: %s", fileName);
		return -1;
	}

	writeInt(con->socket, 0);			// high 4 bytes of contentLength (64-bit)
	writeInt(con->socket, contentLen);

	while ((n = fread((void *) con->buffer, 1, con->bufferLength, fp)) > 0) {
		writeToSocket(con->socket, con->buffer, n); 
	}		

	fclose(fp);
				
	readInt(con->socket, &gotLen);	// high 4 bytes
	readInt(con->socket, &gotLen);
	
	return 0; 
}

int openControlDialog(fs_connection_block_t* con) {
	return writeString(con->socket, OPEN_CONTROL_DIALOG);
}

int closeControlDialog(fs_connection_block_t* con) {
	return writeString(con->socket, CLOSE_CONTROL_DIALOG);
}

int readFSCommand(fs_connection_block_t* con) {
	char* str;

	str = readStringIntoBuffer(con);
	if (str == NULL) {
		return -1;
	}
 
	if (!strcmp(str, DETERMINE_SERVER_REACH_COMMAND_STR)) return DETERMINE_SERVER_REACH_COMMAND; 
	if (!strcmp(str, GET_FILE_COMMAND_STR)) return GET_FILE_COMMAND;
	if (!strcmp(str, PUT_FILE_COMMAND_STR)) return PUT_FILE_COMMAND;
	if (!strcmp(str, DELETE_FILE_COMMAND_STR)) return DELETE_FILE_COMMAND;
	if (!strcmp(str, DELETE_DIR_COMMAND_STR)) return DELETE_DIR_COMMAND;
	if (!strcmp(str, VALIDATE_DIR_COMMAND_STR)) return VALIDATE_DIR_COMMAND;
	if (!strcmp(str, QUERY_SERVER_STATUS_COMMAND_STR)) return QUERY_SERVER_STATUS_COMMAND;
	if (!strcmp(str, MDFY_PERMISSION_COMMAND_STR)) return MDFY_PERMISSION_COMMAND;
	if (!strcmp(str, LIST_CONTENT_COMMAND_STR)) return LIST_CONTENT_COMMAND;

	TPTP_LOG_ERROR_MSG1(con->stateData, "NFS: unknown message: %s", str);

	return -1;		
}

int processServerReachCmd(fs_connection_block_t* con) {
	char* host;
	int port;
	int d, rc;
	SOCKET testSocket;
	unsigned long ipAddr;
	struct addrinfo *AI = NULL;

	host = readStringIntoBuffer(con);
	if (host == NULL) return -1;
	
	readInt (con->socket, &port); 

	testSocket = connectToHostWithHostname(host, port, &AI);

	d = 0;
	
	if(testSocket != INVALID_SOCKET) {
		d = 1;
		closeSocket(testSocket);
	}
	
	writeByte(con->socket, d);	// "server is reachable" responce
	
	return 0;
}

int getFiles(fs_connection_block_t* con, char** names, int num) {
	int i;
	char* name;

	for(i=0; i<num; i++) {
		name = readString(con->socket);
		if (name == NULL) {
			return -1;		
		}
		
		names[i] = name;	
	}

	for (i=0; i<num; i++) {
		if (writeFile(con, names[i])) {
			return -1;
		}
	}
	
	return 0;	
}

void checkFileSeparators(char* path) {
	char  ips;
	char* p;
	
#ifdef _WIN32
	ips = '/';
#else
	ips = '\\';
#endif			
	
	p = path;
	while (1) {
		p = strchr(p, ips);
		if (p == NULL) break;
		
		*p = pathSeparatorCharacter;
	}
}

char* getAbsolutePath(char* path) {
	char *absPath, *tmpDir;
	int tmpDirLen;
	
	if (isAbsolutePath(path)) return NULL;
	
	tmpDir = getTempDir();
	tmpDirLen = strlen(tmpDir);
	
	absPath = (char*) tptp_malloc(strlen(tmpDir) + strlen(path) + 2);	// including trailing null and file separator
	strcpy(absPath, tmpDir);
	if (*(absPath+tmpDirLen-1) != pathSeparatorCharacter) strcat(absPath, pathSeparator);
	strcat(absPath, path);

	return absPath;
}

int putFiles(fs_connection_block_t* con, char** names, int num) {
	int i;
	char *name, *absName;
	
	for(i=0; i<num; i++) {
		name = readStringIntoBuffer(con);
		if (name == NULL) {
			return -1;		
		}
		
		checkFileSeparators(name);	
	
		absName = getAbsolutePath(name);
		if (absName != NULL) { 
			names[i] = absName;
		}
		else {
			names[i] = (char*) tptp_malloc(strlen(name)+1);
			strcpy(names[i], name);
		}	
	}

	for (i=0; i<num; i++) {
		if (readFile(con, names[i])) {
			return -1;
		}
	}	

	writeStrings(con->socket, names, num);	// files resolution
	
	return 0;
}

int processDeleteFileCmd(fs_connection_block_t* con) {
	int i, numFiles, num;
	char* name;

	readInt(con->socket, &numFiles);
	readInt(con->socket, &num);
	if (num <= 0) return -1;
	
	for (i=0; i<num; i++) {
		name = readStringIntoBuffer(con);
		if (name == NULL) return -1;
		
		remove(name);
	}

	return 0;
}

int validateDir(fs_connection_block_t* con, char** names, int num) {
	int i;
	char* name;
	
	for(i=0; i<num; i++) {
		name = readString(con->socket);
		if (name == NULL) {
			return -1;		
		}
		
		names[i] = name;	
	}

	writeInt(con->socket, num);			// reponse array size
	for (i=0; i<num; i++) {
		writeBoolean(con->socket, validateDirectory(names[i]));
	}
	
	return 0;	
}

int processQueryServerStatusCmd(fs_connection_block_t* con) {
	char* message;
	
	message = readStringIntoBuffer(con);
	if (message == NULL) return -1;
	
	writeString(con->socket, message);
	
	return 0;
}

int processDummyCmd(fs_connection_block_t* con) {
	return 0;
}

int processFileArrayCmd(fs_connection_block_t* con, int cmd) {
	int i, numFiles, num;
	char** names=NULL;
	int res=-1;
	
	readInt(con->socket, &numFiles);
	readInt(con->socket, &num);
	if (num <= 0) return -1;
	
	names = (char**) tptp_malloc(num*sizeof(char*));
	memset(names, 0, num*sizeof(char*));

	switch(cmd) {
	case GET_FILE_COMMAND:
		res = getFiles(con, names, num);
		break;
		 
	case PUT_FILE_COMMAND:
		res = putFiles(con, names, num);
		break;

	case VALIDATE_DIR_COMMAND:
		res = validateDir(con, names, num);
		break;
	}

	for (i=0; i<num; i++) {
		if (names[i] != NULL) tptp_free(names[i]);
	}
	
	tptp_free(names);
	
	return res;
}

int processFSCommand (fs_connection_block_t* con, int cmd) {
	openControlDialog(con);
	closeControlDialog(con);

	switch(cmd) {
	case DETERMINE_SERVER_REACH_COMMAND:
		return processServerReachCmd(con);

	case GET_FILE_COMMAND:
	case PUT_FILE_COMMAND:
	case VALIDATE_DIR_COMMAND:
		return processFileArrayCmd(con, cmd);

	case DELETE_FILE_COMMAND:
	case DELETE_DIR_COMMAND:
		return processDeleteFileCmd(con);

	case QUERY_SERVER_STATUS_COMMAND:
		return processQueryServerStatusCmd(con);

	case MDFY_PERMISSION_COMMAND:
	case LIST_CONTENT_COMMAND:
		return processDummyCmd(con);
	}

	
	return -1;
}

THREAD_USER_FUNC_RET_TYPE processFSRequest(LPVOID args){
	SOCKET socket;
	fs_connection_block_t* con;
	int cmd;

	con = (fs_connection_block_t*)args;
	socket = con->socket;
	
	while ((cmd = readFSCommand(con)) > 0) {
		if (processFSCommand(con, cmd)) break;
	}

	tptp_free(con->buffer);
	tptp_free(con);
	closeSocket(socket);
	
	return 0;
}

static SOCKET serverSocket;

static SOCKET * serverSockets;
static int numServerSockets;
static socket_accept_t socketAccept;

THREAD_USER_FUNC_RET_TYPE startNativeFileServer(LPVOID args){
	int rc, port;
	SOCKET clientSock;
	fs_connection_block_t* con;
	tl_state_data_t*    stateData;
	TID                 threadId;
	HANDLE              threadHandle;
	
	serverSockets = (SOCKET *) tptp_malloc(sizeof(SOCKET) * FD_SETSIZE);
	
	initSocketAccept(&socketAccept);

	stateData = (tl_state_data_t*) args;
	port = ((cctl_state_data_t*)stateData->implData)->filePort;
	if (port <= 0) port = RA_FILE_PORT_NUM_SERVER;
	
	rc = getTheSocket(port, serverSockets, &numServerSockets); 

	//serverSocket = getTheSocket(port, &fsSockAddr);
	if (rc < 0 || numServerSockets == 0) {
		TPTP_LOG_ERROR_MSG(stateData, "NFS: unable to create the file server socket.");
		return 0;
	}
	
	while(1) { 
		clientSock = acceptSocketConnections(serverSockets, numServerSockets, &socketAccept); 
	    if (clientSock < 0) break;

		con = (fs_connection_block_t*) tptp_malloc(sizeof(fs_connection_block_t));
		if (con == NULL) {
			closeSocket(clientSock);
			continue;
		}
			
		con->socket = clientSock;        
		con->buffer = (char*) tptp_malloc(TPTP_DEFAULT_BUFFER_LENGTH+1); // +1 since readFromSocket() adds terminating 0
		con->bufferLength = TPTP_DEFAULT_BUFFER_LENGTH;
		con->stateData = stateData;
	        
	    tptpStartThread(processFSRequest, (LPVOID)con, &threadId, &threadHandle) ;
		CLOSE_THREAD_HANDLE(threadHandle);
	}
	
	closeSocket(serverSocket);
	
	return 0;
} 

int stopNativeFileServer() {
	closeSocket(serverSocket);
	return 0;
}
