/*******************************************************************************
 * (c) Copyright 2015 Microsemi Corporation.  All rights reserved.
 *
 *
 *
 * SVN $Revision: $
 * SVN $Date: $
 */

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


#include "egl_types.h"
#include "osal.h"

#include "../inc/egl_log.h"
#include "egl_assert.h"

/* ----------------- Defines ---------------------- */
/*
 * Return the dimension of an array: use this for anything that needs to know this in
 * preference to repeating the value used in the definition.
 *
 * Thus:
 *
 * int table[MAX_TABLE];
 *
 * for (i = 0; i < DIM(table); i++)
 *
 */

#if !defined(DIM)
#define DIM(array)   (sizeof (array)/sizeof ((array)[0]))
#endif

#ifndef FREERTOS
#include "syslog.h"
#endif

//VRextern void nc_verb_verbose(const char * format, ...);
//VRextern void nc_verb_warning(const char * format, ...);
//VRextern void nc_verb_error(const char * format, ...);
//unused declarations

#define LOG_MAX_ID_LEN         (16)
#define LOG_MAX_SOURCES        (32)
#define LOG_MAX_MESSAGE_SIZE   (256)

/* ----------------- Structs/Types ---------------------- */

typedef struct
{
    log_level_t level;
    char        name[LOG_MAX_ID_LEN];
} log_source_t;

/* ----------------- Static Data ---------------------- */

/* These constitute the logging database */
static uint16_t     log_nextFreeIdx = 0;
static log_source_t log_sources[LOG_MAX_SOURCES];

/* Mutex to protect data updates */
static osMutex_t log_dbLock = NULL;

/* Static Level description string */
static char* log_string_emergency   = "EMERGENCY";
static char* log_string_alert   	= "ALERT";
static char* log_string_critical	= "CRITICAL";
static char* log_string_error   	= "ERROR";
static char* log_string_warning		= "WARNING";
static char* log_string_notice		= "NOTICE";
static char* log_string_info    	= "INFO";
static char* log_string_debug		= "DEBUG";
static char* log_string_invalid 	= "INVALID";

/* function prototypes */

/* ----------------- Static Functions ---------------------- */

static void log_lock_db(void)
{
	// TODO: ND - remove after testing
#if 0
    /* ??? The use of pthread_mutex_lock should be replaced with an OS independent function */
    int retVal = pthread_mutex_lock(&log_dbLock);
    ASSERT(retVal == 0, retVal);
#endif
    osMutexTake(log_dbLock,0);
}



static void log_unlock_db(void)
{
	// TODO: ND - remove after testing
#if 0
    /* ??? The use of pthread_mutex_unlock should be replaced with an OS independent function */
    int retVal = pthread_mutex_unlock(&log_dbLock);
    ASSERT(retVal == 0, retVal);
#endif
	osMutexGive(log_dbLock);
}



/*
 * Convert an index to a handle.
 *
 * Indices are [0..(LOG_MAX_SOURCES-1)]
 * Handles are [1..LOG_MAX_SOURCES]
 */
static uint16_t log_idx2Handle(uint16_t idx)
{
    return (idx + 1);
}



static uint16_t log_handle2Idx(uint16_t handle)
{
    return (handle - 1);
}



static bool log_idxIsValid(uint16_t idx)
{
    return (idx < LOG_MAX_SOURCES);
}



static bool log_levelIsValid(log_level_t level)
{
    bool retVal = true;

    switch(level)
    {
        case eLOG_LEVEL_EMERGENCY ... eLOG_LEVEL_DEBUG:
        {
            break;
        }

        default:
        {
            retVal = false;
            break;
        }
    }
    return retVal;
} /* log_levelIsValid */



/*
 * Allocate the next free logging source index or assert if none available
 *
 * If this asserts it means more log modules are declared than supported.
 * Either reduce the number of modules or increase the number supported.
 *
 *\returns		\li newly allocated logging index
 */
static uint16_t log_getNextFreeIdxOrDie()
{
    uint16_t newIdx = 0;

    log_lock_db();
    newIdx = log_nextFreeIdx;
    EGL_ASSERT(log_idxIsValid(newIdx), newIdx);

    log_nextFreeIdx++;
    log_unlock_db();

    return newIdx;
}



