#include "STAFServiceInterface.h"
#include "STAFIncludes.h"
#include "synchelper.h"
#include "perlglue.h"

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

typedef struct STAFProcPerlServiceDataST {
	PHolder *perl;
	STAFMutexSem_t mutex;
	SyncData *syncData;
} STAFProcPerlServiceData;

STAFRC_t ParseParameters(STAFServiceInfoLevel30 *pInfo, PHolder *perl_holder, unsigned int *maxLogs, long *maxLogSize, STAFString_t *pErrorBuffer);
STAFRC_t ReplaceChar(STAFString_t opr_str, char old, char newc);

STAFRC_t STAFServiceConstruct(STAFServiceHandle_t *pServiceHandle,
							  void *pServiceInfo, unsigned int infoLevel,
							  STAFString_t *pErrorBuffer)
{
	if (infoLevel != 30) return kSTAFInvalidAPILevel;
	
	STAFServiceInfoLevel30 *pInfo = static_cast<STAFServiceInfoLevel30 *>(pServiceInfo);
	unsigned int maxLogs = 5; // Defaults to keeping 5 PerlInterpreter logs
	long maxLogSize = 1048576; // Default size of each log is 1M
	STAFRC_t ret;
	
	STAFProcPerlServiceData *pData = (STAFProcPerlServiceData*)malloc(sizeof(STAFProcPerlServiceData));
	if (pData==NULL) {
		return kSTAFUnknownError;
	}
	SyncData *syncData = CreateSyncData();
	if (NULL == syncData) {
		free(pData);
		return kSTAFUnknownError;
	}
	pData->syncData = syncData;

	PHolder *perl_data = CreatePerl(syncData);
	if (perl_data==NULL) {
		free(pData);
		DestroySyncData(syncData);
		return kSTAFUnknownError;
	}

	pData->perl = perl_data;

	PopulatePerlHolder(perl_data, pInfo->name, pInfo->exec, pInfo->serviceType);

	// Walk through and verify the config options
	ret = ParseParameters(pInfo, perl_data, &maxLogs, &maxLogSize, pErrorBuffer);
	if (ret!=kSTAFOk) return ret;

	ret = RedirectPerlStdout(perl_data, pInfo->writeLocation, pInfo->name, maxLogs, maxLogSize, pErrorBuffer);
	if (ret!=kSTAFOk) {
		free(pData);
		DestroySyncData(syncData);
		DestroyPerl(perl_data);
		return ret;
	}
	ret = PreparePerlInterpreter(perl_data, pInfo->exec, pErrorBuffer);
	if (ret!=kSTAFOk) {
		free(pData);
		DestroySyncData(syncData);
		DestroyPerl(perl_data);
		return ret;
	}

	ret = STAFMutexSemConstruct(&(pData->mutex), NULL, NULL);
	if (ret!=kSTAFOk) {
		free(pData);
		DestroySyncData(syncData);
		Terminate(perl_data);
		DestroyPerl(perl_data);
		return ret;
	}
	*pServiceHandle=pData;
	return kSTAFOk;
}

STAFRC_t STAFServiceDestruct(STAFServiceHandle_t *serviceHandle,
							 void *pDestructInfo,
							 unsigned int destructLevel,
							 STAFString_t *pErrorBuffer)
{
	if (destructLevel != 0) return kSTAFInvalidAPILevel;
	
    STAFProcPerlServiceData *pData =
		static_cast<STAFProcPerlServiceData *>(*serviceHandle);
	
	STAFMutexSemDestruct(&(pData->mutex), NULL);
	DestroySyncData(pData->syncData);
	DestroyPerl(pData->perl);
	free(pData);

    *serviceHandle = 0;
	
    return kSTAFOk;
}

