/*
 * FILE:    debug.cpp
 * PROGRAM: RAT
 * AUTHORS: Isidor Kouvelas
 *          Colin Perkins
 *          Mark Handley
 *          Orion Hodson
 *          Jerry Isdale
 *
 * $Revision: 1.1 $
 * $Date: 2007/11/08 09:48:59 $
 *
 * Copyright (c) 1995-2000 University College London
 * Copyright (c) 2005-2021 CESNET, z. s. p. o.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, is permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *      This product includes software developed by the Computer Science
 *      Department at University College London
 * 4. Neither the name of the University nor of the Department may be used
 *    to endorse or promote products derived from this software without
 *    specific prior written permission.
 * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif // defined HAVE_CONFIG_H
#include "config_unix.h"
#include "config_win32.h"

#include <array>
#include <cstdint>
#include <string>
#include <unordered_map>

#include "compat/misc.h" // strdupa
#include "compat/platform_time.h"
#include "debug.h"
#include "host.h"
#include "rang.hpp"
#include "utils/color_out.h"
#include "utils/misc.h" // ug_strerror

using std::string;
using std::unordered_map;

volatile int log_level = LOG_LEVEL_INFO;

static void _dprintf(const char *format, ...)
{
        if (log_level < LOG_LEVEL_DEBUG) {
                return;
        }

#ifdef WIN32
        char msg[65535];
        va_list ap;

        va_start(ap, format);
        _vsnprintf(msg, 65535, format, ap);
        va_end(ap);
        OutputDebugString(msg);
#else
        va_list ap;

        va_start(ap, format);
        vfprintf(stderr, format, ap);
        va_end(ap);
#endif                          /* WIN32 */
}

void log_msg(int level, const char *format, ...)
{
        va_list ap;

        if (log_level < level) {
                return;
        }

#if 0 // WIN32
        if (log_level == LOG_LEVEL_DEBUG) {
                char msg[65535];
                va_list ap;

                va_start(ap, format);
                _vsnprintf(msg, 65535, format, ap);
                va_end(ap);
                OutputDebugString(msg);
                return;
        }
#endif                          /* WIN32 */

        // get number of required bytes
        va_start(ap, format);
        int size = vsnprintf(NULL, 0, format, ap);
        va_end(ap);

        // format the string
        char *buffer = (char *) alloca(size + 1);
        va_start(ap, format);
        if (vsprintf(buffer, format, ap) != size) {
                va_end(ap);
                return;
        }
        va_end(ap);

        LOG(level) << buffer;
}

void log_msg_once(int level, uint32_t id, const char *msg) {
        if (log_level < level) {
                return;
        }
        Logger(level).once(id, msg);
}

/**
 * This function is analogous to perror(). The message is printed using the logger.
 */
void log_perror(int level, const char *msg)
{
        log_msg(level, "%s: %s\n", msg, ug_strerror(errno));
}

/**
 * debug_dump:
 * @lp: pointer to memory region.
 * @len: length of memory region in bytes.
 *
 * Writes a dump of a memory region to stdout.  The dump contains a
 * hexadecimal and an ascii representation of the memory region.
 *
 **/
void debug_dump(void *lp, int len)
{
        char *p;
        int i, j, start;
        char Buff[81];
        char stuffBuff[10];
        char tmpBuf[10];

        _dprintf("Dump of %d=%x bytes\n", len, len);
        start = 0L;
        while (start < len) {
                /* start line with pointer position key */
                p = (char *)lp + start;
                sprintf(Buff, "%p: ", p);

                /* display each character as hex value */
                for (i = start, j = 0; j < 16; p++, i++, j++) {
                        if (i < len) {
                                sprintf(tmpBuf, "%X", ((int)(*p) & 0xFF));

                                if (strlen((char *)tmpBuf) < 2) {
                                        stuffBuff[0] = '0';
                                        stuffBuff[1] = tmpBuf[0];
                                        stuffBuff[2] = ' ';
                                        stuffBuff[3] = '\0';
                                } else {
                                        stuffBuff[0] = tmpBuf[0];
                                        stuffBuff[1] = tmpBuf[1];
                                        stuffBuff[2] = ' ';
                                        stuffBuff[3] = '\0';
                                }
                                strcat(Buff, stuffBuff);
                        } else
                                strcat(Buff, " ");
                        if (j == 7)     /* space between groups of 8 */
                                strcat(Buff, " ");
                }

                strcat(Buff, "  ");

                /* display each character as character value */
                for (i = start, j = 0, p = (char *)lp + start;
                     (i < len && j < 16); p++, i++, j++) {
                        if (((*p) >= ' ') && ((*p) <= '~'))     /* test displayable */
                                sprintf(tmpBuf, "%c", *p);
                        else
                                sprintf(tmpBuf, "%c", '.');
                        strcat(Buff, tmpBuf);
                        if (j == 7)     /* space between groups of 8 */
                                strcat(Buff, " ");
                }
                _dprintf("%s\n", Buff);
                start = i;      /* next line starting byte */
        }
}

