//
// Log.cpp
//
// 他のRaymクラス群やライブラリとの依存関係は無いように実装する。
// 全体として冗長になってもよいので、このファイルのみで完結すること
//
#include <stdio.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <io.h>
#include <share.h>
#include <windows.h>
#include <direct.h>
#include <time.h>
#include <Raym/Log.h>


namespace Raym
{

static const int LOG_SYS_NUM_MIN =  2;  // システム上の最小ログファイル数
static const int LOG_SYS_NUM_MAX = 64;  // システム上の最大ログファイル数
int              LOG_NUM_MAX     =  8;  // ログファイル数の設定値

#if defined(_MSC_VER) || defined(_MSC_EXTENSIONS)
static const __time64_t DELTA_EPOCH_IN_MICROSECS = 11644473600000000Ui64;
#else
static const __time64_t DELTA_EPOCH_IN_MICROSECS = 11644473600000000ULL;
#endif

class LogMutex
{
private:
    CRITICAL_SECTION _cs;

public:
    LogMutex()
    {
        InitializeCriticalSection(&_cs);
    }
    ~LogMutex()
    {
        DeleteCriticalSection(&_cs);
    }
    void lock()
    {
        EnterCriticalSection(&_cs);
    }
    void unlock()
    {
        LeaveCriticalSection(&_cs);
    }
};

static LogMutex     log_mutex_;           // 排他制御用
static FILE *       log_fp_       = NULL; // ログファイルのファイルポインタ
static char *       log_dir_      = NULL; // ログ格納ディレクトリ
static char *       log_filename_ = NULL; // ログファイル名 
static char *       log_create_   = NULL; // ログファイルの作成日

void Log(const char *format, ...)
{
    // ロック
    log_mutex_.lock();

    // ログ格納ディレクトリの確認
    if (log_dir_ == NULL)
    {
        char execute_path[MAX_PATH + 1];
        memset(execute_path, 0x00, sizeof(execute_path));

        TCHAR strbuf[MAX_PATH + 1];
        if (GetModuleFileName(NULL, strbuf, MAX_PATH) != 0)
        {
            errno_t e;
            size_t returnValue;
            e = wcstombs_s(&returnValue, execute_path, sizeof(execute_path), strbuf, _TRUNCATE);
            if (e == 0)
            {
                char *p = strrchr(execute_path, '\\');
                if (p != NULL)
                {
                    // 拡張子(exe)をlogに書き換え
                    size_t len = strlen(execute_path);
                    execute_path[len - 3] = 'l';
                    execute_path[len - 2] = 'o';
                    execute_path[len - 1] = 'g';
                    ++p;
                    log_filename_ = _strdup(p);
                    *p = '\0';
                    strcat_s(execute_path, sizeof(execute_path), "log");
                    log_dir_ = _strdup(execute_path);
                }
            }
        }

        if ((log_dir_ == NULL) || (log_filename_ == NULL))
        {
            // ディレクトリパスが取得できなかった
            MessageBox(NULL, TEXT("ディレクトリパスが取得できませんでした。\nアプリケーションを終了します"), TEXT("ログエラー"), MB_OK);
            exit(1);
        }

        // ディレクトリがあるか確認
        struct __stat64 buffer;
        if (_stat64(log_dir_, &buffer) != 0)
        {
            // パスが存在しないのでディレクトリを作成
            if (_mkdir(log_dir_) != 0)
            {
                // ディレクトリが作成できなかった
                MessageBox(NULL, TEXT("ディレクトリが作成できませんでした。\nアプリケーションを終了します"), TEXT("ログエラー"), MB_OK);
                exit(1);
            }
        }
        else if ((buffer.st_mode & _S_IFDIR) != _S_IFDIR)
        {
            // パスが存在するが、ディレクトリではない
            MessageBox(NULL, TEXT("ログディレクトリが不正です。\nアプリケーションを終了します"), TEXT("ログエラー"), MB_OK);
            exit(1);
        }
    }

    // 現在時刻を取得
    FILETIME ft;
    GetSystemTimeAsFileTime(&ft);

    // EPOCH秒への変換
    __time64_t now_sec;
    __time64_t now_usec;
    now_sec = ft.dwHighDateTime;
    now_sec <<= 32;
    now_sec |= ft.dwLowDateTime;
    now_sec /= 10;  /*convert into microseconds*/
    now_sec -= DELTA_EPOCH_IN_MICROSECS;
    now_usec = (now_sec % 1000000UL);
    now_sec = now_sec / 1000000UL;

    // 
    struct tm now;
    _localtime64_s(&now, &now_sec);
    char today[11];
    sprintf_s(today, 11, "%04d/%02d/%02d", now.tm_year + 1900, now.tm_mon + 1, now.tm_mday);

    // ログファイルのパス
    char log_file_path[MAX_PATH + 1];
    strcpy_s(log_file_path, log_dir_);
    strcat_s(log_file_path, "\\");
    strcat_s(log_file_path, log_filename_);

    // ログファイルの作成日を確認
    if (log_create_ == NULL)
    {
        // ログファイルを開いてみる
        int fd;
        errno_t err = _sopen_s(&fd, log_file_path, (_O_BINARY | _O_RDONLY), _SH_DENYWR, _S_IREAD);
        if (err == 0)
        {
            // 先頭の10バイトを読み込む
            char tmp[11];
            _read(fd, tmp, 10);
            tmp[10] = '\0';
            log_create_ = _strdup(tmp);
            // 閉じる
            _close(fd);
        }
        else
        {
            log_create_ = _strdup(today);
        }
    }

    // ログファイルの作成日と今日を比較
    if (strcmp(log_create_, today) != 0)
    {
        // 日が変わっているのでローテーション必要

        // ログファイルを開いているなら先に閉じる
        if (log_fp_ != NULL)
        {
            fclose(log_fp_);
            log_fp_ = NULL;
        }

        // 設定値の確認
        if (LOG_NUM_MAX < LOG_SYS_NUM_MIN)
        {
            LOG_NUM_MAX = LOG_SYS_NUM_MIN;
        }
        if (LOG_NUM_MAX > LOG_SYS_NUM_MAX)
        {
            LOG_NUM_MAX = LOG_SYS_NUM_MAX;
        }

        // ローテーション実施
        char log_file_path_tmp[MAX_PATH + 1];
        char log_file_path_tmp2[MAX_PATH + 1];
        for (int i = LOG_SYS_NUM_MAX - 1; i >= 0; i -= 1)
        {
            sprintf_s(log_file_path_tmp, "%s.%d", log_file_path, i);
            if (i >= LOG_NUM_MAX - 1)
            {
                // delete
                remove(log_file_path_tmp);
            }
            else
            {
                // hogehoge.log.X -> hogehoge.log.(X+1)
                sprintf_s(log_file_path_tmp2, "%s.%d", log_file_path, (i + 1));
                rename(log_file_path_tmp, log_file_path_tmp2);
            }
        }

        // hogehoge.log -> hogehoge.log.0
        rename(log_file_path, log_file_path_tmp);

        // ログ作成日を更新
        free(log_create_);
        log_create_ = _strdup(today);
    }

    // ログファイルがなければ開く
    if (log_fp_ == NULL)
    {
#if 0
        if (fopen_s(&log_fp_, log_file_path, "a") != 0)
        {
            // ログファイルが作成できない
            MessageBox(NULL, TEXT("ログファイルが作成できません。\nアプリケーションを終了します"), TEXT("ログエラー"), MB_OK);
            exit(1);
        }
#else
        int fd;
        errno_t err = _sopen_s(&fd, log_file_path, (_O_WRONLY | _O_CREAT | _O_APPEND | _O_TEXT), _SH_DENYWR, (_S_IREAD | _S_IWRITE));
        if (err == 0)
        {
            log_fp_ = _fdopen(fd, "a");
        }
        else
        {
            MessageBox(NULL, TEXT("ログファイルが開けねー。\nアプリケーションを終了します"), TEXT("ログエラー"), MB_OK);
        }
        if (log_fp_ == NULL)
        {
            // ログファイルが作成できない
            MessageBox(NULL, TEXT("ログファイルが作成できません。\nアプリケーションを終了します"), TEXT("ログエラー"), MB_OK);
            exit(1);
        }
#endif
    }

    // ログを書き込み
    fprintf(log_fp_, "%04d/%02d/%02d %02d:%02d:%02d.%06d ",
            now.tm_year + 1900, now.tm_mon + 1, now.tm_mday, now.tm_hour, now.tm_min, now.tm_sec, (int)now_usec);
    va_list ap;
    va_start(ap, format);
    vfprintf_s(log_fp_, format, ap);
    va_end(ap);

    // フォーマットの最後が改行でなければ
    if (format[strlen(format) - 1] != '\n')
    {
        // 改行しておく
        fprintf(log_fp_, "\n");
    }
    fflush(log_fp_);

    // ロック解除
    log_mutex_.unlock();
}

} // Foundation
