/*
 * IoT Common V1.1.1
 * Copyright (C) 2020 Amazon.com, Inc. or its affiliates.  All Rights Reserved.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy of
 * this software and associated documentation files (the "Software"), to deal in
 * the Software without restriction, including without limitation the rights to
 * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
 * the Software, and to permit persons to whom the Software is furnished to do so,
 * subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
 * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
 * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
 * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */

/**
 * @file iot_logging.c
 * @brief Implementation of logging functions from iot_logging.h
 */

/* The config header is always included first. */
#include "iot_config.h"

/* Standard includes. */
#include <stdarg.h>
#include <stdio.h>
#include <string.h>

/* Platform clock include. */
#include "platform/iot_clock.h"

/* Logging includes. */
#include "iot_logging.h"

/*-----------------------------------------------------------*/

/* This implementation assumes the following values for the log level constants.
 * Ensure that the values have not been modified. */
#if IOT_LOG_NONE != 0
    #error "IOT_LOG_NONE must be 0."
#endif
#if IOT_LOG_ERROR != 1
    #error "IOT_LOG_ERROR must be 1."
#endif
#if IOT_LOG_WARN != 2
    #error "IOT_LOG_WARN must be 2."
#endif
#if IOT_LOG_INFO != 3
    #error "IOT_LOG_INFO must be 3."
#endif
#if IOT_LOG_DEBUG != 4
    #error "IOT_LOG_DEBUG must be 4."
#endif

/**
 * @def IotLogging_Puts( message )
 * @brief Function the logging library uses to print a line.
 *
 * This function can be set by using a define. By default, the standard library
 * [puts](http://pubs.opengroup.org/onlinepubs/9699919799/functions/puts.html)
 * function is used.
 */
#ifndef IotLogging_Puts
    #define IotLogging_Puts    puts
#endif

/*
 * Provide default values for undefined memory allocation functions based on
 * the usage of dynamic memory allocation.
 */
#if IOT_STATIC_MEMORY_ONLY == 1
    /* Static memory allocation header. */
    #include "iot_static_memory.h"

/**
 * @brief Allocate a new logging buffer. This function must have the same
 * signature as [malloc](http://pubs.opengroup.org/onlinepubs/9699919799/functions/malloc.html).
 */
    #ifndef IotLogging_Malloc
        #define IotLogging_Malloc    Iot_MallocMessageBuffer
    #endif

/**
 * @brief Free a logging buffer. This function must have the same signature
 * as [free](http://pubs.opengroup.org/onlinepubs/9699919799/functions/free.html).
 */
    #ifndef IotLogging_Free
        #define IotLogging_Free    Iot_FreeMessageBuffer
    #endif

/**
 * @brief Get the size of a logging buffer. Statically-allocated buffers
 * should all have the same size.
 */
    #ifndef IotLogging_StaticBufferSize
        #define IotLogging_StaticBufferSize    Iot_MessageBufferSize
    #endif
#else /* if IOT_STATIC_MEMORY_ONLY == 1 */
    #ifndef IotLogging_Malloc
        #include <stdlib.h>
        #define IotLogging_Malloc    malloc
    #endif

    #ifndef IotLogging_Free
        #include <stdlib.h>
        #define IotLogging_Free    free
    #endif
#endif /* if IOT_STATIC_MEMORY_ONLY == 1 */

/**
 * @brief A guess of the maximum length of a timestring.
 *
 * There's no way for this logging library to know the length of a timestring
 * before it's generated. Therefore, the logging library will assume a maximum
 * length of any timestring it may get. This value should be generous enough
 * to accommodate the vast majority of timestrings.
 *
 * @see @ref platform_clock_function_gettimestring
 */
#define MAX_TIMESTRING_LENGTH    ( 64 )

/**
 * @brief The longest string in #_pLogLevelStrings (below), plus 3 to accommodate
 * `[]` and a null-terminator.
 */
#define MAX_LOG_LEVEL_LENGTH     ( 8 )

/**
 * @brief How many bytes @ref logging_function_genericprintbuffer should output on
 * each line.
 */
#define BYTES_PER_LINE           ( 16 )

/*-----------------------------------------------------------*/

/**
 * @brief Lookup table for log levels.
 *
 * Converts one of the @ref logging_constants_levels to a string.
 */
static const char * const _pLogLevelStrings[ 5 ] =
{
    "",      /* IOT_LOG_NONE */
    "ERROR", /* IOT_LOG_ERROR */
    "WARN ", /* IOT_LOG_WARN */
    "INFO ", /* IOT_LOG_INFO */
    "DEBUG"  /* IOT_LOG_DEBUG */
};

/*-----------------------------------------------------------*/

