/*******************************************************************************
 * Copyright (c) 2005, 2009 IBM, 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:
 *    Samson Wai, IBM - Initial implementation
 *    Andy Kaylor, Intel - Refactored code for shared library
 *
 * $Id: TPTPCBE.cpp,v 1.12 2009/08/26 14:59:53 jwest Exp $ 
 ******************************************************************************/ 

#include "tptp/TPTPOSCalls.h"
#include "tptp/TPTPLogUtils.h"
#include "TPTPLogUtilsInternal.h"

static char formattedLocalIPAddress[CBE_HOSTNAME_SIZE+1] = "\0";

/* Resolved functions */
static cbeConstructor_t cbeConstructor;
static cbeDestructor_t cbeDestructor;
static cbeCompIdConstructor_t cbeCompIdConstructor;
static cbeSituationTypeConstructor_t cbeSituationTypeConstructor;
static cbeSituationConstructor_t cbeSituationConstructor;
static cbeGenerateGuid_t cbeGenerateGuid;
static cbeGetCurrentTime_t cbeGetCurrentTime;
static cbeToXML_t cbeToXML;

static DLL_REFERENCE cbeLibrary = NULL;
static bool cbeFunctionsLoaded = false;

BOOL loadCBEFunctions()
{
    /* Try to load the CBE native library */
    if (!cbeLibrary) 
    {
		#ifdef MVS
			cbeLibrary = (dllhandle_tag *)LOAD_LIBRARY(CBE_LIBRARY_NAME);
		#else
			cbeLibrary = LOAD_LIBRARY(CBE_LIBRARY_NAME);
		#endif
        cbeConstructor = (cbeConstructor_t)RESOLVE_ENTRY_POINT(cbeLibrary, FUNC_CBE_CTOR);
        cbeDestructor = (cbeDestructor_t)RESOLVE_ENTRY_POINT(cbeLibrary, FUNC_CBE_DTOR);
        cbeCompIdConstructor = (cbeCompIdConstructor_t)RESOLVE_ENTRY_POINT(cbeLibrary, FUNC_CBE_SRC_COMP_CTOR);
        cbeSituationTypeConstructor = (cbeSituationTypeConstructor_t)RESOLVE_ENTRY_POINT(cbeLibrary, FUNC_CBE_SITUATION_TYPE_CTOR);
        cbeSituationConstructor = (cbeSituationConstructor_t)RESOLVE_ENTRY_POINT(cbeLibrary, FUNC_CBE_SITUATION_CTOR);
        cbeGenerateGuid = (cbeGenerateGuid_t)RESOLVE_ENTRY_POINT(cbeLibrary, FUNC_CBE_GENERATE_GUID);
        cbeGetCurrentTime = (cbeGetCurrentTime_t)RESOLVE_ENTRY_POINT(cbeLibrary, FUNC_CBE_GET_CURRENT_TIME);
        cbeToXML = (cbeToXML_t)RESOLVE_ENTRY_POINT(cbeLibrary, FUNC_CBE_TO_XML);
	}

    /* Check to make sure we found everything */
    if ( cbeConstructor && 
		 cbeDestructor &&
	     cbeCompIdConstructor && 
	     cbeSituationTypeConstructor && 
	     cbeSituationConstructor && 
	     cbeGenerateGuid && 
	     cbeGetCurrentTime && 
	     cbeToXML ) 
    {
		cbeFunctionsLoaded = true;
         return TRUE;
    }
    else
    {
    	
    	return FALSE;
    }
}

