/*******************************************************************************
 * Copyright (c) 2005, 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:
 *    Hoang Nguyen, Intel - Initial implementation.
 *    Spundun Bhatt (spundun@gmail.com), with the support and encouragement of the University of Southern California Information Sciences Institute Distributed Scalable Systems Division.
 *    
 * $Id: FileTransferAgent.cpp,v 1.29 2009/08/26 14:59:58 jwest Exp $
 *
 *******************************************************************************/ 


#include "tptp/agents/FileTransferAgent.h"
#include "tptp/TPTPCommon.h"
#include "tptp/TPTPOSCalls.h"
#include "tptp/agents/AgentLog.h"
#include "tptp/TPTPUtils.h"
#include <time.h>
#include <stdio.h>

#include "tptp/dime.h"


#define UNKNOWN_CMD				100
#define GET_FILE_CMD	1
#define PUT_FILE_CMD	2
#define DELETE_FILE_CMD	3


#define IID_FILE_TRANSFER_AGENT		1
#define IID_AGENT_MANAGER		2
#define UNKNOWN_INTERFACE		100

#define BLOCK_SIZE 512

#ifndef ERROR
#define ERROR 0
#endif

typedef struct {
	tptp_int32 clientId;
	char *localFile;
	char *remoteFile;
	FileTransferAgent *pAgent;
} ThreadPInfo;

THREAD_USER_FUNC_RET_TYPE getFileThread(void *pThreadInfo) 
{
	ThreadPInfo *pInfo;
	tptp_int32 rc;

	pInfo = (ThreadPInfo *) pThreadInfo;

	rc = pInfo->pAgent->openAndSendFile(pInfo->clientId, pInfo->localFile, pInfo->remoteFile);
	return 0;
}

THREAD_USER_FUNC_RET_TYPE putFileThread(void *pThreadInfo) 
{
	ThreadPInfo *pInfo;
	tptp_int32 rc;

	pInfo = (ThreadPInfo *) pThreadInfo;

	rc = pInfo->pAgent->createAndGetFile(pInfo->clientId, pInfo->localFile, pInfo->remoteFile);
	return 0;
}


FileTransferAgent::FileTransferAgent(char* name) : BaseCollectorImpl(name), BaseAgentImpl(name)
{	
	tptp_initializeLock(&fileOpListLock);
	tptp_list_init(&fileOpList);
}


FileTransferAgent::~FileTransferAgent()
{
}

tptp_int32 FileTransferAgent::getFile(CmdBlock* cmdBlock)
{
	return processGetFileRequest(cmdBlock);
}

tptp_int32 FileTransferAgent::putFile(CmdBlock* cmdBlock)
{
	return processPutFileRequest(cmdBlock);
}

tptp_int32 FileTransferAgent::deleteFile(CmdBlock* cmdBlock)
{
	return processDeleteFileRequest(cmdBlock);
}

tptp_int32 FileTransferAgent::processDeleteFileRequest(CmdBlock* cmdBlock)
{
	tptp_int32 ret = 0;

	const tptp_list_t* paramList  = cmdBlock->getParamList();
	char *remoteFile;

	/* First compute the parameters with error checking code */
	/* Only 1 parameter name remoteFile is expected!        */
	if (paramList->count != 1) {
		TPTP_LOG_ERROR_MSG1(this, "FileTransferAgent deleteFile params= %d", 
				paramList->count);
		return -1;
	} else {
		if (0 != getStringParam("remoteFile", paramList, &remoteFile)) {
			TPTP_LOG_ERROR_MSG(this, "FileTransferAgent deleteFile remote file name error");
			return -1;
		}
	}

	ret = remove(remoteFile);

	if (ret == -1) {
		TPTP_LOG_ERROR_MSG(this, "FileTransferAgent deleteFile system error");
		return ret;
	}

	return ret;
}


