/* 
 * This software is licensed under terms of the GNU GENERAL PUBLIC LICENSE
 *
 * This software is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This software  is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this software.  If not, see <http://www.gnu.org/licenses/>.
 *
 * Source code is available at http://ppcenter.free.fr
 */


#include "log.h"
#include <windows.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>

#ifdef  __cplusplus
extern "C" {
#endif



/* Log function for debug purpose only. */
void llog(const char *string, ...)
{
#if 0
    char buff[8192];
    va_list argptr;

    /* Display "LOG>" at the begining of each lines. */
    int buff_stt = 0;
    buff[buff_stt++] = 'L';
    buff[buff_stt++] = 'L';
    buff[buff_stt++] = 'O';
    buff[buff_stt++] = 'G';
    buff[buff_stt++] = '>';

    /* Print log contents to memory. */
    va_start(argptr, string);
    vsnprintf(buff+buff_stt, sizeof(buff)-buff_stt, string, argptr);
    va_end(argptr);

    /* Probably not useful, but better than forgetting it ... */
    buff[sizeof(buff) - 1] = '\0';

    /* Send to DebugView. */
    OutputDebugString(buff);
#endif
}


/*************************************************************************************/
/*************************************************************************************/


/**
 * Pointer to global log settings.
 * Need to be a global variable shared by host program, DLL and wrapper.
 * Hence, it supposes no concurent data access from each user.
**/
scl_settings_t* _logset = NULL;



/*************************************************************************************/
/*************************************************************************************/


/**
 * Buffered log functions.
 * Purpose is to decrease log printing time.
 * (when log amout is too big, synchronization problems
 * may happen with freewing device).
**/


void bufflog_flush(void)
{
    //llog("TEST(%d) = 0x%08X", __LINE__, _logset);
    if(_logset == NULL) return;
    if(_logset->bufflog_ptr    == NULL) return;
    if(_logset->bufflog_end    == 0   ) return;
    if(_logset->bufflog_stream == NULL) return;

    fprintf(_logset->bufflog_stream, "%s", _logset->bufflog_ptr);
    fflush(_logset->bufflog_stream);
    _logset->bufflog_end = 0;
}



void bufflog_print(char* str)
{
    int len;
    //llog("TEST(%d) = 0x%08X", __LINE__, _logset);
    if(_logset == NULL) return;
    if(_logset->bufflog_ptr    == 0) return;
    if(_logset->bufflog_stream == NULL) return;

    len = strlen(str);

    /* Check log file fize. */
    //llog("MAX = %d, FILESZ = %d, DIFF = %d", _logset->bufflog_maxsz, _logset->bufflog_filesz, _logset->bufflog_maxsz - _logset->bufflog_filesz);
    if((_logset->bufflog_maxsz != 0) && (_logset->bufflog_filesz > _logset->bufflog_maxsz))
    { /* Log file size over. */
        bufflog_flush();
        return;
    }
    _logset->bufflog_filesz += len;

    /* Can't add to buffer ? */
    if((_logset->bufflog_end + len) >= _logset->bufflog_sz)
    {
        /* Flush buffer */
        bufflog_flush();
    }
    /* String is too big for the buffer ? */
    if((_logset->bufflog_end + len) >= _logset->bufflog_sz)
    {
        /* Directly write to output. */
        fprintf(_logset->bufflog_stream, "%s", str);
        fflush(_logset->bufflog_stream);
    }
    else
    {
        /* Concatenate to buffer. */
        memcpy(_logset->bufflog_ptr+_logset->bufflog_end, str, len);
        _logset->bufflog_end += len;
        _logset->bufflog_ptr[_logset->bufflog_end] = '\0';
    }
}


void bufflog_end(void)
{
    //llog("TEST(%d) = 0x%08X", __LINE__, _logset);
    if(_logset == NULL) return;

    bufflog_flush();
    free(_logset->bufflog_ptr); _logset->bufflog_ptr = NULL;
    _logset->bufflog_sz = 0;
    _logset->bufflog_end = 0;
    if(_logset->bufflog_stream != NULL)
    {
        fclose(_logset->bufflog_stream);
        _logset->bufflog_stream = NULL;
    }
}


void bufflog_set_file(char* filename)
{
    //llog("TEST(%d) = 0x%08X", __LINE__, _logset);
    if(_logset == NULL) return;

    //llog("bufflog_set_file(%s)", filename);

    /* Close previous file. */
    if(_logset->bufflog_stream != NULL)
    {
        fclose(_logset->bufflog_stream);
        _logset->bufflog_stream = NULL;
    }
    /* Open destination file. */
    _logset->bufflog_stream = fopen(filename, "wb");
}
void bufflog_set_size(int sz)
{
    if(_logset == NULL) return;

    /* If we might loose some data, flush and free previous data. */
    if(sz <= _logset->bufflog_end)
    {
        bufflog_flush();
    }

    /* Allocate new buffer. */
    /* Allocate one more byte for the final null character. */
    _logset->bufflog_ptr = (char*)realloc((void*)_logset->bufflog_ptr, sz+1);
    if(_logset->bufflog_ptr)
    {
        _logset->bufflog_sz = sz;
    }
    else
    {
        llog("Log buffer alloc error !");
        _logset->bufflog_sz = 0;
    }
}
void bufflog_set_maxsize(int sz)
{
    if(_logset == NULL) return;

    _logset->bufflog_maxsz = sz;
}




/*************************************************************************************/
/*************************************************************************************/

/* Default log level when log is turned to silent. */
#define DEFAULT_SILENT_LEVEL 1

#define INIFILE_MAXSIZE (32*1024)
#define MAXARGS 1024
void scl_parse_inifile(char* inifilebuffer, char* filename, int* argc, char** argv)
{
    int i, j;

    FILE *fp;
    fp = fopen(filename,"rb");
    if(fp)
    { /* The first parameter is a path to an ini file. */
        int len;
        memset((void*)inifilebuffer, '\0', INIFILE_MAXSIZE);
        len = fread(inifilebuffer, 1, INIFILE_MAXSIZE, fp);
        fclose(fp); //fp = NULL;
        inifilebuffer[INIFILE_MAXSIZE-1] = '\0';

        /* If needed, append first line to argv. */
        if((inifilebuffer[0] > ' ') && (inifilebuffer[0] != '#'))
        {
            argv[(*argc)] = inifilebuffer; (*argc)++;
        }
        for(i=0; i<(len-2); i++)
        {
            /* If found, append command. */
            if((inifilebuffer[i  ] < ' ')
            && (inifilebuffer[i+1] > ' '))
            {
                /* Ignore comments */
                if(inifilebuffer[i+1] != '#')
                {
                    argv[(*argc)] = inifilebuffer + i+1; (*argc)++;
                }
            }
            /* Remove non displayable characters. */
            if(inifilebuffer[i] < ' ') inifilebuffer[i] = '\0';
        }
        /* Remove non displayable characters. */
        j=i;
        for(i=j; i<len; i++)
        {
            if(inifilebuffer[i] < ' ') inifilebuffer[i] = '\0';
        }
    }
}



/*************************************************************************************/
/*************************************************************************************/



void scl_init(scl_settings_t* settings, unsigned char satlink_init)
{
    int i, j;
    /* Re-open ? */
    if(_logset != NULL)
    {
        scl_end();
    }

    /* Init global log settings. */
    _logset = settings;
    memset((void*)_logset, 0, sizeof(scl_settings_t));


    /* Set default values to global log settings. */
    for(i=0; i<SCLT_COUNT; i++)
    {
        for(j=0; j<SCLO_COUNT; j++)
        {
            if(satlink_init == 1)
            {
                /* SatLink init: loglevel 5 to main log and file. */
                _logset->levels[i][j] = ((j == SCLO_CBCK1) || (j == SCLO_FILE) ? 5 : 0xF);
            }
            else
            {
                /* slnk init: loglevel 3 to DebugView and printf. */
                _logset->levels[i][j] = ((j == SCLO_DBGVIEW) || (j == SCLO_PRINTF) ? 3 : 0xF);
            }
        }
    }

    /* Init Silent mode parameters (default is ON). */
    _logset->dbg_outmask = 0x0C;
    _logset->silent_on = 0; scl_silentmode(1/*silent_on*/, DEFAULT_SILENT_LEVEL);

    /* Set log file name. */
    bufflog_set_file("satlink.log");
    /* Set file log buffer size to 100KB. */
    bufflog_set_size(100*1024);
}

void scl_end(void)
{
    //llog("TEST(%d) = 0x%08X", __LINE__, _logset);
    /* Not opened yet ? */
    if(_logset == NULL) return;

    /* Flush buffered data to disk and if needed, close the log file. */
    bufflog_end();

    /* Clear setting data. */
    memset((void*)_logset, 0, sizeof(scl_settings_t));
    _logset = NULL;
}



void scl_read_ini(char* filename, char* section_name)
{
    int i, j, index;
    char tmp[512];
    unsigned long val;
    int argc=0;
    char* argv[MAXARGS];
    char inifilebuffer[INIFILE_MAXSIZE];
    time_t     now;
    struct tm  *ts;
    char       buf[80];

    if(_logset == NULL) return;


    for(index=0; index<MAXARGS; index++) argv[index] = NULL;
    /* Add dummy program path. */
    argv[argc] = (char*)""; argc++;

    /* Get log settings commands from ini file. */
    scl_parse_inifile(inifilebuffer, filename, &argc, argv);

    for(index = 1; index < argc; index++)
    { //llog("***PARSE[%d/%d] = %s", index, argc, argv[index]);

        /* Log file name. */
        sprintf(tmp, "-%sfile", section_name);
        if(!stricmp(argv[index], tmp))
        {
            if(!strcmp(argv[index+1], "<date>"))
            { /* Special case: set current date and time to file name. */
                now = time(NULL);
                ts = localtime(&now);
                strftime(buf, sizeof(buf), "%Y%m%d_%H%M%S", ts);
                sprintf(tmp, "satlink_%s.log", buf);
                bufflog_set_file(tmp);
            }
            else
            { /* Set specified log file name. */
                bufflog_set_file(argv[index+1]);
            }

            index += 1;
        }
        /* Log file maximum size (in MB). */
        sprintf(tmp, "-%smaxfilesize", section_name);
        if(!stricmp(argv[index], tmp))
        {
            val = strtoul(argv[index+1], NULL, 0);
            llog("Set log file MAX size = %d MB", val);
            bufflog_set_maxsize(val * 1024 * 1024);

            index += 1;
        }

        /* Log buffer size. */
        sprintf(tmp, "-%sbuffsize", section_name);
        if(!stricmp(argv[index], tmp))
        {
            val = strtoul(argv[index+1], NULL, 0);
            llog("Set log file buffer size = %d KB", val);
            bufflog_set_size(val * 1024);

            index += 1;
        }

        /* Output and levels stuff. */
        sprintf(tmp, "-%soutlvl", section_name);
        if(!stricmp(argv[index], tmp))
        {
            for(i=0; i<SCLT_COUNT; i++)
            {
                /* Format is 0x00LLLLLL.
                 *  L is a log level value (0~9). Each L is assigned to a different output.
                 *  Note: when you don't want output at all, set L to 0xF value.
                 *  (setting to zero will display messages whose loglevel is zero).
                 */
                /* Default value is all output set on printf+debugview only, and all log levels set to 3. */
                val = strtoul(argv[index+1 + i], NULL, 0);

                for(j=0; j<SCLO_COUNT; j++)
                {
                    _logset->levels[i][j] = (val >> (j*4)) & 0x0000000F;
                }
            }

            index += SCLT_COUNT;
        }

        /* Set debug mode output mask. */
        sprintf(tmp, "-%ssatdbg", section_name);
        if(!stricmp(argv[index], tmp))
        {
            val = strtoul(argv[index+1], NULL, 0);
            llog("Set dbg output mask = 0x%02X", val);
            _logset->dbg_outmask = val & 0xFF;

            index += 1;
        }

    } // Parse log parameters

    /* Init Silent mode parameters (default is ON). */
    _logset->silent_on = 0; scl_silentmode(1/*silent_on*/, DEFAULT_SILENT_LEVEL);
}



void scl_set_callback(int id, Fct_scl_callback c)
{
    //llog("TEST(%d) = 0x%08X", __LINE__, _logset);
    if(_logset == NULL) return;
    _logset->callbacks[id] = c;
}


/* Buffer used to format log message. */
char _log_buffer[8192];
void scl_log(unsigned char type_prompt, unsigned char level, const char *string, ...)
{
    va_list argptr;
    int need_format = 1;
    char* log_ptr = _log_buffer;
    unsigned char log_ofs   = 0;
    unsigned char type, prompt;

    if(_logset == NULL) return;

    /* Extract infos from type+mask parameter. */
    type   = type_prompt & SCLT_TYPE_MSK;
    prompt = (type_prompt & SCLT_PROMPT_MSK ? 1 : 0);

    
    /* If needed, print log contents to memory. */
#define FORMAT_MESSAGE() if(need_format) {log_ofs += sprintf(log_ptr+log_ofs, "%01X%01X>", type, level); va_start(argptr, string); vsnprintf(log_ptr+log_ofs, sizeof(_log_buffer)-log_ofs, string, argptr); va_end(argptr); _log_buffer[sizeof(_log_buffer) - 1] = '\0';} need_format = 0;
#define TEST_OUT(_OV_) ((_logset->levels[type][_OV_] != 0xF) && ((prompt == 1) || (level <= _logset->levels[type][_OV_])))

    /* For each output, test if output is needed. */
    if(TEST_OUT(SCLO_PRINTF))
    { /* Printf requested. */
        FORMAT_MESSAGE();

        printf("%s\r\n", _log_buffer);
    }

    if(TEST_OUT(SCLO_DBGVIEW))
    { /* OutputDebugString requested. */
        FORMAT_MESSAGE();

        OutputDebugString(_log_buffer);
    }

    if(TEST_OUT(SCLO_FILE))
    { /* Output to file requested. */
        FORMAT_MESSAGE();

        bufflog_print(_log_buffer);
        /* Don't forget the line break. */
        bufflog_print((char*)"\r\n");
    }


    if(TEST_OUT(SCLO_CBCK1))
    { /* Use user-defined callback #1. */
        if(_logset->callbacks[0])
        {
            FORMAT_MESSAGE();

            (_logset->callbacks[0])(type, level, prompt, log_ofs, _log_buffer);
        }
    }
    if(TEST_OUT(SCLO_CBCK2))
    { /* Use user-defined callback #2. */
        if(_logset->callbacks[1])
        {
            FORMAT_MESSAGE();

            (_logset->callbacks[1])(type, level, prompt, log_ofs, _log_buffer);
        }
    }
    if(TEST_OUT(SCLO_CBCK3))
    { /* Use user-defined callback #3. */
        if(_logset->callbacks[2])
        {
            FORMAT_MESSAGE();

            (_logset->callbacks[2])(type, level, prompt, log_ofs, _log_buffer);
        }
    }
}

void scl_logflush(void)
{
    bufflog_flush();
}



void scl_silentmode(unsigned char silent_on, unsigned char silent_level)
{
    int i, j;
    if(_logset == NULL) return;

    /* Turn on silent mode ? (first time only) */
    if((silent_on) && (!(_logset->silent_on)))
    {
        /* Save current log levels. */
        memcpy((void*)(_logset->levels_backup), (void*)(_logset->levels), SCLT_COUNT*SCLO_COUNT*sizeof(unsigned char));
    }
    /* Turn on silent mode ? If so, update log levels. */
    if(silent_on)
    {
        /* Set debug level to selected level on selected outputs. */
        for(j=0; j<SCLO_COUNT; j++)
        {
            if(((_logset->dbg_outmask) >> j) & 0x01)
            { /* Set loglevels to selected level on this output. */
                for(i=0; i<SCLT_COUNT; i++)
                {
                    /* Don't reset log level for Saturn log messages. */
                    if((i != SCLT_SATMSG) && (i != SCLT_SATGRD))
                    {
                        /* Don't reset log when it is disabled. */
                        if(_logset->levels[i][j] != 0xF)
                        {
                            /* Allow only selected level's log messages. */
                            _logset->levels[i][j] = silent_level;
                        }
                    }
                }
            }
        }

        /* Set silent flag */
        _logset->silent_on = 1;
    }


    /* Turn off silent mode ? */
    if((!silent_on) && (_logset->silent_on))
    {
        /* Restore log levels. */
        memcpy((void*)(_logset->levels), (void*)(_logset->levels_backup), SCLT_COUNT*SCLO_COUNT*sizeof(unsigned char));

        /* Reset silent flag */
        _logset->silent_on = 0;
    }
}

void scl_adjust(unsigned char type, unsigned char output, unsigned char level)
{
    if(_logset == NULL) return;
    _logset->levels[type][output] = level;

    /* Update backup level when silent is ON. */
    if(_logset->silent_on)
    {
        _logset->levels_backup[type][output] = level;
    }

    /* Update Silent mode parameters. */
    if(!(_logset->silent_on))
    {
        /* Save current log levels. */
        memcpy((void*)(_logset->levels_backup), (void*)(_logset->levels), SCLT_COUNT*SCLO_COUNT*sizeof(unsigned char));
    }
}

#ifdef  __cplusplus
}
#endif

