/*
 * Copyright (C) 2015 by Multi-Tech Systems
 *
 * This file is part of libmts.
 *
 * libmts is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 2 of the License, or
 * (at your option) any later version.
 *
 * libmts 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 Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with libmts.  If not, see <http://www.gnu.org/licenses/>.
 *
 */

#include <mts/MTS_Logger.h>
#include <mts/MTS_Lock.h>
#include <mts/MTS_Text.h>
#include <mts/MTS_System.h>
#include <stdio.h>
#include <errno.h>
#include <stdarg.h>
#include <syslog.h>

#include <iostream>

using namespace MTS;

const char* Logger::PrintLevel::OFF_LABEL = "OFF";
const char* Logger::PrintLevel::FATAL_LABEL = "FATAL";
const char* Logger::PrintLevel::ERROR_LABEL = "ERROR";
const char* Logger::PrintLevel::WARNING_LABEL = "WARNING";
const char* Logger::PrintLevel::INFO_LABEL = "INFO";
const char* Logger::PrintLevel::CONFIG_LABEL = "CONFIG";
const char* Logger::PrintLevel::DEBUG_LABEL = "DEBUG";
const char* Logger::PrintLevel::TRACE_LABEL = "TRACE";
const char* Logger::PrintLevel::MAXIMUM_LABEL = "MAXIMUM";

const int Logger::PrintLevel::OFF_LEVEL = 0;
const int Logger::PrintLevel::MINIMUM_LEVEL = 1;
const int Logger::PrintLevel::FATAL_LEVEL = 1;
const int Logger::PrintLevel::ERROR_LEVEL = 10;
const int Logger::PrintLevel::WARNING_LEVEL = 20;
const int Logger::PrintLevel::INFO_LEVEL = 30;
const int Logger::PrintLevel::CONFIG_LEVEL = 40;
const int Logger::PrintLevel::DEBUG_LEVEL = 50;
const int Logger::PrintLevel::TRACE_LEVEL = 60;
const int Logger::PrintLevel::MAXIMUM_LEVEL = 100;

volatile int Logger::m_iPrintLevel = Logger::PrintLevel::MAXIMUM_LEVEL;
std::string Logger::m_sPrintLevel = Logger::PrintLevel::MAXIMUM_LABEL;
Logger::PrintMode Logger::m_eMode = Logger::PrintMode::STDOUT_ONLY;
FILE* Logger::m_pFile = NULL;
int Logger::m_iLogFacility = -1;
Lock Logger::m_oPrintLock;
std::string Logger::m_sIdent;
std::string Logger::m_sFileName;

int Logger::getPrintLevel() {
    return m_iPrintLevel;
}

const std::string& Logger::getPrintLevelString() {
    if (m_iPrintLevel == PrintLevel::OFF_LEVEL)
        m_sPrintLevel = std::string(PrintLevel::OFF_LABEL);
    else if (m_iPrintLevel == PrintLevel::FATAL_LEVEL)
        m_sPrintLevel = std::string(PrintLevel::FATAL_LABEL);
    else if (m_iPrintLevel > PrintLevel::FATAL_LEVEL && m_iPrintLevel <= PrintLevel::ERROR_LEVEL)
        m_sPrintLevel = std::string(PrintLevel::ERROR_LABEL);
    else if (m_iPrintLevel > PrintLevel::ERROR_LEVEL && m_iPrintLevel <= PrintLevel::WARNING_LEVEL)
        m_sPrintLevel = std::string(PrintLevel::WARNING_LABEL);
    else if (m_iPrintLevel > PrintLevel::WARNING_LEVEL && m_iPrintLevel <= PrintLevel::INFO_LEVEL)
        m_sPrintLevel = std::string(PrintLevel::INFO_LABEL);
    else if (m_iPrintLevel > PrintLevel::INFO_LEVEL && m_iPrintLevel <= PrintLevel::CONFIG_LEVEL)
        m_sPrintLevel = std::string(PrintLevel::CONFIG_LABEL);
    else if (m_iPrintLevel > PrintLevel::CONFIG_LEVEL && m_iPrintLevel <= PrintLevel::DEBUG_LEVEL)
        m_sPrintLevel = std::string(PrintLevel::DEBUG_LABEL);
    else if (m_iPrintLevel > PrintLevel::DEBUG_LEVEL && m_iPrintLevel <= PrintLevel::TRACE_LEVEL)
        m_sPrintLevel = std::string(PrintLevel::TRACE_LABEL);
    else
        m_sPrintLevel = std::string(PrintLevel::MAXIMUM_LABEL);

    return m_sPrintLevel;
}