#if !defined( IOT_STATIC_MEMORY_ONLY ) || ( IOT_STATIC_MEMORY_ONLY == 0 )
    static bool _reallocLoggingBuffer( void ** pOldBuffer,
                                       size_t newSize,
                                       size_t oldSize )
    {
        bool status = false;

        /* Allocate a new, larger buffer. */
        void * pNewBuffer = IotLogging_Malloc( newSize );

        /* Ensure that memory allocation succeeded. */
        if( pNewBuffer != NULL )
        {
            /* Copy the data from the old buffer to the new buffer. */
            ( void ) memcpy( pNewBuffer, *pOldBuffer, oldSize );

            /* Free the old buffer and update the pointer. */
            IotLogging_Free( *pOldBuffer );
            *pOldBuffer = pNewBuffer;

            status = true;
        }

        return status;
    }
#endif /* if !defined( IOT_STATIC_MEMORY_ONLY ) || ( IOT_STATIC_MEMORY_ONLY == 0 ) */

/*-----------------------------------------------------------*/

void IotLog_Generic( int32_t libraryLogSetting,
                     const char * const pLibraryName,
                     int32_t messageLevel,
                     const IotLogConfig_t * const pLogConfig,
                     const char * const pFormat,
                     ... )
{
    int requiredMessageSize = 0;
    size_t bufferSize = 0,
           bufferPosition = 0, timestringLength = 0;
    char * pLoggingBuffer = NULL;
    va_list args;

    /* If the library's log level setting is lower than the message level,
     * return without doing anything. */
    if( ( messageLevel == 0 ) || ( messageLevel > libraryLogSetting ) )
    {
        return;
    }

    if( ( pLogConfig == NULL ) || ( pLogConfig->hideLogLevel == false ) )
    {
        /* Add length of log level if requested. */
        bufferSize += MAX_LOG_LEVEL_LENGTH;
    }

    /* Estimate the amount of buffer needed for this log message. */
    if( ( pLogConfig == NULL ) || ( pLogConfig->hideLibraryName == false ) )
    {
        /* Add size of library name if requested. Add 2 to accommodate "[]". */
        bufferSize += strlen( pLibraryName ) + 2;
    }

    if( ( pLogConfig == NULL ) || ( pLogConfig->hideTimestring == false ) )
    {
        /* Add length of timestring if requested. */
        bufferSize += MAX_TIMESTRING_LENGTH;
    }

    /* Add 64 as an initial (arbitrary) guess for the length of the message. */
    bufferSize += 64;

    /* In static memory mode, check that the log message will fit in the a
     * static buffer. */
    #if IOT_STATIC_MEMORY_ONLY == 1
        if( bufferSize >= IotLogging_StaticBufferSize() )
        {
            /* If the static buffers are likely too small to fit the log message,
             * return. */
            return;
        }

        /* Otherwise, update the buffer size to the size of a static buffer. */
        bufferSize = IotLogging_StaticBufferSize();
    #endif

    /* Allocate memory for the logging buffer. */
    pLoggingBuffer = ( char * ) IotLogging_Malloc( bufferSize );

    if( pLoggingBuffer == NULL )
    {
        return;
    }

    /* Print the message log level if requested. */
    if( ( pLogConfig == NULL ) || ( pLogConfig->hideLogLevel == false ) )
    {
        /* Ensure that message level is valid. */
        if( ( messageLevel >= IOT_LOG_NONE ) && ( messageLevel <= IOT_LOG_DEBUG ) )
        {
            /* Add the log level string to the logging buffer. */
            requiredMessageSize = snprintf( pLoggingBuffer + bufferPosition,
                                            bufferSize - bufferPosition,
                                            "[%s]",
                                            _pLogLevelStrings[ messageLevel ] );

            /* Check for encoding errors. */
            if( requiredMessageSize <= 0 )
            {
                IotLogging_Free( pLoggingBuffer );

                return;
            }

            /* Update the buffer position. */
            bufferPosition += ( size_t ) requiredMessageSize;
        }
    }

    /* Print the library name if requested. */
    if( ( pLogConfig == NULL ) || ( pLogConfig->hideLibraryName == false ) )
    {
        /* Add the library name to the logging buffer. */
        requiredMessageSize = snprintf( pLoggingBuffer + bufferPosition,
                                        bufferSize - bufferPosition,
                                        "[%s]",
                                        pLibraryName );

        /* Check for encoding errors. */
        if( requiredMessageSize <= 0 )
        {
            IotLogging_Free( pLoggingBuffer );

            return;
        }

        /* Update the buffer position. */
        bufferPosition += ( size_t ) requiredMessageSize;
    }

    /* Print the timestring if requested. */
    if( ( pLogConfig == NULL ) || ( pLogConfig->hideTimestring == false ) )
    {
        /* Add the opening '[' enclosing the timestring. */
        pLoggingBuffer[ bufferPosition ] = '[';
        bufferPosition++;

        /* Generate the timestring and add it to the buffer. */
        if( IotClock_GetTimestring( pLoggingBuffer + bufferPosition,
                                    bufferSize - bufferPosition,
                                    &timestringLength ) == true )
        {
            /* If the timestring was successfully generated, add the closing "]". */
            bufferPosition += timestringLength;
            pLoggingBuffer[ bufferPosition ] = ']';
            bufferPosition++;
        }
        else
        {
            /* Sufficient memory for a timestring should have been allocated. A timestring
             * probably failed to generate due to a clock read error; remove the opening '['
             * from the logging buffer. */
            bufferPosition--;
            pLoggingBuffer[ bufferPosition ] = '\0';
        }
    }

    /* Add a padding space between the last closing ']' and the message, unless
     * the logging buffer is empty. */
    if( bufferPosition > 0 )
    {
        pLoggingBuffer[ bufferPosition ] = ' ';
        bufferPosition++;
    }

    va_start( args, pFormat );

    /* Add the log message to the logging buffer. */
    requiredMessageSize = vsnprintf( pLoggingBuffer + bufferPosition,
                                     bufferSize - bufferPosition,
                                     pFormat,
                                     args );

    va_end( args );

    /* If the logging buffer was too small to fit the log message, reallocate
     * a larger logging buffer. */
    if( ( size_t ) requiredMessageSize >= bufferSize - bufferPosition )
    {
        #if IOT_STATIC_MEMORY_ONLY == 1

            /* There's no point trying to allocate a larger static buffer. Return
             * immediately. */
            IotLogging_Free( pLoggingBuffer );

            return;
        #else
            if( _reallocLoggingBuffer( ( void ** ) &pLoggingBuffer,
                                       ( size_t ) requiredMessageSize + bufferPosition + 1,
                                       bufferSize ) == false )
            {
                /* If buffer reallocation failed, return. */
                IotLogging_Free( pLoggingBuffer );

                return;
            }

            /* Reallocation successful, update buffer size. */
            bufferSize = ( size_t ) requiredMessageSize + bufferPosition + 1;

            /* Add the log message to the buffer. Now that the buffer has been
             * reallocated, this should succeed. */
            va_start( args, pFormat );
            requiredMessageSize = vsnprintf( pLoggingBuffer + bufferPosition,
                                             bufferSize - bufferPosition,
                                             pFormat,
                                             args );
            va_end( args );
        #endif /* if IOT_STATIC_MEMORY_ONLY == 1 */
    }

    /* Check for encoding errors. */
    if( requiredMessageSize <= 0 )
    {
        IotLogging_Free( pLoggingBuffer );

        return;
    }

    /* Print the logging buffer to stdout. */
    IotLogging_Puts( pLoggingBuffer );

    /* Free the logging buffer. */
    IotLogging_Free( pLoggingBuffer );
}

