/*
 * Copyright (c) 2020.Huawei Technologies Co., Ltd. All rights reserved.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <fstream>
#include <iomanip>
#include <iostream>
#ifdef _WIN32
#include <windows.h>
#include <stdio.h>
#else
#include <sys/time.h>
#endif

#include "FileManager.h"

#include "Log.h"

namespace MatrixAILog
{
    const int TIME_SIZE = 32;
    const int TIME_DIFF = 28800; // 8 hour
    const int BYTES6 = 6;
    const int FILE_SIZE = 52428800; // 50M
    uint32_t Log::logLevel = LOG_LEVEL_INFO;
    std::vector<std::string> Log::levelString{"[Debug]", "[Info ]", "[Warn ]", "[Error]", "[Fatal]"};
    std::mutex Log::mutex;
    std::string Log::logFile = "./Logs/log.log"; // default log file
    std::string Log::logFileBak = "./Logs/log.log.bak";
    std::string Log::logFileBakPath = "";

    Log::Log(std::string file, std::string function, int line, uint32_t level)
        : myLevel_(level), file_(file), function_(function), line_(line)
    {
    }

    Log::~Log()
    {
        if (myLevel_ >= logLevel)
        {
            std::lock_guard<std::mutex> locker(mutex);
            // cout to screen
            std::cout << ss_.str() << std::endl;
            // log to the file
            CreateDirRecursivelyByFile(logFile);
            std::ofstream fs(logFile, std::ios::app);
            if (!fs)
            {
                std::cout << "open file " << logFile << " fail" << std::endl;
                return;
            }
            fs.seekp(0, fs.end);
            size_t dstFileSize = fs.tellp();
            fs << ss_.str() << std::endl;
            fs.close();

            if (dstFileSize < FILE_SIZE)
            {
                return;
            }
            // dstFileSize >= FILE_SIZE

            // "date_格式[yyyy-mm-dd HH:MM:SS:"改为"yyyymmmdd_HHMMSS"
            if (!logFileBakPath.empty())
            {
                CreateDirRecursivelyByFile(logFileBakPath);
                std::string strData;
                for (int i = 0; i < date_.size(); i++)
                {
                    if (date_[i] != '-' && date_[i] != ':' && date_[i] != '[')
                    {
                        if (date_[i] == ' ')
                        {
                            strData += "_";
                            continue;
                        }
                        strData += date_[i];
                    }
                }
                logFileBak = logFileBakPath + strData + "_bak.txt";
            }

            if (access(logFileBak.c_str(), 0) == APP_ERR_OK)
            {
                APP_ERROR ret = remove(logFileBak.c_str());
                if (ret != APP_ERR_OK)
                {
                    std::cout << "remove " << logFileBak << " failed." << std::endl;
                    return;
                }
            }
            APP_ERROR ret = rename(logFile.c_str(), logFileBak.c_str());
            if (ret != APP_ERR_OK)
            {
                std::cout << "rename " << logFile << " failed." << std::endl;
                return;
            }
        }
    };

    std::ostringstream &Log::Stream()
    {
        if (myLevel_ >= logLevel)
        {
            long int usValue = 0;
#ifndef _WIN32
            struct timeval time = {0, 0};
            gettimeofday(&time, nullptr);
            time_t timep = time.tv_sec + TIME_DIFF;
            struct tm *ptm = gmtime(&timep);
            char timeString[TIME_SIZE] = {0};
            strftime(timeString, TIME_SIZE, "[%F %X:", ptm);
            usValue = time.tv_usec;
#else
            SYSTEMTIME sysTimes;
            GetLocalTime(&sysTimes);
            std::string timeString;
            timeString = '[' + std::to_string(sysTimes.wYear) + '-' + std::to_string(sysTimes.wMonth) + '-' +
                         std::to_string(sysTimes.wDay) + " " + std::to_string(sysTimes.wHour) + ':' +
                         std::to_string(sysTimes.wMinute) + ':' + std::to_string(sysTimes.wSecond) + ':';
            uint32_t msToUs = 1000;
            usValue = sysTimes.wMilliseconds * msToUs;
#endif
            date_ = timeString;
            ss_.fill('0');
            ss_ << levelString[myLevel_] << timeString << std::setw(BYTES6) << usValue << "]";

            std::string fileName = file_.substr(file_.rfind('/') + 1);
            ss_ << "[" << fileName << " " << function_ << ":" << line_ << "] ";
        }
        return ss_;
    }

    void Log::LogDebugOn()
    {
        logLevel = LOG_LEVEL_DEBUG;
        return;
    }
    void Log::LogInfoOn()
    {
        logLevel = LOG_LEVEL_INFO;
        return;
    }
    void Log::LogWarnOn()
    {
        logLevel = LOG_LEVEL_WARN;
        return;
    }
    void Log::LogErrorOn()
    {
        logLevel = LOG_LEVEL_ERROR;
        return;
    }
    void Log::LogFatalOn()
    {
        logLevel = LOG_LEVEL_FATAL;
        return;
    }
    void Log::LogAllOn()
    {
        logLevel = LOG_LEVEL_DEBUG;
        return;
    }
    void Log::LogAllOff()
    {
        logLevel = LOG_LEVEL_NONE;
        return;
    }
    void Log::SetLogLevel(const std::string &log_level)
    {
        if(log_level == "DEBUG")
        {
            logLevel = LOG_LEVEL_DEBUG;
        }
        else if(log_level == "INFO")
        {
            logLevel = LOG_LEVEL_INFO;
        }
        else if(log_level == "WARN")
        {
            logLevel = LOG_LEVEL_WARN;
        }
        else if(log_level == "ERROR")
        {
            logLevel = LOG_LEVEL_ERROR;
        }
        else if(log_level == "FATAL")
        {
            logLevel = LOG_LEVEL_FATAL;
        }
        else if(log_level == "NONE")
        {
            logLevel = LOG_LEVEL_NONE;
        }
    }
    void Log::SetLogFile(const std::string &log_file, const std::string &logfile_bakpath)
    {
        if (!log_file.empty())
        {
            logFile = log_file;
            logFileBak = log_file + ".bak";
        }
        //设置了日志备份路径，则按格式yyyymmdd_HHMMSS_bak.txt备份到指定目录下。
        if (!logfile_bakpath.empty())
        {
            logFileBakPath = logfile_bakpath;
            if (logFileBakPath.back() != '/')
            {
                logFileBakPath += "/";
            }
        }
    }

    void Log::SetLogFile(const std::string &log_file)
    {
        if (!log_file.empty())
        {
            logFile = log_file;
            logFileBak = log_file + ".bak";
        }
    }

#define LOG_DEBUG Log(__FILE__, __FUNCTION__, __LINE__, MatrixAILog::LOG_LEVEL_DEBUG)
#define LOG_INFO Log(__FILE__, __FUNCTION__, __LINE__, MatrixAILog::LOG_LEVEL_INFO)
#define LOG_WARN Log(__FILE__, __FUNCTION__, __LINE__, MatrixAILog::LOG_LEVEL_WARN)
#define LOG_ERROR Log(__FILE__, __FUNCTION__, __LINE__, MatrixAILog::LOG_LEVEL_ERROR)
#define LOG_FATAL Log(__FILE__, __FUNCTION__, __LINE__, MatrixAILog::LOG_LEVEL_FATAL)
} // namespace MatrixAILog