static tptp_int32 createBaseCBE( tptp_int32             severity,
                                 tptp_string*           event,
                                 hcbe_CommonBaseEvent** pcommonBaseEvent )
{
	hcbe_CommonBaseEvent* commonBaseEvent;
	char*  guid;
	char*  creationTime;
	
	/* The CBE library stores these pointers rather than copying the strings, so
	   we'll allocate some memory here and free it when we're done with the CBE  */
	guid = (char *)tptp_malloc(CBE_GUIDSTR_SIZE);
	if ( guid == NULL )
	{
		return -1;
	}
	creationTime = (char *)tptp_malloc(CBE_TIMESTR_SIZE);
	if ( creationTime == NULL )
	{
		tptp_free( guid );
		return -1;
	}

	/* create the cbe */
	commonBaseEvent = (hcbe_CommonBaseEvent*)(*cbeConstructor)();

	/* generate the guid */
	(*cbeGenerateGuid)(guid);
	commonBaseEvent->fpRoot->setGlobalInstanceId(commonBaseEvent, guid);

	/* generate the creation time */
	(*cbeGetCurrentTime)(creationTime);
	commonBaseEvent->fpRoot->setCreationTime(commonBaseEvent, creationTime);

	/* set the severity */
	commonBaseEvent->fpRoot->setSeverity(commonBaseEvent, severity);

	/* Make sure the message string doesn't exceed CBEs defined maximum size */
	if ( strlen(event) > CBE_MAX_MSG_SIZE )
	{
		event[CBE_MAX_MSG_SIZE] = '\0';
	}

	/* insert the message */
	commonBaseEvent->fpRoot->setMsg(commonBaseEvent, event);  // TODO: Convert event to char *

	*pcommonBaseEvent = commonBaseEvent;

	return 0;
}

static tptp_int32 createSourceComponentID( tptp_string*                   component,
                                           tptp_string*                   subcomponent,
                                           tptp_uint32                    instanceId,
                                           hcbe_ComponentIdentification** psourceComponentIdentification )
{
	hcbe_ComponentIdentification* sourceComponentIdentification;

	char*  processIDStr;
	char*  threadIDStr;
	char*  instanceIDStr;

	/* The CBE library stores these pointers rather than copying the strings, so
	   we'll allocate some memory here and free it when we're done with the CBE  */
	processIDStr = (char*)tptp_malloc(MAX_PIDSTR_SIZE+1);
	if ( processIDStr == NULL )
	{
		return -1;
	}
	threadIDStr = (char*)tptp_malloc(MAX_TIDSTR_SIZE+1);
	if ( threadIDStr == NULL )
	{
		tptp_free( processIDStr );
		return -1;
	}
	instanceIDStr = (char*)tptp_malloc(MAX_INSTIDSTR_SIZE+1);
	if ( instanceIDStr == NULL )
	{
		tptp_free( processIDStr );
		tptp_free( threadIDStr );
		return -1;
	}

	/* populate the source component structure */
	sourceComponentIdentification = (hcbe_ComponentIdentification*)(*cbeCompIdConstructor)();

	if ( formattedLocalIPAddress[0] == '\0' ) 
	{
		char hostname[CBE_HOSTNAME_SIZE+1];

		// TODO: Make WSAStartup call on Windows
	
		if ( gethostname(hostname, CBE_HOSTNAME_SIZE) == 0 ) 
		{
			sprintf(formattedLocalIPAddress, "%s", hostname);
		}
		else 
		{
			sprintf(formattedLocalIPAddress, "localhost");
		}
	}

	/* Resolve the process ID: */
	sprintf(processIDStr, "%lu", (unsigned long)getCurrentlyRunningProcessId());

	/* Resolve the thread ID: */
	sprintf(threadIDStr, "%lu", (unsigned long)getCurrentlyRunningThreadId());

	/* Format the instance ID */
	sprintf(instanceIDStr, "%u", (unsigned int)instanceId);

	sourceComponentIdentification->fpRoot->setLocationType(sourceComponentIdentification, "IPV4");
	sourceComponentIdentification->fpRoot->setLocation(sourceComponentIdentification, formattedLocalIPAddress);
	sourceComponentIdentification->fpRoot->setComponent(sourceComponentIdentification, component);
	sourceComponentIdentification->fpRoot->setSubComponent(sourceComponentIdentification, subcomponent);
	sourceComponentIdentification->fpRoot->setComponentIdType(sourceComponentIdentification, "TPTPComponent");
	sourceComponentIdentification->fpRoot->setComponentType(sourceComponentIdentification, "Eclipse_TPTP");
	sourceComponentIdentification->fpRoot->setProcessId(sourceComponentIdentification, processIDStr);
	sourceComponentIdentification->fpRoot->setThreadId(sourceComponentIdentification, threadIDStr);
	sourceComponentIdentification->fpRoot->setInstanceId(sourceComponentIdentification, instanceIDStr);

	*psourceComponentIdentification = sourceComponentIdentification;

	return 0;
}

