#include "clog_impl.h"
#include "fmt/format.h"
#include <time.h>
#include <QDataStream>
#include <QString>
#include <QFile>
#include <QDir>
#include <QDebug>
#include <QDirIterator>
#include <QFileInfoList>
#include <QFileInfo>
#include <QDateTime>

#define SEGMENTFILE_SIZE (1024 * 1024 * 1)
#define LOGFILE_DIR "..\\LogFile\\"

namespace lp {
FileLogger::FileLogger()
{
    m_bExit = false;
    m_bOpenLog = true;
    m_nSaveDay = 7;
    m_nSegmentSize = SEGMENTFILE_SIZE;
}

FileLogger::~FileLogger()
{
    UnInit();
}

bool FileLogger::Init(bool bOpenLog, int nSaveDay, int nSegmentSize)
{
    if (m_trdTask.joinable()) {
        return false;
    }

    m_bOpenLog = bOpenLog;
    m_nSaveDay = nSaveDay;
    m_nSegmentSize = nSegmentSize * SEGMENTFILE_SIZE;

    return true;
}

bool FileLogger::UnInit()
{
    if (m_trdTask.joinable())
    {
        m_bExit = true;
        m_condTask.notify_one();
        m_trdTask.join();
    }

    return CloseLog();
}

void FileLogger::AddLog(lp::ELogType type, const std::string& info)
{
    if (!m_trdTask.joinable()) {
        StartAnsyLog();
    }

    std::string sHeader = "I";
    switch (type) {
    case lp::kDebug:
        sHeader = "D";
        break;
    case lp::kWarn:
        sHeader = "W";
        break;
    case lp::kError:
        sHeader = "E";
        break;
    case lp::kFatal:
        sHeader = "T";
        break;
    default:
        break;
    }

    std::unique_lock<std::mutex> lkAuto(m_lkTask);
    m_deqLogs.push_back(std::make_pair<>("", fmt::format("{} {} {}", ILogST::getDateTime(), sHeader, info).c_str()));
    lkAuto.unlock();
    m_condTask.notify_one();
}

void FileLogger::AddLog(lp::ELogType type, const std::string& info, const char* sFile, int iLine)
{
    const char* base = strrchr(sFile, '\\');
    if (!base) {
        base = strrchr(sFile, '/');
    }

    AddLog(type, fmt::format("[{}:{}] {}", base ? (base + 1) : sFile, iLine, info));
}

void FileLogger::AddLog(ILogST& stLog, const std::string& moduleName,
                        const std::string& sFileName, int line, lp::EWriteMode eWritype)
{
    if (!m_trdTask.joinable()) {
        StartAnsyLog();
    }

    std::string filename = GetFileName(sFileName);
    stLog << filename << line;
    std::unique_lock<std::mutex> lkAuto(m_lkTask);
    if (eWritype == EWriteMode::kSync) {
        //同步等待写日志
        //WriteLog(stLog, filename, line, modefiled);
        m_deqLogs.push_front(std::make_pair<>(moduleName, stLog.str()));
    }
    else {
        m_deqLogs.push_back(std::make_pair<>(moduleName, stLog.str()));
    }
    lkAuto.unlock();

    m_condTask.notify_one();
}

void FileLogger::AddLog(const std::string& info, const std::string& moduleName)
{
    {
        std::lock_guard<std::mutex> lkAuto(m_lkTask);
        m_deqLogs.push_front(std::make_pair<>(moduleName, fmt::format("{} {}", QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss zzz").toStdString(), info)));
    }

    m_condTask.notify_one();
}

void FileLogger::SetLogLevel(ELogType eType)
{
    m_eLogType = eType;
}

std::string FileLogger::GetFileName(const std::string& filepath)
{
    QFileInfo file(filepath.c_str());

    return file.fileName().toStdString();
}

void FileLogger::StartAnsyLog()
{
    if (!m_trdTask.joinable())
    {
        m_trdTask = std::thread([this]() {
            while (!m_bExit) {
                //锁作用域，提前释放
                {
                    std::unique_lock<std::mutex> lkAuto(m_lkTask);

                    m_condTask.wait(lkAuto, [this]()->bool {
                        if (m_bExit) {
                            return true;
                        }

                        return m_deqLogs.size() > 0;
                    });

                    if (m_deqLogs.size()) {
                        //先交换日志，再准备写日志
                        std::swap(m_deqLogsWrite, m_deqLogs);
                    }
                }

                if (m_deqLogsWrite.size()) {
                    //写完当前日志
                    WriteLog(m_deqLogsWrite);
                    //检查是否分割日志
                    CheckSegmentFile();
                }

                //检查是否删除日志
                CheckClearFile();

                std::this_thread::sleep_for(std::chrono::milliseconds(25));
            }
        });
    }
}

bool FileLogger::OpenLog(pair_file_ofs& spOfs, std::string moduleName)
{
    if (!spOfs.second->is_open()
        && m_bOpenLog)
    {
        std::string running_path = _pgmptr;
        size_t found = running_path.rfind('\\');
        if (found != std::string::npos) {
            running_path = running_path.substr(0, found + 1);
            running_path.append(LOGFILE_DIR + moduleName + "\\" + ILogST::getDate() + "\\");
        }

        QDir dir(running_path.c_str());
        if (!dir.exists()) {
            dir.mkpath(running_path.c_str());
        }

        std::string fileName = running_path + "\\Log_" + ILogST::getTime() + ".txt";
        spOfs.second->open(fileName.c_str());
        if (!spOfs.second->is_open()) {
            return false;
        }
        spOfs.first = fileName;
    }

    return m_bOpenLog;
}

bool FileLogger::CloseLog()
{
    for (auto it = m_ofsMapStream.begin(); it != m_ofsMapStream.end(); ++it) {
        pair_file_ofs& sp_filed = it->second;
        if (sp_filed.second->is_open()) {
            sp_filed.second->close();
        }
    }

    return true;
}

bool FileLogger::CloseLog(std::shared_ptr<std::ofstream>& spOfs)
{
    if (spOfs->is_open()) {
        spOfs->close();
    }

    return true;
}

void FileLogger::Flush()
{
    for (auto it = m_ofsMapStream.begin(); it != m_ofsMapStream.end(); ++it) {
        pair_file_ofs& sp_filed = it->second;
        if (sp_filed.second->is_open()) {
            sp_filed.second->flush();
        }
    }
}

void FileLogger::Flush(std::shared_ptr<std::ofstream>& spOfs)
{
    if (spOfs->is_open()) {
        spOfs->flush();
    }
}

void FileLogger::WriteLog(std::deque<pair_filed_log>& deqLogs)
{
    while (deqLogs.size()) {
        pair_filed_log& stLog = deqLogs.front();
        auto it = m_ofsMapStream.find(stLog.first);
        if (it == m_ofsMapStream.end()) {
            m_ofsMapStream[stLog.first] = std::make_pair<>(stLog.first, std::make_shared<std::ofstream>());
            it = m_ofsMapStream.find(stLog.first);
        }

        if (!it->second.second->is_open())
        {
            if (!OpenLog(it->second, stLog.first))
            {
                continue;
            }
        }

        (*it->second.second.get()) << stLog.second.c_str() << "\n";
        deqLogs.pop_front();
    }

    Flush();
}

void FileLogger::WriteLog(std::ostringstream& stLog, const std::string& name, int line, const std::string& modefiled)
{
    auto it = m_ofsMapStream.find(modefiled);
    if (it == m_ofsMapStream.end()) {
        std::shared_ptr<std::ofstream> _spOfs;
        m_ofsMapStream[modefiled] = std::make_pair<>(modefiled, _spOfs);
        it = m_ofsMapStream.find(modefiled);
    }

    if (!it->second.second->is_open())
    {
        if (OpenLog(it->second, modefiled))
        {
            (*it->second.second.get()) << name << line << stLog.str().c_str() << "\n";
            Flush(it->second.second);
        }
    }
}

bool FileLogger::CheckSegmentFile()
{
    for (auto it = m_ofsMapStream.begin(); it != m_ofsMapStream.end(); ++it)
    {
        if (it->second.second->is_open()) {
            std::ifstream in(it->second.first);
            if (!in.is_open()) {
                continue;
            }
            in.seekg(0, std::ios_base::end);
            std::streampos sp = in.tellg();

            if (sp >= m_nSegmentSize)
            {
                CloseLog(it->second.second);

                return OpenLog(it->second, it->first);
            }
        }
    }

    return false;
}

bool FileLogger::CheckClearFile()
{
    bool bClear = false;

    std::string running_path = _pgmptr;
    size_t found = running_path.rfind('\\');
    if (found != std::string::npos) {
        running_path = running_path.substr(0, found + 1);
        running_path.append(LOGFILE_DIR);
    }

    QDirIterator it(running_path.c_str(), QDir::Files, QDirIterator::Subdirectories);
    time_t curTime = time(nullptr);
    while (it.hasNext()) {
        long tInterval = 0;
        if (it.fileInfo().exists()
            && it.fileInfo().isFile())
        {
            QDateTime lastModified = it.fileInfo().lastModified();
            QDateTime currentTime = QDateTime::currentDateTime();
            int dayDiff = currentTime.daysTo(lastModified);
            if (dayDiff > m_nSaveDay)
            {
                QFile::remove(it.fileInfo().absoluteFilePath());

                bClear = true;
            }
        }

        it.next();
    }

    return bClear;
}
}