#if REPLACED
static bool log_debugIsEnabled(const uint16_t logIdx)
{
    bool retVal = false; /* Default is 'disabled' */

    ASSERT(log_idxIsValid(logIdx), logIdx);

    switch(log_sources[logIdx].level)
    {
        case eLOG_LEVEL_DEBUG:
        {
            retVal = true;
            break;
        }
        // all others are false
        default:
		{
			break;
		}
    }

    return retVal;
} /* log_debugIsEnabled */
#endif

static bool log_levelIsEnabled(const uint16_t logIdx, log_level_t level)
{
    EGL_ASSERT(log_idxIsValid(logIdx), logIdx);

    // Note: level is an inverse scale. 0 is the 'highest' level 7 the lowest
    // Any log with level <= eLOG_LEVEL_INFO cannot be masked
    // If the level of the log_source is >= the reference level then logging is enabled
    if ((level <= eLOG_LEVEL_ERROR) || (level <= log_sources[logIdx].level))
    	return true;
    else
		return false;
} /* log_levelIsEnabled */

#if REPLACED
static bool log_infoIsEnabled(const uint16_t logIdx)
{
    bool retVal = false; /* Default is 'disabled' */

    ASSERT(log_idxIsValid(logIdx), logIdx);

    switch(log_sources[logIdx].level)
    {
        case eLOG_LEVEL_INFO ... eLOG_LEVEL_DEBUG:
        {
            retVal = true;
            break;
        }

        default:
        {
            break;
        }
    }

    return retVal;
} /* log_infoIsEnabled */
#endif


static void log_level2String(const log_level_t level, char** levelStringPtr)
{
    EGL_ASSERT(log_levelIsValid(level), level);
    ASSERT_NOT_NULL(levelStringPtr);

    switch(level)
    {
        case eLOG_LEVEL_DEBUG:
        {
            *levelStringPtr = log_string_debug;
            break;
        }
        case eLOG_LEVEL_INFO:
        {
            *levelStringPtr = log_string_info;
            break;
        }
        case eLOG_LEVEL_NOTICE:
        {
            *levelStringPtr = log_string_notice;
            break;
        }
        case eLOG_LEVEL_WARNING:
        {
            *levelStringPtr = log_string_warning;
            break;
        }
        case eLOG_LEVEL_ERROR:
        {
            *levelStringPtr = log_string_error;
            break;
        }
        case eLOG_LEVEL_CRITICAL:
        {
            *levelStringPtr = log_string_critical;
            break;
        }
        case eLOG_LEVEL_ALERT:
        {
            *levelStringPtr = log_string_alert;
            break;
        }
        case eLOG_LEVEL_EMERGENCY:
		{
			*levelStringPtr = log_string_emergency;
			break;
		}
    } /* switch */
}     /* log_getLevelStringFromIdx */



/*
 * Main logging function, this is ONLY called from within the log_error, log_info and
 * log_debug functions.
 *
 * The outer functions have ensured logging is supposed to occur and that the parameters are
 * valid.
 */