STAFRC_t STAFServiceInit(STAFServiceHandle_t serviceHandle,
						 void *pInitInfo, unsigned int initLevel,
						 STAFString_t *pErrorBuffer)
{
	if (initLevel != 30) return kSTAFInvalidAPILevel;
	
    STAFProcPerlServiceData *pData =
		static_cast<STAFProcPerlServiceData *>(serviceHandle);
	STAFServiceInitLevel30 *pInfo =
		static_cast<STAFServiceInitLevel30 *>(pInitInfo);

	STAFRC_t ret = STAFMutexSemRequest(pData->mutex, -1, NULL);
	if (ret!=kSTAFOk)
		return ret;

	ret = InitService(pData->perl, pInfo->parms, pInfo->writeLocation, pErrorBuffer);
	
	if (STAFMutexSemRelease(pData->mutex, NULL) != kSTAFOk) {
		const char *msg = "Error in ServiceInit: could not aquire lock!";
		//fprintf(stderr, "%s\n", msg);
		STAFStringConstruct(pErrorBuffer, msg, strlen(msg), NULL);
		return kSTAFUnknownError;
	}

	return ret;
}

STAFRC_t STAFServiceTerm(STAFServiceHandle_t serviceHandle,
						 void *pTermInfo, unsigned int termLevel,
						 STAFString_t *pErrorBuffer)
{
	if (termLevel != 0) return kSTAFInvalidAPILevel;
	
    STAFProcPerlServiceData *pData =
		static_cast<STAFProcPerlServiceData *>(serviceHandle);

	STAFRC_t ret = STAFMutexSemRequest(pData->mutex, -1, NULL);
	if (ret!=kSTAFOk)
		return ret;

	ret = Terminate(pData->perl);
	
	if (STAFMutexSemRelease(pData->mutex, NULL) != kSTAFOk) {
		const char *msg = "Error in ServiceTerm: could not aquire lock!";
		//fprintf(stderr, "%s\n", msg);
		STAFStringConstruct(pErrorBuffer, msg, strlen(msg), NULL);
		return kSTAFUnknownError;
	}

	return ret;
}

STAFRC_t STAFServiceAcceptRequest(STAFServiceHandle_t serviceHandle,
								  void *pRequestInfo, unsigned int reqLevel,
								  STAFString_t *pResultBuffer)
{
	if (reqLevel != 30) return kSTAFInvalidAPILevel;

    STAFProcPerlServiceData *pData = static_cast<STAFProcPerlServiceData *>(serviceHandle);
	STAFServiceRequestLevel30 *pInfo = static_cast<STAFServiceRequestLevel30 *>(pRequestInfo);

	SingleSync *ss = GetSingleSync(pData->syncData, pInfo->requestNumber);
	if ( NULL == ss ) {
		const char *msg = "Error in AcceptRequest: received NULL as sync record";
		STAFStringConstruct(pResultBuffer, msg, strlen(msg), NULL);
		return kSTAFUnknownError;
	}
	
	STAFRC_t ret = STAFMutexSemRequest(pData->mutex, -1, NULL);
	if (ret!=kSTAFOk) {
		const char *msg = "Error in AcceptRequest: Failed to get mutex";
		STAFStringConstruct(pResultBuffer, msg, strlen(msg), NULL);
		return ret;
	}

	ret = ServeRequest(pData->perl, pInfo, pResultBuffer);

	if (STAFMutexSemRelease(pData->mutex, NULL) != kSTAFOk) {
		const char *msg = "Error in AcceptRequest: could not release lock!";
		STAFStringConstruct(pResultBuffer, msg, strlen(msg), NULL);
		ret = kSTAFUnknownError;
	}

	if (77==ret && NULL == *pResultBuffer) {
		// this is a delayed answer. wait for the real answer
		ret = WaitForSingleSync(ss, pResultBuffer);
	}
	ReleaseSingleSync(pData->syncData, ss);
	return ret;
}