static tptp_int32 writeCBEString( hcbe_CommonBaseEvent* commonBaseEvent,
                                  tptp_string**         cbeStr )
{
	char *eventString;
	char  buffer[MAX_CBE_BUFFER];
	int   bufferSize = MAX_CBE_BUFFER;

	/* Serialize the CBE */
	eventString = buffer;
	while ((*cbeToXML)(commonBaseEvent, eventString, bufferSize, 1) < 0) 
	{
		bufferSize += 1024; /* Message too long, increment by 1K bytes */

		if (eventString != buffer)
		{ /* Make sure we are not freeing up the constant 8K array */
			tptp_free(eventString);
		}

		eventString = ((char*)(tptp_malloc(bufferSize)));
		
		if ( eventString == NULL )
		{
			return TPTP_ERROR_OUTOFMEMORY;
		}
	}
	
	// TODO: Convert char string to tptp_string
	//       The reason I'm doing a new allocation here is the expectation that
	//       soon tptp_string won't be a char* and a conversion will be needed.
	*cbeStr = (char*)tptp_malloc(bufferSize);
	strcpy( *cbeStr, eventString );

	// If we allocated a temporary buffer, free it now
	if (eventString != buffer)
	{ /* Make sure we are not freeing up the constant 8K array */
		tptp_free(eventString);
	}
	
	return 0;
}

static void cleanupCBE(hcbe_CommonBaseEvent* commonBaseEvent)
{
	hcbe_ComponentIdentification* sourceComponentIdentification;

	char*  guid;
	char*  creationTime;
	char*  processIDStr;
	char*  threadIDStr;
	char*  instanceIDStr;

	/* We allocated these strings earlier.  Now we need to free them */
	guid = commonBaseEvent->fpRoot->getGlobalInstanceId(commonBaseEvent);
	creationTime = commonBaseEvent->fpRoot->getCreationTime(commonBaseEvent);

	commonBaseEvent->fpRoot->setGlobalInstanceId(commonBaseEvent, NULL);
	commonBaseEvent->fpRoot->setCreationTime(commonBaseEvent, NULL);

	tptp_free(guid);
	tptp_free(creationTime);
	
	sourceComponentIdentification = commonBaseEvent->fpRoot->getSourceComponentIdentification(commonBaseEvent);

	processIDStr  = sourceComponentIdentification->fpRoot->getProcessId(sourceComponentIdentification);
	threadIDStr   = sourceComponentIdentification->fpRoot->getThreadId(sourceComponentIdentification);
	instanceIDStr = sourceComponentIdentification->fpRoot->getInstanceId(sourceComponentIdentification);

	sourceComponentIdentification->fpRoot->setProcessId(sourceComponentIdentification, NULL);
	sourceComponentIdentification->fpRoot->setThreadId(sourceComponentIdentification, NULL);
	sourceComponentIdentification->fpRoot->setInstanceId(sourceComponentIdentification, NULL);

	tptp_free(processIDStr);
	tptp_free(threadIDStr);
	tptp_free(instanceIDStr);

	/* This destructor cleans up the sub-elements */
	*cbeDestructor(commonBaseEvent);
}

/* Extract a local function format_exec_env_string from original
 * code of the function tptp_createLogCBE for better modularity.
 * This function is only used by function tptp_createLogCBE.
 */