void Logger::setPrintLevel(int32_t level, bool silent) {
    m_iPrintLevel = level;
    if (!silent) {
        printf(level, "Logger Level Changed to %d\n", level);
    }
}

bool Logger::isPrintable(int32_t level) {
    int32_t currentLevel = getPrintLevel();
    return (level <= currentLevel) && (currentLevel > PrintLevel::OFF_LEVEL);
}

int32_t Logger::syslogPrintLevelConversion(const int32_t& level) {
    if (level < 10) {
        return LOG_EMERG;
    } else if (level < 20) {
        return LOG_ERR;
    } else if (level < 30) {
        return LOG_WARNING;
    } else if (level < 50) {
        return LOG_INFO;
    } else {
        return LOG_DEBUG;
    }
}

void Logger::printMessage(const int32_t& level, const char* label, const char* format, va_list argptr) {

    m_oPrintLock.lock();
    switch (m_eMode) {
        case Logger::PrintMode::STDOUT_ONLY:
            ::printf("%s|%s|", MTS::Text::time(MTS::System::timeMicros()).c_str(), label);
            vprintf(format, argptr);
            ::printf("\n");
            break;

        case Logger::PrintMode::STDERR_ONLY:
            fprintf(stderr, "%s|%s|", MTS::Text::time(MTS::System::timeMicros()).c_str(), label);
            vfprintf(stderr, format, argptr);
            fprintf(stderr, "\n");
            break;

        case Logger::PrintMode::FILE_ONLY:
            fprintf(m_pFile, "%s|%s| ", MTS::Text::time(MTS::System::timeMicros()).c_str(), label);
            vfprintf(m_pFile, format, argptr);
            fprintf(m_pFile, "\n");
            fflush(m_pFile);
            break;

        case Logger::PrintMode::SYSLOG_ONLY:
            if (level <= Logger::PrintLevel::TRACE_LEVEL) {
                vsyslog(syslogPrintLevelConversion(level), format, argptr);
            }
            break;

        case Logger::PrintMode::STDOUT_AND_FILE: {
            const std::string timestr(MTS::Text::time(MTS::System::timeMicros()));
            va_list argptr2;
            va_copy(argptr2, argptr);
            ::printf("%s|%s|", timestr.c_str(), label);
            vprintf(format, argptr);
            ::printf("\n");
            fprintf(m_pFile, "%s|%s| ", timestr.c_str(), label);
            vfprintf(m_pFile, format, argptr2);
            fprintf(m_pFile, "\n");
            fflush(m_pFile);
            va_end(argptr2);
        }
            break;

        case Logger::PrintMode::STDOUT_AND_SYSLOG: {
            if (level <= Logger::PrintLevel::TRACE_LEVEL) {
                va_list argptr2;
                va_copy(argptr2, argptr);
                vsyslog(syslogPrintLevelConversion(level), format, argptr2);
                va_end(argptr2);
            }
            ::printf("%s|", MTS::Text::time(MTS::System::timeMicros()).c_str());
            ::printf("%s|", label);
            vprintf(format, argptr);
            ::printf("\n");
        }
            break;

        case Logger::PrintMode::STDERR_AND_SYSLOG: {
            if (level <= Logger::PrintLevel::TRACE_LEVEL) {
                va_list argptr2;
                va_copy(argptr2, argptr);
                vsyslog(syslogPrintLevelConversion(level), format, argptr2);
                va_end(argptr2);
            }
            fprintf(stderr, "%s|%s|", MTS::Text::time(MTS::System::timeMicros()).c_str(), label);
            vfprintf(stderr, format, argptr);
            fprintf(stderr, "\n");
        }
            break;

        case Logger::PrintMode::NO_PRINTING:
        default:
            break;

    }
    m_oPrintLock.unlock();
}

void Logger::printfFatal(const char* format, ...) {
    if (isPrintable(PrintLevel::FATAL_LEVEL)) {
        va_list argptr;
        va_start(argptr, format);
        printMessage(PrintLevel::FATAL_LEVEL, PrintLevel::FATAL_LABEL, format, argptr);
        va_end(argptr);
    }
}

