// 简易日志

#ifndef __SIMPLE_LOG_H__
#define __SIMPLE_LOG_H__

#include <time.h>
#include <string>
#include <map>
#include <thread>
#include <mutex>
#if(defined WIN32 || defined _WIN32)
#include <Windows.h>
#else
#include <sys/time.h>
#endif

using namespace std;

/** 简易日志
 *
 * 不依赖于其他第三方库，只需要包含一个头文件就可以使用。提供了4种日志级别，包括INFO,DEBUG,WARN和ERROR。
 *
 * 示例1:
    //
 初始化日志，在./Log/目录下创建两个日志文件log1.log和log2.log(注意：目录./Log/需要存在，否则日志创建失败)
    LogManager::GetInstance()->Initialize("./Log/","log1");
    LogManager::GetInstance()->Initialize("./Log/","log2");

    // 写日志
    string log = "Hello World";
    LOG_INFO(LogManager::GetInstance()->GetLogFile("log1"), "%s\n", log.c_str()); // 写入log1.log
    LOG_INFO(LogManager::GetInstance()->GetLogFile("log2"), "%s\n", log.c_str()); // 写入log2.log

    // 关闭日志
    LogManager::GetInstance()->Close("log1");
    LogManager::GetInstance()->Close("log2");

  * 示例2:
    // 将日志输出到控制台
    string log = "Hello World";
    LOG_INFO(stdout, "%s\n", log.c_str());

  * 注意：
    1. 需要C++11
    2. 多线程的时候需要加锁(打开#define LOG_MUTEX)，否则会导致日志显示混乱

*/

// #define LOG_MUTEX  // 加锁

class LogManager
{
    private:
    LogManager() {}

    public:
    ~LogManager() {}
    inline void Initialize(const string& parentPath, const string& logName)
    {
        // 日志名为空表示输出到控制台
        if(logName.size() == 0)
            return;

        // 查找该日志文件，如果没有则创建
        std::map<string, FILE*>::const_iterator iter = logMap.find(logName);
        if(iter == logMap.end())
        {
            string pathOfLog = parentPath + logName + ".log";
            FILE* logFile    = fopen(pathOfLog.c_str(), "a"); // w:覆盖原有文件,a:追加
            if(logFile != NULL)
            {
                logMap.insert(std::make_pair(logName, logFile));
            }
        }
    }
    inline FILE* GetLogFile(const string& logName)
    {
        std::map<string, FILE*>::const_iterator iter = logMap.find(logName);
        if(iter == logMap.end())
        {
            return NULL;
        }

        return (*iter).second;
    }
    inline void Close(const string& logName)
    {
        std::map<string, FILE*>::const_iterator iter = logMap.find(logName);
        if(iter == logMap.end())
        {
            return;
        }

        fclose((*iter).second);
        logMap.erase(iter);
    }
    inline std::mutex& GetLogMutex() { return logMutex; }

    // Singleton
    static LogManager* GetInstance()
    {
        static LogManager logManager;
        return &logManager;
    }

    private:
    std::map<string, FILE*> logMap;
    std::mutex logMutex;
};

#ifdef LOG_MUTEX
#define LOCK LogManager::GetInstance()->GetLogMutex().lock()
#define UNLOCK LogManager::GetInstance()->GetLogMutex().unlock()
#else
#define LOCK
#define UNLOCK
#endif

// log time
typedef struct _LogTime
{
    string year;
    string month;
    string day;
    string hour;
    string minute;
    string second;
    string millisecond; // ms
    string microsecond; // us
    string weekDay;
} LogTime;