static void log_message(const uint16_t logIdx,
                        log_level_t    level,
                        const char*    file_name,
                        const char*    function_name,
                        int            line_no,
                        const char*    fmt,
                        va_list		arglist)
{
    char    buffer[LOG_MAX_MESSAGE_SIZE + 1];
    int		preamble_length;
    int		message_length;
    int		last_char;
    //va_list arglist;
    char*   short_filename;
    char*   levelStr = NULL;

    EGL_ASSERT(log_idxIsValid(logIdx), logIdx);
    EGL_ASSERT(log_levelIsValid(level), level);
    ASSERT_NOT_NULL(file_name);
    ASSERT_NOT_NULL(function_name);
    ASSERT_NOT_NULL(fmt);


    /* cut filename down to size, take from last '/' only, if there is one */
    short_filename = (char*)(file_name + strlen(file_name));

    while(short_filename > file_name && *(short_filename - 1) != '/')
    {
        short_filename--;
    }

    log_level2String(level, &levelStr);

    /* construct the string, preamble followed by user-buffer */
	if (level == eLOG_LEVEL_DEBUG)
	{
		preamble_length = snprintf(&buffer[0],
								   LOG_MAX_MESSAGE_SIZE,
								   "%s %s %s:%d:%s: ",
								   log_sources[logIdx].name, levelStr,
								   short_filename, line_no, function_name
								   );
	}
	else
	{
#ifdef FREERTOS
		preamble_length = snprintf(&buffer[0],
								   LOG_MAX_MESSAGE_SIZE,
								   "%s %s ",
								   log_sources[logIdx].name, levelStr
								   );
#else
		preamble_length = snprintf(&buffer[0],
								   LOG_MAX_MESSAGE_SIZE,
								   "%s ",
								   log_sources[logIdx].name);
#endif
	}
    if(preamble_length >= 0) /* snprintf returns negative if there was a problem */
    { /* add variable arguments */
        //va_start(arglist, fmt);
        message_length = vsnprintf(&buffer[preamble_length],
                                   (LOG_MAX_MESSAGE_SIZE - preamble_length),
                                   fmt, arglist);
       // va_end(arglist);

        if(message_length >= 0) /* vsnprintf returns negative if there was a problem */
        {
#ifdef GUI
            /* Ensure there is a trailing new-line, this is why our buffer is +1! */
            last_char = preamble_length + message_length;
            if(buffer[last_char - 1] != '\n')
            {
                buffer[last_char]     = '\n';
                buffer[last_char + 1] = '\0';
            }

	#ifdef FREERTOS
            /* ??? The use of printf is inappropriate.h it should be replaced with an OS independent print/logging function */
            ts_printf("%s", buffer);
	#else
            syslog (level, buffer);
	#endif
#else
	#if 0
            if (level > eLOG_LEVEL_WARNING)
            {
            	nc_verb_verbose("%s", buffer);
            }
            else if (level == eLOG_LEVEL_WARNING)
            {
            	nc_verb_warning("%s", buffer);
            }
            else
            {
            	nc_verb_error("%s", buffer);
            }
	#else
            //VRsyslog (level, buffer);
            //syslog unavailable in FreeRTOS
	#endif
#endif
        }
    }
} /* log_message */



/* ----------------- External Functions ---------------------- */

/*
 * See log_api.h for prototype description.
 *
 * Initilialise function to be invoked by application start-up
 *
 * 'Singleton' pattern that will actually only execute the initialisation
 *  once even if called multiple times.
 *
 *  Reset the sources database and initialise the database lock
 */
void log_init(void)
{
    static bool initialised = false;

    if(!initialised)
    {
        uint16_t i = 0;

        initialised = true;

#ifndef FREERTOS
        openlog ("API", LOG_CONS | LOG_PID | LOG_NDELAY, LOG_LOCAL1);
#endif
        /* Initialise the Logging source DB, */
        for(i = 0; i < DIM(log_sources); i++)
        {
            memset(log_sources[i].name, 0, DIM(log_sources[0].name));
            log_sources[i].level = eLOG_LEVEL_ERROR;
        }
        log_nextFreeIdx = 0;

        if (log_dbLock == NULL)
        	log_dbLock = osMutexCreate();
        ASSERT_NOT_NULL(log_dbLock);
    }
} /* log_init */



/*
 * See log_api.h for prototype description.
 *
 * This API call allocates an entry in the list of available logging sources
 * and returns a unique 'handle' for the newly allocated entry.
 * The default logging level is also established.
 *
 */
uint16_t log_register(const char* name)
{
    uint16_t newIdx    = 0;

    newIdx = log_getNextFreeIdxOrDie();

    /* TODO - use a safer alternative to strcpy that checks string lengths etc */
    strcpy(log_sources[newIdx].name, name);

    log_sources[newIdx].level = eLOG_LEVEL_ERROR;

    return log_idx2Handle(newIdx);
}


#if REPLACED
/*
 * See log_api.h for prototype description.
 *
 * This API logs DEBUG message to all destinations if DEBUG level
 * logging is enabled for the specified source.
 */