void Logger::printfError(const char* format, ...) {
    if (isPrintable(PrintLevel::ERROR_LEVEL)) {
        va_list argptr;
        va_start(argptr, format);
        printMessage(PrintLevel::ERROR_LEVEL, PrintLevel::ERROR_LABEL, format, argptr);
        va_end(argptr);
    }
}

void Logger::printfWarning(const char* format, ...) {
    if (isPrintable(PrintLevel::WARNING_LEVEL)) {
        va_list argptr;
        va_start(argptr, format);
        printMessage(PrintLevel::WARNING_LEVEL, PrintLevel::WARNING_LABEL, format, argptr);
        va_end(argptr);
    }
}

void Logger::printfInfo(const char* format, ...) {
    if (isPrintable(PrintLevel::INFO_LEVEL)) {
        va_list argptr;
        va_start(argptr, format);
        printMessage(PrintLevel::INFO_LEVEL, PrintLevel::INFO_LABEL, format, argptr);
        va_end(argptr);
    }
}

void Logger::printfConfig(const char* format, ...) {
    if (isPrintable(PrintLevel::CONFIG_LEVEL)) {
        va_list argptr;
        va_start(argptr, format);
        printMessage(PrintLevel::CONFIG_LEVEL, PrintLevel::CONFIG_LABEL, format, argptr);
        va_end(argptr);
    }
}

void Logger::printfDebug(const char* format, ...) {
    if (isPrintable(PrintLevel::DEBUG_LEVEL)) {
        va_list argptr;
        va_start(argptr, format);
        printMessage(PrintLevel::DEBUG_LEVEL, PrintLevel::DEBUG_LABEL, format, argptr);
        va_end(argptr);
    }
}

void Logger::printfTrace(const char* format, ...) {
    if (isPrintable(PrintLevel::TRACE_LEVEL)) {
        va_list argptr;
        va_start(argptr, format);
        printMessage(PrintLevel::TRACE_LEVEL, PrintLevel::TRACE_LABEL, format, argptr);
        va_end(argptr);
    }
}

void Logger::printfGeneric(int level, const char* label, const char* format, ...) {
    va_list argptr;
    va_start(argptr, format);
    printMessage(level, label, format, argptr);
    va_end(argptr);
}

void Logger::printf(int level, const char* format, ...) {
    if (isPrintable(level)) {
        va_list argptr;
        va_start(argptr, format);
        m_oPrintLock.lock();
        vprintf(format, argptr);
        m_oPrintLock.unlock();
        va_end(argptr);
    }
}

void Logger::printf(const char* format, ...) {
    if (isPrintable(PrintLevel::MAXIMUM_LEVEL)) {
        va_list argptr;
        va_start(argptr, format);
        m_oPrintLock.lock();
        vprintf(format, argptr);
        m_oPrintLock.unlock();
        va_end(argptr);
    }
}

bool Logger::setup(const PrintMode& mode) {
    m_oPrintLock.lock();
    m_eMode = mode;
    m_oPrintLock.unlock();
    return true;
}

bool Logger::setup(const PrintMode& mode, const std::string& filename) {
    /* close the handle and reopen it each time setup() is called in case
     * we are being used with programs like logrotate, etc
     *
     * if the file is different, switch to the new file */

    m_oPrintLock.lock();
    if (m_pFile) {
        fclose(m_pFile);
    }
    if (m_sFileName != filename) {
        m_sFileName = filename;
    }
    m_pFile = fopen(m_sFileName.c_str(), "a");
    m_iLogFacility = -1;
    m_oPrintLock.unlock();

    if (!m_pFile) {
        fprintf(stderr, "Error opening logfile %s\n", m_sFileName.c_str());
        return false;
    }
    m_eMode = mode;
    return true;
}

bool Logger::setup(const PrintMode& mode, const std::string& ident, const int& option, const int& facility) {
    m_oPrintLock.lock();
    m_pFile = NULL;
    m_sFileName = "";
    m_iLogFacility = facility;
    m_sIdent = ident;
    m_eMode = mode;
    m_oPrintLock.unlock();
    openlog(m_sIdent.c_str(), option, m_iLogFacility);
    return true;
}