/*-----------------------------------------------------------*/

void IotLog_GenericPrintBuffer( const char * const pLibraryName,
                                const char * const pHeader,
                                const uint8_t * const pBuffer,
                                size_t bufferSize )
{
    size_t i = 0, offset = 0;

    /* Allocate memory to hold each line of the log message. Since each byte
     * of pBuffer is printed in 4 characters (2 digits, a space, and a null-
     * terminator), the size of each line is 4 * BYTES_PER_LINE. */
    char * pMessageBuffer = IotLogging_Malloc( 4 * BYTES_PER_LINE );

    /* Exit if no memory is available. */
    if( pMessageBuffer == NULL )
    {
        return;
    }

    /* Print pHeader before printing pBuffer. */
    if( pHeader != NULL )
    {
        IotLog_Generic( IOT_LOG_DEBUG,
                        pLibraryName,
                        IOT_LOG_DEBUG,
                        NULL,
                        pHeader );
    }

    /* Print each byte in pBuffer. */
    for( i = 0; i < bufferSize; i++ )
    {
        /* Print a line if BYTES_PER_LINE is reached. But don't print a line
         * at the beginning (when i=0). */
        if( ( i % BYTES_PER_LINE == 0 ) && ( i != 0 ) )
        {
            IotLogging_Puts( pMessageBuffer );

            /* Reset offset so that pMessageBuffer is filled from the beginning. */
            offset = 0;
        }

        /* Print a single byte into pMessageBuffer. */
        ( void ) snprintf( pMessageBuffer + offset, 4, "%02x ", pBuffer[ i ] );

        /* Move the offset where the next character is printed. */
        offset += 3;
    }

    /* Print the final line of bytes. This line isn't printed by the for-loop above. */
    IotLogging_Puts( pMessageBuffer );

    /* Free memory used by this function. */
    IotLogging_Free( pMessageBuffer );
}

/*-----------------------------------------------------------*/