tptp_int32 FileTransferAgent::processPutFileRequest(CmdBlock* cmdBlock)
{
	tptp_int32 ret = ERROR;

	const tptp_int32 sourceID	  = cmdBlock->getSourceID();
	const tptp_list_t* paramList  = cmdBlock->getParamList();

	char *localFile=NULL;
	char *remoteFile=NULL;

	FILE *fp=NULL;

	/* First compute the parameters with error checking code */
	/* 2 parameters (localFile, remoteFile) are expected     */
	if (paramList->count != 2) {
		TPTP_LOG_ERROR_MSG1(this, "FileTransferAgent putFile params= %d", 
				paramList->count);
		return 0;
	} else {
		if (0 != getStringParam("localFile", paramList, &localFile))
		{
			TPTP_LOG_ERROR_MSG(this, "FileTransferAgent putFile local file name error");
			return 0;
		}
		if (0 != getStringParam("remoteFile", paramList, &remoteFile))
		{
			TPTP_LOG_ERROR_MSG(this, "FileTransferAgent putFile remote file name error");
			return 0;
		}
	}

	fp = fopen(remoteFile, "wb");

	if (fp == NULL) {
		TPTP_LOG_ERROR_MSG(this, "FileTransferAgent putFile remote NULL ");
		return ret;
	}

	initializeFileTransfer(remoteFile, fp);

	TID pthreadId;
	HANDLE pthreadHandle;
	ThreadPInfo *pInfo= (ThreadPInfo *) tptp_malloc(sizeof(ThreadPInfo));;

	pInfo->clientId = sourceID;
	pInfo->localFile = localFile;
	pInfo->remoteFile = remoteFile;
	pInfo->pAgent = this;

 	ret= tptpStartThread(putFileThread, (void *) pInfo, &pthreadId, &pthreadHandle);

	if (ret == -1) {
		TPTP_LOG_ERROR_MSG(this, "FileTransferAgent putFile -error with create and get");
		return ERROR;
	}

	return ret;
}

tptp_int32 FileTransferAgent::sendFileData(tptp_int32 destinationID, char buffer[], tptp_int32 bufferLength, tptp_int32 recType, char *fileName)
{
	DIME_HEADER_PTR_T dp;
	tptp_int32 dimeLength;
	tptp_int32 rc;

	dimeLength = MAKE_DIME_FILE_TRANSFER(&dp, fileName, bufferLength);

	if (recType == DIME_MESSAGE_START_END) {
		DIME_START(dp);
		DIME_END(dp);
	} else if (recType == DIME_MESSAGE_START) {
		DIME_START(dp);
	} else if (recType == DIME_MESSAGE_END) {
		DIME_END(dp);
	}

	MAKE_NBO_DIME_HEADER((char *) dp);
	rc = sendData(destinationID, buffer, bufferLength, dp, dimeLength);
	FREE_DIME (dp);

	return 0;
}

void FileTransferAgent::sendError(tptp_int32 destinationID, char *fileName, int errCode)
{
	DIME_HEADER_PTR_T dp;
	tptp_int32 dimeLength;

	dimeLength = MAKE_DIME_FILE_TRANSFER_ERROR(&dp, fileName, 0, errCode);
	DIME_START(dp);
	DIME_END(dp);

	MAKE_NBO_DIME_HEADER((char *) dp);
	sendData(destinationID, NULL, 0, dp, dimeLength);
	FREE_DIME (dp);
}

tptp_int32 FileTransferAgent::openAndSendFile(tptp_int32 clientId, char *localFile, char *remoteFile)
{
	char buffer[BLOCK_SIZE+1]; 
	FILE *fp;
	tptp_int32 i;
	tptp_int32 ret = ERROR;
	tptp_int32 n;
	tptp_int32 recType;
	tptp_int32 fileSize;

	/* Open the file */
	fp = fopen(remoteFile, "rb");
	if (fp == NULL) {
		TPTP_LOG_ERROR_MSG(this, "FileTransferAgent getFile remote NULL");
		sendError(clientId, localFile, FILE_NOT_FOUND);
		return ret;
	}

	/* Read by BLOCK_SIZE byte blocks */
	/* The last read will send the remaining n bytes... all other
	 * reads and send's are for BLOCK_SIZE */
	tptp_int32 count = 0;
	fileSize = getFileSize(remoteFile);
	i =0;
	
	while ((n = fread((void *) buffer, 1, BLOCK_SIZE, fp)) > 0) {		
		count = count + n;
		if (i == 0) {
			recType = DIME_MESSAGE_START;
		} else {
			recType = DIME_MESSAGE_MIDDLE;
		}
		//TPTP_LOG_DEBUG_MSG1(this, "The chunk number - %d", i);
		//TPTP_LOG_DEBUG_MSG1(this, "The file size - %d", fileSize);
		//TPTP_LOG_DEBUG_MSG1(this, "The Total bytes sent - %d", count);
		//TPTP_LOG_DEBUG_MSG1(this, "The chunk data - %s", buffer);
		//TPTP_LOG_DEBUG_MSG1(this, "The Total chunks - %d", fileSize/BLOCK_SIZE);
		if (count == fileSize) {
			if (i == 0) {
				recType = DIME_MESSAGE_START_END;
			} else {

				recType = DIME_MESSAGE_END;
			}
		}		
		ret = this->sendFileData(clientId, buffer, n, recType, localFile);
		//TPTP_LOG_DEBUG_MSG1(this, "Send %d bytes of data", n);
		i++;
	}
	if (fileSize == 0) 
	{
		recType = DIME_MESSAGE_START_END;
		ret = this->sendFileData(clientId, buffer, 0, recType, localFile);
	}
	//TPTP_LOG_DEBUG_MSG1(this, "Out of file reading and sending block - %d", count);	
	fclose(fp);

	return 0;
}