inline LogTime GetTime()
{
    LogTime currentTime;

#if(defined WIN32 || defined _WIN32)
    SYSTEMTIME systemTime;
    GetLocalTime(&systemTime);

    char temp[8] = {0};
    sprintf(temp, "%04d", systemTime.wYear);
    currentTime.year = string(temp);
    sprintf(temp, "%02d", systemTime.wMonth);
    currentTime.month = string(temp);
    sprintf(temp, "%02d", systemTime.wDay);
    currentTime.day = string(temp);
    sprintf(temp, "%02d", systemTime.wHour);
    currentTime.hour = string(temp);
    sprintf(temp, "%02d", systemTime.wMinute);
    currentTime.minute = string(temp);
    sprintf(temp, "%02d", systemTime.wSecond);
    currentTime.second = string(temp);
    sprintf(temp, "%03d", systemTime.wMilliseconds);
    currentTime.millisecond = string(temp);
    sprintf(temp, "%d", systemTime.wDayOfWeek);
    currentTime.weekDay = string(temp);
#else
    struct timeval tv;
    struct tm* p;
    gettimeofday(&tv, NULL);
    p = localtime(&tv.tv_sec);

    char temp[8] = {0};
    sprintf(temp, "%04d", 1900 + p->tm_year);
    currentTime.year = string(temp);
    sprintf(temp, "%02d", 1 + p->tm_mon);
    currentTime.month = string(temp);
    sprintf(temp, "%02d", p->tm_mday);
    currentTime.day = string(temp);
    sprintf(temp, "%02d", p->tm_hour);
    currentTime.hour = string(temp);
    sprintf(temp, "%02d", p->tm_min);
    currentTime.minute = string(temp);
    sprintf(temp, "%02d", p->tm_sec);
    currentTime.second = string(temp);
    sprintf(temp, "%03d", (int)(tv.tv_usec / 1000));
    currentTime.millisecond = string(temp);
    sprintf(temp, "%03d", (int)(tv.tv_usec % 1000));
    currentTime.microsecond = string(temp);
    sprintf(temp, "%d", p->tm_wday);
    currentTime.weekDay = string(temp);
#endif
    return currentTime;
}

#define LOG_TIME(logFile)                               \
    do                                                  \
    {                                                   \
        LogTime currentTime = GetTime();                \
        fprintf(((logFile == NULL) ? stdout : logFile), \
                "%s-%s-%s %s:%s:%s.%s\t",               \
                currentTime.year.c_str(),               \
                currentTime.month.c_str(),              \
                currentTime.day.c_str(),                \
                currentTime.hour.c_str(),               \
                currentTime.minute.c_str(),             \
                currentTime.second.c_str(),             \
                currentTime.millisecond.c_str());       \
    } while(0)

#define LOG_INFO(logFile, logInfo, ...)                                          \
    do                                                                           \
    {                                                                            \
        LOCK;                                                                    \
        LOG_TIME(logFile);                                                       \
        fprintf(((logFile == NULL) ? stdout : logFile), "INFO\t");               \
        fprintf(((logFile == NULL) ? stdout : logFile),                          \
                "[%s:%d (%s) ]: ",                                               \
                __FILE__,                                                        \
                __LINE__,                                                        \
                __FUNCTION__);                                                   \
        fprintf(((logFile == NULL) ? stdout : logFile), logInfo, ##__VA_ARGS__); \
        fflush(logFile);                                                         \
        UNLOCK;                                                                  \
    } while(0)

#define LOG_DEBUG(logFile, logInfo, ...)                                         \
    do                                                                           \
    {                                                                            \
        LOCK;                                                                    \
        LOG_TIME(logFile);                                                       \
        fprintf(((logFile == NULL) ? stdout : logFile), "DEBUG\t");              \
        fprintf(((logFile == NULL) ? stdout : logFile),                          \
                "[%s:%d (%s) ]: ",                                               \
                __FILE__,                                                        \
                __LINE__,                                                        \
                __FUNCTION__);                                                   \
        fprintf(((logFile == NULL) ? stdout : logFile), logInfo, ##__VA_ARGS__); \
        fflush(logFile);                                                         \
        UNLOCK;                                                                  \
    } while(0)

#define LOG_ERROR(logFile, logInfo, ...)                                         \
    do                                                                           \
    {                                                                            \
        LOCK;                                                                    \
        LOG_TIME(logFile);                                                       \
        fprintf(((logFile == NULL) ? stdout : logFile), "ERROR\t");              \
        fprintf(((logFile == NULL) ? stdout : logFile),                          \
                "[%s:%d (%s) ]: ",                                               \
                __FILE__,                                                        \
                __LINE__,                                                        \
                __FUNCTION__);                                                   \
        fprintf(((logFile == NULL) ? stdout : logFile), logInfo, ##__VA_ARGS__); \
        fflush(logFile);                                                         \
        UNLOCK;                                                                  \
    } while(0)

#define LOG_WARN(logFile, logInfo, ...)                                          \
    do                                                                           \
    {                                                                            \
        LOCK;                                                                    \
        LOG_TIME(logFile);                                                       \
        fprintf(((logFile == NULL) ? stdout : logFile), "WARN\t");               \
        fprintf(((logFile == NULL) ? stdout : logFile),                          \
                "[%s:%d (%s) ]: ",                                               \
                __FILE__,                                                        \
                __LINE__,                                                        \
                __FUNCTION__);                                                   \
        fprintf(((logFile == NULL) ? stdout : logFile), logInfo, ##__VA_ARGS__); \
        fflush(logFile);                                                         \
        UNLOCK;                                                                  \
    } while(0)

#endif // __SIMPLE_LOG_H__