static void format_exec_env_string(char exeEnvStr[], tptp_string* pfilename,
		int lineNum)
{
	const size_t len_max_line_num = 10; // max integer 0x7FFFFFFF=2147483647
	char lineNumstr[len_max_line_num + 1] = {'\0'};
	int radix = 10;

    lineNum = (lineNum < 0) ? 0 : lineNum; // negative line number is illegal
    sprintf(lineNumstr, "%d", lineNum);

    const char* hint_str = ", line ";

    // For original code, make sure the formatted string fits size limits
    char filename[MAX_EXECENV_SIZE] = {'\0'};

	// add for bug 143547, see the descriptions on Eclipse bugzilla
	// Fix solution: split the path string after the last src token
	// Precondition: src token should be contained in filename string
	if (NULL != pfilename)
	{
	    #ifdef _WIN32
	    const char* src_token = "\\src\\";
	    #else
	    const char* src_token = "/src/";
	    #endif

        const size_t len_src_token = strlen(src_token);

	    // search the path string after the last src token
	    char* ptempfilename = strstr(pfilename, src_token);
	    while ( NULL != ptempfilename )
	    {
	    	pfilename = ptempfilename + len_src_token;
	    	ptempfilename = strstr(pfilename, src_token);
	    }

		// Make sure the formatted string fits size limits
	    strncpy(filename, pfilename, (MAX_EXECENV_SIZE - strlen(lineNumstr) - strlen(hint_str)));
	}
	// add for bug 143547, see the descriptions on Eclipse bugzilla

	sprintf(exeEnvStr, "%s%s%d", filename, hint_str, lineNum);
}

tptp_int32 tptp_createLogCBE( tptp_string*  component,
                              tptp_string*  subcomponent,
                              tptp_uint32   instanceId,
                              tptp_string*  filename, 
                              tptp_int32    lineNum, 
                              tptp_int32    severity,
                              tptp_string*  event,
                              tptp_string** cbe )
{
	hcbe_CommonBaseEvent*         commonBaseEvent;
	hcbe_ComponentIdentification* sourceComponentIdentification;
	int                           error;
	tptp_int32                    ret;
	char                          exeEnvStr[MAX_EXECENV_SIZE+1];

	/* If we haven't already loaded the CBE library, do it now */
	if ( !cbeFunctionsLoaded )
	{
		if ( ! loadCBEFunctions() )
		{
			return TPTP_ERROR_CBE_LIBRARY_UNAVAILABLE;
		}
	}
	
	ret = createBaseCBE( severity, event, &commonBaseEvent );
	if ( ret != 0 )
	{
		return ret;
	}

	ret = createSourceComponentID( component, subcomponent, instanceId, &sourceComponentIdentification );
	if ( ret != 0 )
	{
		/* TODO: Release the CBE record? */
		return ret;
	}

	/* populate the situation type */
	hcbe_SituationType* situationType = (hcbe_SituationType*)(*cbeSituationTypeConstructor)(HCBE_REPORT_SITUATION, &error);
	situationType->fpRoot->setReasoningScope(situationType, "INTERNAL");
	situationType->fpRoot->setReportCategory(situationType, "LOG");

	/* populate the situation */
	hcbe_Situation* situation = (hcbe_Situation*)(*cbeSituationConstructor)();
	situation->fpRoot->setCategoryName(situation, "ReportSituation");
	situation->fpRoot->setSituationType(situation, situationType);

	/* add the situation to the cbe */
	commonBaseEvent->fpRoot->setSituation(commonBaseEvent, situation);		


	/* Format the execution environment string */
	format_exec_env_string(exeEnvStr, filename, lineNum);

	sourceComponentIdentification->fpRoot->setExecutionEnvironment(sourceComponentIdentification, exeEnvStr);

	/* add the source component to the cbe */
	commonBaseEvent->fpRoot->setSourceComponentIdentification(commonBaseEvent,sourceComponentIdentification);


	/* Write the CBE to a string */
	ret = writeCBEString( commonBaseEvent, cbe );
	
	/* Free our CBE memory */
	cleanupCBE(commonBaseEvent);

	return 0;
}