void log_debug(const uint16_t logHandle,
               const char*    file,
               const char*    func,
               int            line,
               const char*    msg,
               ...)
{
    uint8_t logIdx = log_handle2Idx(logHandle);

    if(log_idxIsValid(logIdx) &&
       log_debugIsEnabled(logIdx) &&
       (file != NULL) &&
       (func != NULL) &&
       (msg != NULL))
    {
        va_list fmtargs;

        va_start(fmtargs, msg);
        log_message(logIdx, eLOG_LEVEL_DEBUG, file, func, line, msg, fmtargs);
        va_end(fmtargs);
    }
} /* log_debug */



/*
 * See log_api.h for prototype description.
 *
 * This API logs INFO message to all destinations if INFO level
 * logging is enabled for the specified source.
 */
void log_info(const uint16_t logHandle,
              const char*    file,
              const char*    func,
              int            line,
              const char*    msg,
              ...)
{
    uint8_t logIdx = log_handle2Idx(logHandle);

    if(log_idxIsValid(logIdx) &&
       log_infoIsEnabled(logIdx) &&
       (file != NULL) &&
       (func != NULL) &&
       (msg != NULL))
    {
        va_list fmtargs;

        va_start(fmtargs, msg);
        log_message(logIdx, eLOG_LEVEL_INFO, file, func, line, msg, fmtargs);
        va_end(fmtargs);
    }
} /* log_info */



/*
 * See log_api.h for prototype description.
 *
 * This API logs ERROR message to all destinations.
 * ERROR is always logged.
 */
void log_error(const uint16_t logHandle,
               const char*    file,
               const char*    func,
               int            line,
               const char*    msg,
               ...)
{
    uint8_t logIdx = log_handle2Idx(logHandle);

    if(log_idxIsValid(logIdx) &&
       (file != NULL) &&
       (func != NULL) &&
       (msg != NULL))
    {
        va_list fmtargs;

        va_start(fmtargs, msg);
        log_message(logIdx, eLOG_LEVEL_ERROR, file, func, line, msg, fmtargs);
        va_end(fmtargs);
    }
} /* log_error */
#endif

static void log_xxx(const uint16_t logHandle,
				log_level_t    level,
               const char*    file,
               const char*    func,
               int            line,
               const char*    msg,
               va_list fmtargs)
{
    uint8_t logIdx = log_handle2Idx(logHandle);

    if(log_idxIsValid(logIdx) &&
    	log_levelIsEnabled(logIdx,level) &&
       (file != NULL) &&
       (func != NULL) &&
       (msg != NULL))
    {
        log_message(logIdx, level, file, func, line, msg, fmtargs);
    }
} /* log_error */

void log_emergency(const uint16_t logHandle,
               const char*    file,
               const char*    func,
               int            line,
               const char*    msg,
               ...)
{
	va_list fmtargs;
	va_start(fmtargs, msg);
	log_xxx(logHandle,eLOG_LEVEL_EMERGENCY,file,func,line,msg,fmtargs);
	va_end(fmtargs);
}

void log_alert(const uint16_t logHandle,
               const char*    file,
               const char*    func,
               int            line,
               const char*    msg,
               ...)
{
	va_list fmtargs;
	va_start(fmtargs, msg);
	log_xxx(logHandle,eLOG_LEVEL_ALERT,file,func,line,msg,fmtargs);
	va_end(fmtargs);
}

void log_critical(const uint16_t logHandle,
               const char*    file,
               const char*    func,
               int            line,
               const char*    msg,
               ...)
{
	va_list fmtargs;
	va_start(fmtargs, msg);
	log_xxx(logHandle,eLOG_LEVEL_CRITICAL,file,func,line,msg,fmtargs);
	va_end(fmtargs);
}

void log_error(const uint16_t logHandle,
               const char*    file,
               const char*    func,
               int            line,
               const char*    msg,
               ...)
{
	va_list fmtargs;
	va_start(fmtargs, msg);
	log_xxx(logHandle,eLOG_LEVEL_ERROR,file,func,line,msg,fmtargs);
	va_end(fmtargs);
}