bool set_log_level(const char *optarg, bool *logger_repeat_msgs, int *show_timestamps) {
        assert(optarg != nullptr);
        assert(logger_repeat_msgs != nullptr);

        using namespace std::string_literals;
        using std::clog;
        using std::cout;

        static const struct { const char *name; int level; } mapping[] = {
                { "quiet", LOG_LEVEL_QUIET },
                { "fatal", LOG_LEVEL_FATAL },
                { "error", LOG_LEVEL_ERROR },
                { "warning", LOG_LEVEL_WARNING},
                { "notice", LOG_LEVEL_NOTICE},
                { "info", LOG_LEVEL_INFO  },
                { "verbose", LOG_LEVEL_VERBOSE},
                { "debug", LOG_LEVEL_DEBUG },
                { "debug2", LOG_LEVEL_DEBUG2 },
        };

        if ("help"s == optarg) {
                cout << "log level: [0-" << LOG_LEVEL_MAX;
                for (auto m : mapping) {
                        cout << "|" << m.name;
                }
                cout << "][+repeat][+/-timestamps]\n";
                cout << BOLD("\trepeat") << " - print repeating log messages\n";
                cout << BOLD("\ttimestamps") << " - enable/disable timestamps\n";
                return false;
        }

        if (strstr(optarg, "+repeat") != nullptr) {
                *logger_repeat_msgs = true;
        }

        if (const char *timestamps = strstr(optarg, "timestamps")) {
                if (timestamps > optarg) {
                        *show_timestamps = timestamps[-1] == '+' ? 1 : 0;
                }
        }

        if (getenv("ULTRAGRID_VERBOSE") != nullptr) {
                log_level = LOG_LEVEL_VERBOSE;
        }

        if (optarg[0] == '+' || optarg[0] == '-') { // only flags, no log level
                return true;
        }

        if (isdigit(optarg[0])) {
                long val = strtol(optarg, nullptr, 0);
                if (val < 0 || val > LOG_LEVEL_MAX) {
                        clog << "Log: wrong value: " << optarg << " (allowed range [0.." << LOG_LEVEL_MAX << "])\n";
                        return false;
                }
                log_level = val;
                return true;
        }

        char *log_level_str = strdupa(optarg);
        if (char *delim = strpbrk(log_level_str, "+-")) {
                *delim = '\0';
        }
        for (auto m : mapping) {
                if (strcmp(log_level_str, m.name) == 0) {
                        log_level = m.level;
                        return true;
                }
        }

        LOG(LOG_LEVEL_ERROR) << "Wrong log level specification: " << optarg << "\n";
        return false;
}

/**
 * @param show_timestamps 0 - no; 1 - yes; -1 auto
 */
void Logger::preinit(bool skip_repeated, int show_timestamps)
{
        Logger::skip_repeated = skip_repeated;
        Logger::show_timestamps = show_timestamps;
        if (rang::rang_implementation::supportsColor()
                        && rang::rang_implementation::isTerminal(std::cout.rdbuf())
                        && rang::rang_implementation::isTerminal(std::cerr.rdbuf())) {
                // force ANSI sequences even when written to ostringstream
                rang::setControlMode(rang::control::Force);
#ifdef _WIN32
                // ANSI control sequences need to be explicitly set in Windows
                if ((rang::rang_implementation::setWinTermAnsiColors(std::cout.rdbuf()) || rang::rang_implementation::isMsysPty(_fileno(stdout))) &&
                                (rang::rang_implementation::setWinTermAnsiColors(std::cerr.rdbuf()) || rang::rang_implementation::isMsysPty(_fileno(stderr)))) {
                        rang::setWinTermMode(rang::winTerm::Ansi);
                }
#endif
        }
}

#ifdef DEBUG
void debug_file_dump(const char *key, void (*serialize)(const void *data, FILE *), void *data) {
        const char *dump_file_val = get_commandline_param("debug-dump");
        static thread_local unordered_map<string, int> skip_map;
        if (dump_file_val == nullptr) {
                return;
        }

        // check if key is contained
        string not_first = ",";
        not_first + key;
        int skip_n = 0;
        if (strstr(dump_file_val, key) == dump_file_val) {
                const char *val = dump_file_val + strlen(key);
                if (val[0] == '=') {
                        skip_n = atoi(val + 1);
                }
        } else if (strstr(dump_file_val, not_first.c_str()) != NULL) {
                const char *val = strstr(dump_file_val, not_first.c_str()) + strlen(key);
                if (val[0] == '=') {
                        skip_n = atoi(val + 1);
                }
        } else {
                return;
        }

        if (skip_map.find(key) == skip_map.end()) {
                skip_map[key] = skip_n;
        }

        if (skip_map[key] == -1) { // already exported
                return;
        }

        if (skip_map[key] > 0) {
                skip_map[key]--;
                return;
        }

        // export
        string name = string(key) + ".dump";
        FILE *out = fopen(name.c_str(), "wb");
        if (out == nullptr) {
                perror("debug_file_dump fopen");
                return;
        }
        serialize(data, out);
        fclose(out);

        skip_map[key] = -1;
}
#endif

std::atomic<Logger::last_message *> Logger::last_msg{};
thread_local std::set<uint32_t> Logger::oneshot_messages;
std::atomic<bool> Logger::skip_repeated{true};
int Logger::show_timestamps = -1;

