/**
 * @file logging.cpp
 * @brief Logging class
 *
 * (c) 2013-2014 by Mega Limited, Auckland, New Zealand
 *
 * This file is part of the MEGA SDK - Client Access Engine.
 *
 * Applications using the MEGA API must present a valid application key
 * and comply with the the rules set forth in the Terms of Service.
 *
 * The MEGA SDK 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.
 *
 * @copyright Simplified (2-clause) BSD License.
 *
 * You should have received a copy of the license along with this
 * program.
 *
 * This file is also distributed under the terms of the GNU General
 * Public License, see http://www.gnu.org/copyleft/gpl.txt for details.
 */

#include "mega/logging.h"

#include <ctime>

namespace mega {

ExternalLogger& getExternalLogger()
{
    // Leaky Singleton to expand the lifetime of the ExternalLogger object.
    // This will prevent the race condition during abnormal exit in multi-threaded environment
    // Note: If SDK was used as a dynamic library, this will cause memory leak,
    //       However, this is a cheap price to pay for solving race conditions.
    // This approach is quite popular in several open-source projects, such as Glog, Chrome etc.
    static ExternalLogger* const instance = new ExternalLogger;
    return *instance;
}

ExclusiveLogger& getExclusiveLogger()
{
    // Same Leaky Singleton
    static ExclusiveLogger* const instance = new ExclusiveLogger;
    return *instance;
}

std::atomic<Logger*> SimpleLogger::logger = &getExternalLogger();

// by the default, display logs with level equal or less than logInfo
std::atomic<LogLevel> SimpleLogger::logCurrentLevel{logInfo};

#ifndef ENABLE_LOG_PERFORMANCE
std::string SimpleLogger::getTime()
{
    char ts[50];
    time_t currentTime = std::time(NULL);
    std::tm tm{};

#ifdef WIN32
    gmtime_s(&tm, &currentTime);
#else
    gmtime_r(&currentTime, &tm);
#endif

    if (std::strftime(ts, sizeof(ts), "%H:%M:%S", &tm)) return ts;

    return {};
}
#endif

std::ostream& operator<< (std::ostream& ostr, const std::error_code &value)
{
    return ostr << value.category().name() << ": " << value.message();
}

std::ostream& operator<< (std::ostream& ostr, const std::system_error &se)
{
    return ostr << se.code().category().name() << ": " << se.what();
}



ExternalLogger::ExternalLogger()
{
    logToConsole = false;
}

ExternalLogger::~ExternalLogger() {}

void ExternalLogger::addMegaLogger(void* id, LogCallback lc)
{
    std::lock_guard<std::recursive_mutex> g(mutex);
    megaLoggers[id] = lc;
}

void ExternalLogger::removeMegaLogger(void* id)
{
    std::lock_guard<std::recursive_mutex> g(mutex);
    megaLoggers.erase(id);
}

void ExternalLogger::setLogToConsole(bool enable)
{
    this->logToConsole = enable;
}

void ExternalLogger::log(const char *time, int loglevel, const char *source, const char *message
#ifdef ENABLE_LOG_PERFORMANCE
    , const char **directMessages = nullptr, size_t *directMessagesSizes = nullptr, unsigned numberMessages = 0
#endif
)
{
    if (!time)
    {
        time = "";
    }

    if (!source)
    {
        source = "";
    }

    if (!message)
    {
        message = "";
    }

    lock_guard<std::recursive_mutex> g(mutex);

    // solve the mystery of why the mutex is recursive
    // if we hit the assert, it's due to logging from inside the processing of the logging?
    assert(!alreadyLogging);
    alreadyLogging = true;


    for (auto& logger : megaLoggers)
    {
        logger.second(time, loglevel, source, message
#ifdef ENABLE_LOG_PERFORMANCE
            , directMessages, directMessagesSizes, numberMessages
#endif
        );

        if (useOnlyFirstMegaLogger)
        {
            break;
        }
    }

    if (logToConsole)
    {
        std::cout << "[" << time << "][" << SimpleLogger::toStr((LogLevel)loglevel) << "] ";
        if (message) std::cout << message;
#ifdef ENABLE_LOG_PERFORMANCE
        for (unsigned i = 0; i < numberMessages; ++i)
        {
            std::cout.write(directMessages[i],
                            static_cast<std::streamsize>(directMessagesSizes[i]));
        }
#endif
        std::cout << std::endl;
    }
    alreadyLogging = false;
}


void ExclusiveLogger::log(const char *time, int loglevel, const char *source, const char *message
#ifdef ENABLE_LOG_PERFORMANCE
    , const char **directMessages = nullptr, size_t *directMessagesSizes = nullptr, unsigned numberMessages = 0
#endif
)
{
    if (!time)
    {
        time = "";
    }

    if (!source)
    {
        source = "";
    }

    if (!message)
    {
        message = "";
    }

    exclusiveCallback(time, loglevel, source, message
#ifdef ENABLE_LOG_PERFORMANCE
            , directMessages, directMessagesSizes, numberMessages
#endif
    );
}

} // namespace