STAFRC_t STAFServiceGetLevelBounds(unsigned int levelID,
                                   unsigned int *minimum,
                                   unsigned int *maximum)
{
    switch (levelID)
    {
        case kServiceInfo:
        {
            *minimum = 30;
            *maximum = 30;
            break;
        }
        case kServiceInit:
        {
            *minimum = 30;
            *maximum = 30;
            break;
        }
        case kServiceAcceptRequest:
        {
            *minimum = 30;
            *maximum = 30;
            break;
        }
        case kServiceTerm:
        case kServiceDestruct:
        {
            *minimum = 0;
            *maximum = 0;
            break;
        }
        default:
        {
            return kSTAFInvalidAPILevel;
        }
    }

    return kSTAFOk;
}

// Helper Functions

int STAFStringCompare(STAFString_t str1, STAFString_t str2) {
	unsigned int result;
	STAFStringIsEqualTo(str1, str2, kSTAFStringCaseInsensitive, &result, NULL);
	if (result==0)
		return 1;
	return 0;
}

STAFRC_t ParseParameters(STAFServiceInfoLevel30 *pInfo, PHolder *perl_holder, unsigned int *maxLogs, long *maxLogSize, STAFString_t *pErrorBuffer) {

	STAFString_t MaxLogsSizeString;
	STAFString_t MaxLogsString;
	STAFString_t UseLibString;
	STAFRC_t ret = kSTAFOk;

	STAFStringConstruct(&MaxLogsSizeString, "MAXLOGSIZE", 10, NULL);
	STAFStringConstruct(&MaxLogsString, "MAXLOGS", 7, NULL);
	STAFStringConstruct(&UseLibString, "USELIB", 6, NULL);

    for (unsigned int i = 0; i < pInfo->numOptions; ++i)
    {
		STAFString_t optionValue = pInfo->pOptionValue[i];
		STAFString_t upperOptionName = pInfo->pOptionName[i];

        if (STAFStringCompare(upperOptionName, MaxLogsString)==0)
        {
            // Check to make sure it is an integer value > 0
			STAFRC_t ret1 = STAFStringToUInt(optionValue, maxLogs, 10, NULL);
			if (ret1 != kSTAFOk) {
				const char *msg = "Error: MAXLOGS value is incorrect!";
				STAFStringConstruct(pErrorBuffer, msg, strlen(msg), NULL);
				ret = kSTAFServiceConfigurationError;
				break;
			}

        }
        else if (STAFStringCompare(upperOptionName, MaxLogsSizeString)==0)
        {
            // Check to make sure it is an integer value > 0
			unsigned int maxLogSizeTmp;
			STAFRC_t ret1 = STAFStringToUInt(optionValue, &maxLogSizeTmp, 10, NULL);
			if (ret1 != kSTAFOk) {
				const char *msg = "Error: MAXLOGSIZE value is incorrect!";
				STAFStringConstruct(pErrorBuffer, msg, strlen(msg), NULL);
				ret = kSTAFServiceConfigurationError;
				break;
			} else
				*maxLogSize = maxLogSizeTmp;
		}
        else if (STAFStringCompare(upperOptionName, UseLibString)==0)
        {
			perl_uselib(perl_holder, optionValue);
        }
        else
        {
			STAFStringConstructCopy(pErrorBuffer, upperOptionName, NULL);
            ret = kSTAFServiceConfigurationError;
			break;
        }
	}
	STAFStringDestruct(&MaxLogsSizeString , NULL);
	STAFStringDestruct(&MaxLogsString , NULL);
	STAFStringDestruct(&UseLibString , NULL);
    return ret;
}

STAFRC_t ReplaceChar(STAFString_t opr_str, char old, char newc) {
	STAFString_t old_t, newc_t;
	char tmp[2];
	tmp[1] = 0;
	tmp[0] = old;
	STAFStringConstruct(&old_t, tmp, 1, NULL);
	tmp[0] = newc;
	STAFStringConstruct(&newc_t, tmp, 1, NULL);
	STAFRC_t ret = STAFStringReplace(opr_str, old_t, newc_t, NULL);
	STAFStringDestruct(&old_t, NULL);
	STAFStringDestruct(&newc_t, NULL);
	return ret;
}