tptp_int32 FileTransferAgent::createAndGetFile(tptp_int32 clientId, char *localFile, char* remoteFile)
{
	tptp_int32 ret = 0;
	waitForFileComplete(remoteFile);

	return ret;
}

tptp_int32 FileTransferAgent::processGetFileRequest(CmdBlock* cmdBlock)
{
	tptp_int32 ret = ERROR;

	const tptp_int32 sourceID	 = cmdBlock->getSourceID();
	const tptp_list_t* paramList  = cmdBlock->getParamList();

	char *localFile;
	char *remoteFile;

	if (paramList->count != 2) {
		TPTP_LOG_ERROR_MSG1(this, "FileTransferAgent getFile params= %d", paramList->count);
		return ret;
	} else {
		if (0 != getStringParam("localFile", paramList, &localFile)) {
			TPTP_LOG_ERROR_MSG(this, "FileTransferAgent getFile local file name error ");
			return ret;
		}
		if (0 != getStringParam("remoteFile", paramList, &remoteFile)) {
			TPTP_LOG_ERROR_MSG(this, "FileTransferAgent getFile remote file name error ");
			return ret;
		}
	}

 	TID pthreadId;
	HANDLE pthreadHandle;
	ThreadPInfo *pInfo= (ThreadPInfo *) tptp_malloc(sizeof(ThreadPInfo));

	pInfo->clientId = sourceID;
	pInfo->localFile = localFile;
	pInfo->remoteFile = remoteFile;
	pInfo->pAgent = this;

 	ret= tptpStartThread(getFileThread, (void *) pInfo, &pthreadId, &pthreadHandle);

	if (ret == -1) {
		TPTP_LOG_ERROR_MSG(this, "FileTransferAgent -- error with open and send ");
		return ERROR;
	}

	return ret;
}

tptp_int32 FileTransferAgent::MapInterfaceNameToID(char* interfaceName)
{
	if (isEqualString(interfaceName, "org.eclipse.tptp.FileTransferAgent"))
	{
		return IID_FILE_TRANSFER_AGENT;
	}

	return UNKNOWN_INTERFACE;
}


tptp_int32 FileTransferAgent::MapCommandNameToID(char* cmdName)
{
	if (isEqualString(cmdName, "getFile"))
	{
		return GET_FILE_CMD;
	}
	else if (isEqualString(cmdName, "putFile"))
	{
		return PUT_FILE_CMD;
	}
	else if (isEqualString(cmdName, "deleteFile"))
	{
		return DELETE_FILE_CMD;
	}

	return UNKNOWN_CMD;
}