void log_warning(const uint16_t logHandle,
               const char*    file,
               const char*    func,
               int            line,
               const char*    msg,
               ...)
{
	va_list fmtargs;
	va_start(fmtargs, msg);
	log_xxx(logHandle,eLOG_LEVEL_WARNING,file,func,line,msg,fmtargs);
	va_end(fmtargs);
}

void log_notice(const uint16_t logHandle,
               const char*    file,
               const char*    func,
               int            line,
               const char*    msg,
               ...)
{
	va_list fmtargs;
	va_start(fmtargs, msg);
	log_xxx(logHandle,eLOG_LEVEL_NOTICE,file,func,line,msg,fmtargs);
	va_end(fmtargs);
}

void log_info(const uint16_t logHandle,
               const char*    file,
               const char*    func,
               int            line,
               const char*    msg,
               ...)
{
	va_list fmtargs;
	va_start(fmtargs, msg);
	log_xxx(logHandle,eLOG_LEVEL_INFO,file,func,line,msg,fmtargs);
	va_end(fmtargs);
}

void log_debug(const uint16_t logHandle,
               const char*    file,
               const char*    func,
               int            line,
               const char*    msg,
               ...)
{
#if 0
	va_list fmtargs;
	va_start(fmtargs, msg);
	log_xxx(logHandle,eLOG_LEVEL_DEBUG,file,func,line,msg,fmtargs);
	va_end(fmtargs);
#endif
	if(logHandle == 2)
	ts_printf(msg);
}


/*
 * See log_api.h for prototype description.
 */
void log_setLevelDebug(const uint16_t logHandle)
{
    uint16_t logIdx = log_handle2Idx(logHandle);
    if(log_idxIsValid(logIdx))
    {
        log_sources[logIdx].level = eLOG_LEVEL_DEBUG;
    }
}

/*
 * See log_api.h for prototype description.
 */
void log_setLevelInfo(const uint16_t logHandle)
{
    uint16_t logIdx = log_handle2Idx(logHandle);
    if(log_idxIsValid(logIdx))
    {
        log_sources[logIdx].level = eLOG_LEVEL_INFO;
    }
}

/*
 * See log_api.h for prototype description.
 */
void log_setLevelNotice(const uint16_t logHandle)
{
    uint16_t logIdx = log_handle2Idx(logHandle);
    if(log_idxIsValid(logIdx))
    {
        log_sources[logIdx].level = eLOG_LEVEL_NOTICE;
    }
}

/*
 * See log_api.h for prototype description.
 */
void log_setLevelWarning(const uint16_t logHandle)
{
    uint16_t logIdx = log_handle2Idx(logHandle);
    if(log_idxIsValid(logIdx))
    {
        log_sources[logIdx].level = eLOG_LEVEL_WARNING;
    }
}

/*
 * See log_api.h for prototype description.
 */
void log_setLevelError(const uint16_t logHandle)
{
    uint16_t logIdx = log_handle2Idx(logHandle);
    if(log_idxIsValid(logIdx))
    {
        log_sources[logIdx].level = eLOG_LEVEL_ERROR;
    }
}

void setModuleLogLevel(const uint16_t logHandle, log_level_t logLevel)
{
	switch (logLevel)
	{
		case(eLOG_LEVEL_DEBUG):
			log_setLevelDebug(logHandle);
			break;
		case(eLOG_LEVEL_INFO):
			log_setLevelInfo(logHandle);
			break;
		case(eLOG_LEVEL_NOTICE):
			log_setLevelNotice(logHandle);
			break;
		case(eLOG_LEVEL_WARNING):
			log_setLevelWarning(logHandle);
			break;
		case(eLOG_LEVEL_ERROR):
			log_setLevelError(logHandle);
			break;
		default:
			log_setLevelError(logHandle);
	}
}

/*
 * See log_api.h for prototype description.
 */
void log_getLevelString(const uint16_t logHandle, char** levelStringPtr)
{
    if(levelStringPtr != NULL)
    {
        uint16_t logIdx = log_handle2Idx(logHandle);

        if(log_idxIsValid(logIdx) && log_levelIsValid(log_sources[logIdx].level))
        {
            log_level2String(log_sources[logIdx].level, levelStringPtr);
        }
        else
        {
            *levelStringPtr = log_string_invalid;
        }
    }
}