tptp_int32 tptp_createErrorCmdCBE( tptp_string*  component,
                                   tptp_string*  subcomponent,
                                   tptp_uint32   instanceId,
                                   tptp_int32    severity,
                                   tptp_string*  event,
                                   tptp_string** cbe )
{
	hcbe_CommonBaseEvent*         commonBaseEvent;
	hcbe_ComponentIdentification* sourceComponentIdentification;
	int                           error;
	tptp_int32                    ret;

	/* If we haven't already loaded the CBE library, do it now */
	if ( !cbeFunctionsLoaded )
	{
		if ( ! loadCBEFunctions() )
		{
			return TPTP_ERROR_CBE_LIBRARY_UNAVAILABLE;
		}
	}
	
	ret = createBaseCBE( severity, event, &commonBaseEvent );
	if ( ret != 0 )
	{
		return ret;
	}

	ret = createSourceComponentID( component, subcomponent, instanceId, &sourceComponentIdentification );
	if ( ret != 0 )
	{
		/* TODO: Release the CBE record? */
		return ret;
	}

	/* add the source component to the cbe */
	commonBaseEvent->fpRoot->setSourceComponentIdentification(commonBaseEvent,sourceComponentIdentification);

	/* populate the situation type */
	hcbe_SituationType* situationType = (hcbe_SituationType*)(*cbeSituationTypeConstructor)(HCBE_REPORT_SITUATION, &error);
	situationType->fpRoot->setReasoningScope(situationType, "EXTERNAL");
	situationType->fpRoot->setSuccessDisposition(situationType, "UNSUCCESSFUL");

	/* populate the situation */
	hcbe_Situation* situation = (hcbe_Situation*)(*cbeSituationConstructor)();
	situation->fpRoot->setCategoryName(situation, "RequestSituation");
	situation->fpRoot->setSituationType(situation, situationType);

	/* add the situation to the cbe */
	commonBaseEvent->fpRoot->setSituation(commonBaseEvent, situation);		


	/* add the source component to the cbe */
	commonBaseEvent->fpRoot->setSourceComponentIdentification(commonBaseEvent,sourceComponentIdentification);

	/* add the situation to the cbe */
	commonBaseEvent->fpRoot->setSituation(commonBaseEvent, situation);		

	ret = writeCBEString( commonBaseEvent, cbe );

	/* Free our CBE memory */
	cleanupCBE(commonBaseEvent);

	return 0;
}

int countExpansionChars( tptp_string* msg )
{
	int count = 0;
	int i;
	int len = strlen(msg);

	for ( i = 0; i < len; i++ )
	{
		if ( (msg[i] == '<' ) ||
		     (msg[i] == '>' ) ||
			 (msg[i] == '&' ) ||
			 (msg[i] == '"' ) ||
			 (msg[i] == '\'') ||
			 (msg[i] == '\t') ||
			 (msg[i] == '\n') ||
			 (msg[i] == '\r') )
		{
			count++;
		}
	}

	return count;

}

void normalize( char* out, char* in )
{
	int  len = strlen(in);
	int  i;

	out[0] = '\0';

	for ( i = 0; i < len; i++ )
	{
		if ( in[i] == '<' )
		{
			strcat( out, "&lt;" );
		}
		else if ( in[i] == '>' )
		{
			strcat( out, "&gt;" );
		}
		else if ( in[i] == '&' )
		{
			strcat( out, "&amp;" );
		}
		else if ( in[i] == '"' )
		{
			strcat( out, "&quot;" );
		}
		else if ( in[i] == '\'' )
		{
			strcat( out, "&apos;" );
		}
		else if ( in[i] == '\t' )
		{
			strcat( out, "&#x9;" );
		}
		else if ( in[i] == '\n' )
		{
			strcat( out, "&#xA;" );
		}
		else if ( in[i] == '\r' )
		{
			strcat( out, "&#xD;" );
		}
		else
		{
			char  ch[2];

			ch[0] = in[i];
			ch[1] = '\0';

			strcat( out, ch );
		}
	}
}

tptp_int32 tptp_normalizeLogMsg( tptp_string*  msg,
                                 tptp_string** normalizedMsg )
{
	int   normalizedSize;
	int   charsToBeExpanded;

	if ( (msg == 0) || (normalizedMsg == 0) )
	{
		return TPTP_CMD_BAD_ARG;
	}

	charsToBeExpanded = countExpansionChars( msg );

	normalizedSize = strlen(msg) + (charsToBeExpanded*5);

	(*normalizedMsg) = (char*)tptp_malloc(normalizedSize+1);
	if ( ! *normalizedMsg )
	{
		return TPTP_SYS_NO_MEM;
	}

	normalize( *normalizedMsg, msg );

	return 0;
}