tptp_int32 FileTransferAgent::processCommand(CmdBlock* cmdBlock)
{

	tptp_int32 ret = 0;

	ret = BaseCollectorImpl::processCommand(cmdBlock);

	if (ret == 0) return ret;

	char* cmdName = cmdBlock->getCommandName();

	if (!isEqualString(cmdBlock->getIID(), "org.eclipse.tptp.FileTransferAgent"))
	{
		return ERROR;
	}
	switch(MapInterfaceNameToID(cmdBlock->getIID()))
	{
		case IID_FILE_TRANSFER_AGENT:
			switch(MapCommandNameToID(cmdName))
			{
				case GET_FILE_CMD:
					ret = this->getFile(cmdBlock);					
					break;

				case PUT_FILE_CMD:
					ret = this->putFile(cmdBlock);					
					break;

				case DELETE_FILE_CMD:
					ret = this->deleteFile(cmdBlock);					
					break;

				default:
					ret = ERROR;
					break;
			}
			break;
	}

	return ret;
}

 tptp_int32 FileTransferAgent::findFileRecordByName(const char *fileName, tptp_file_t *fileRec)
 {
       tptp_node_t*    node;
       tptp_file_t* tmpFileRec;

       tptp_getReadLock(&fileOpListLock);
       for (node = fileOpList.head; node != 0; node = node->next )
       {
               tmpFileRec = (tptp_file_t*)node->data;
               if ( isEqualString(tmpFileRec->name, fileName))
               {
                       fileRec->fp = tmpFileRec->fp;
                       fileRec->semaphore = tmpFileRec->semaphore;
                       tptp_releaseReadLock(&fileOpListLock);
                       return 0;
               }
       }
       tptp_releaseReadLock(&fileOpListLock);
       return -1;
 }

 /* This is used for remove so it does not lock! */
 tptp_file_t* FileTransferAgent::findFileRecordByName(const char *fileName)
 {
       tptp_node_t*    node;
       tptp_file_t* tmpFileRec;

       for (node = fileOpList.head; node != 0; node = node->next )
       {
               tmpFileRec = (tptp_file_t*)node->data;
               if ( isEqualString(tmpFileRec->name, fileName))
               {
                       return tmpFileRec;
               }
       }
       return NULL;
 }


 tptp_int32 FileTransferAgent::receiveData(tptp_int32 sourceID, char buffer[], tptp_int32 bytesRead, DIME_HEADER_PTR_T dimeHeader)
 {
       tptp_int32 rc;
       char* fileName;
       tptp_file_t fileRec;
       tptp_int32 dime_length;
	size_t hi = 0;

       if (!is_valid_header(dimeHeader, bytesRead+dimeHeader->id_length+sizeof(DIME_HEADER_T))) {
		return 0;
       }
       fileName = GET_DIME_ID(dimeHeader);
       dime_length = GET_DIME_LENGTH(dimeHeader);

	#ifdef MVS
		__atoe(fileName); // Convert the filename passed in the dime header
	#endif

       rc = findFileRecordByName(fileName, &fileRec);
       /* It's possible that the data has gotten here before the file could
        * be initialized... let the main thread run */
       while (rc == -1) {
               Sleep(1);
               rc = findFileRecordByName(fileName, &fileRec);
       }
       if (rc != -1) {

		hi = fwrite((void *) buffer, 1, bytesRead, fileRec.fp);
		fflush( fileRec.fp );
		//fclose( fileRec.fp );

		/* Post the semaphore when we get to the last record */
               if (IS_DIME_END_RECORD(dimeHeader)) {
                       tptp_postSemaphore(fileRec.semaphore);
               }
       } else {
               TPTP_LOG_ERROR_MSG1(this, "Can't find filename: %s", fileName);
       }

       return 0;
 }


 void FileTransferAgent::initializeFileTransfer(char *fileName, FILE *fp)
 {
       tptp_file_t* fileRec;
       fileRec = (tptp_file_t *) tptp_malloc(sizeof(tptp_file_t));

       fileRec->name = fileName;
       fileRec->fp   = fp;
      fileRec->semaphore = (Semaphore_t *) tptp_malloc(sizeof(Semaphore_t));

       tptp_int32 rc = tptp_initializeSemaphore(fileRec->semaphore);
       if (rc != 0) {
               TPTP_LOG_ERROR_MSG(this, "FileTransferAgent semaphore initialize error.");
      }

       tptp_getWriteLock(&fileOpListLock);
       tptp_list_add(&fileOpList, fileRec);
       tptp_releaseWriteLock(&fileOpListLock);
 }

 void FileTransferAgent::waitForFileComplete(char *fileName)
 {

       tptp_file_t fileRec;
       tptp_int32 rc;

       rc = findFileRecordByName(fileName, &fileRec);

       if (rc != -1) {
               rc = tptp_waitSemaphore(fileRec.semaphore);
               tptp_deleteSemaphore(fileRec.semaphore);
               tptp_getWriteLock(&fileOpListLock);
               fclose(fileRec.fp);
               tptp_list_remove(&fileOpList, findFileRecordByName(fileName));
               tptp_releaseWriteLock(&fileOpListLock);
       }
 }

int main(tptp_int32 argc, char* argv[])
{

	char* agentName = "org.eclipse.tptp.FileTransferAgent";
	FileTransferAgent* FileTransferAgentI 
		= new FileTransferAgent(agentName);

	FileTransferAgentI->processCommandLine(argc, argv);
	
	FileTransferAgentI->registerAgent();

	//Wait for the terminate message.
	FileTransferAgentI->waitForTermination();
	
	return 0;
}
