#include "pqlog.h"
#include <QTime>
#include <QEvent>
#include <QThread>
#include <QCoreApplication>
#include <iostream>
#include <QDir>
#include <QDate>
#include <memory>
#include <QDebug>
#include <QDateTime>
#include "core/pqapppath.h"

namespace PQ {

static const char * LevelName[6] = {"Test","Debug","Info","Warning","Critical","Fatal"};

void customMessageHandler(QtMsgType type, const QMessageLogContext &context,const QString & msg)
{
    auto log = PQLogManger::this_()->qDebugLog();
    if(log == nullptr) return;
    switch (type) {
    //调试信息提示
    case QtDebugMsg:
        if (log->checkLevel(DebugMsg)) {
            LogMessage(log,context.file,context.line,DebugMsg).stream()  << msg ;
        }
        break;
        //一般的warning提示
    case QtWarningMsg:
        if (log->checkLevel(WarningMsg)) {
            LogMessage(log,context.file,context.line,WarningMsg).stream() << msg ;
        }
        break;
        //严重错误提示
    case QtCriticalMsg:
        if (log->checkLevel(CriticalMsg)) {
            LogMessage(log,context.file,context.line,CriticalMsg).stream() << msg ;
        }
        break;
        //致命错误提示
    case QtFatalMsg:
        if (log->checkLevel(FatalMsg)) {
            LogMessage(log,context.file,context.line,FatalMsg).stream() << msg ;
        }
        break;
    case QtInfoMsg :
        if (log->checkLevel(InfoMsg)) {
            LogMessage(log,context.file,context.line,InfoMsg).stream() << msg ;
        }
        break;
    }
}

PQLogManger::PQLogManger()
{
    _thread.start();
    auto nm = QCoreApplication::applicationName();
    _baseLog.reset(new PQLog(nm,STD_OUT));
    _baseLog->moveToThread(&_thread);
    _qtLog.reset(new PQLog("qtdebug",STD_OUT));
    _qtLog->moveToThread(&_thread);
    _logs.insert(nm,_baseLog);
    _logs.insert("qtdebug",_qtLog);
    connect(this,&PQLogManger::updateSavePath,_baseLog.data(),&PQLog::restLog,Qt::QueuedConnection);
    connect(this,&PQLogManger::updateSavePath,_qtLog.data(),&PQLog::restLog,Qt::QueuedConnection);
    setSaveFilePath(AppPath::this_()->logDir().absolutePath());
}

PQLogManger * PQLogManger::this_()
{
    static PQLogManger logManger;
    return &logManger;
}

PQLogManger::~PQLogManger()
{
    _thread.quit();
    _thread.wait(1000);
    if(_thread.isRunning()){
        _thread.terminate();
    }
}

bool PQLogManger::setSaveFilePath(const QString & path)
{
    QDir dir(path);
    if(dir.exists() || dir.mkpath(dir.absolutePath())) {
        QFileInfo info(path);
        if(info.isWritable()){
            path_ = dir.absolutePath();
            emit updateSavePath();
        }
    }
    return false;
}

PQLog * PQLogManger::enbleQtMessage()
{
    qInstallMessageHandler(customMessageHandler);
    return _qtLog.data();
}

PQLog * PQLogManger::getLog(const QString & name)
{
    _locker.lockForRead();
    auto log = _logs.value(name);
    _locker.unlock();
    if(log.isNull()){
        _locker.lockForWrite();
        log = _logs.value(name);
        if(log.isNull()){
            log.reset(new PQLog(name));
            log->moveToThread(&_thread);
            connect(this,&PQLogManger::updateSavePath,log.data(),&PQLog::restLog,Qt::QueuedConnection);
            _logs.insert(name,log);
        }
        _locker.unlock();
    }
    return log.data();
}

class LogEvent : public QEvent
{
public:
    LogEvent(QString && str,LogLevel lv): QEvent(eventType),level(lv),str_(str) {}
    ~LogEvent() {}

    const QString & getString() const {return str_;}

    static QEvent::Type eventType;
    LogLevel level;
private:
    QString str_;
};

QEvent::Type LogEvent::eventType = static_cast<QEvent::Type>(QEvent::registerEventType());

class UpEventOutStatus : public QEvent
{
public:
    UpEventOutStatus(OutState state): QEvent(eventType),state(state) {}
    ~UpEventOutStatus() {}


    static QEvent::Type eventType;
    OutState state;
};

QEvent::Type UpEventOutStatus::eventType = static_cast<QEvent::Type>(QEvent::registerEventType());

LogMessage::LogMessage(PQLog * log,const char * file, int line,LogLevel level) : log(log), ts(&str_),level_(level)
{
    if(log == nullptr || level < log->level) return;
    if(log->writeTime){
        ts << "[" << QTime::currentTime().toString("hh:mm:ss.zzz") << "]:";
    }
    if(log->writeFileInfo) {
        ts << "[@ " << file << " : " << line << "]:";
    }
    if(log->writeLevel){
        ts << "[" << LevelName[level] << "]:";
    }
}

LogMessage::LogMessage(PQLog * log,LogLevel level): log(log), ts(&str_),level_(level)
{
    if(log == nullptr || level < log->level) return;
    if(log->writeTime){
        ts << "[" << QTime::currentTime().toString("hh:mm:ss.zzz") << "]:";
    }
    if(log->writeFileInfo) {
        ts << "[@ ]:";
    }
    if(log->writeLevel){
        ts << "[" << LevelName[level] << "]:";
    }
}

LogMessage::~LogMessage()
{
    if (log != nullptr && level_ >= log->level ) {
        if(log->writeEndLine)
            ts << "\r\n";
        ts.setDevice(nullptr);
        QCoreApplication::postEvent(log,new LogEvent(std::move(str_),level_));
        if (level_ == FatalMsg) {
            QCoreApplication::exit(-1);
        }
    }
}


PQLog::PQLog(const QString & name, OutState state)
        : QObject(nullptr),state_(state),file_(nullptr),_name(name)
{
    level = TestMsg;
    writeTime = true;
    writeFileInfo = true;
    writeLevel = true;
    writeEndLine = true;
    fileCreateType = DayOne;
    ts_.setDevice(nullptr);
    _fileCreateId = -1;
}

PQLog::~PQLog()
{
    releaseLogFile();

}

void PQLog::setOutState(OutState outState)
{
    QCoreApplication::postEvent(this,new UpEventOutStatus(outState));
}

void PQLog::restLog()
{
    if(state_ == File){
        releaseLogFile();
    }
}

void PQLog::releaseLogFile()
{
  ts_.setDevice(nullptr);
  if(file_){
      file_->close();
      delete file_;
      file_ = nullptr;
  }
}

void PQLog::customEvent(QEvent * event)
{
    if(event->type() == UpEventOutStatus::eventType){
        UpEventOutStatus * ev = static_cast<UpEventOutStatus *>(event);
        if(state_ != ev->state) {
            state_ = ev->state;
            releaseLogFile();

        }
    } else if (event->type() == LogEvent::eventType) {
        LogEvent * ev = static_cast<LogEvent *>(event);
        if(ev->level >= level) {
            switch (state_) {
            case STD_OUT :{
                std::cout << ev->getString().toStdString();
                return;
            }
                break;
            case STD_ERROR:{
                std::cerr << ev->getString().toStdString();
                return;
            }
                break;
            case File : {
                if(file_){
                    int id = _fileCreateId;
                    switch (fileCreateType) {
                    case DayOne: {
                        id = QDate::currentDate().day();
                    }
                        break;
                    case HourOne:
                        id = QTime::currentTime().hour();
                        break;
                    case MonthOne:
                        id = QDate::currentDate().month();
                        break;
                    default:
                        id = _fileCreateId;
                        break;
                    }
                    if(id != _fileCreateId)
                        releaseLogFile();
                }
                if(file_ == nullptr){
                    QString fname = PQLogManger::this_()->getSaveFilePath() + "/" + _name;
                    switch (fileCreateType) {
                    case OneStartOne:{
                        auto pid = QCoreApplication::applicationPid();
                        fname += "_";
                        fname += QDateTime::currentDateTime().toString("yyyy-MM-dd.hh-mm-ss.zzz");
                        fname += "_";
                        fname += QString::number(pid);
                        _fileCreateId = 0;
                    }
                    case DayOne: {
                        auto dt = QDate::currentDate();
                        fname += "_";
                        fname += dt.toString("yyyy-MM-dd");
                        _fileCreateId = dt.day();
                    }
                        break;
                    case HourOne:{
                        auto dt = QDateTime::currentDateTime();
                        fname += "_";
                        fname += dt.toString("yyyy-MM-dd.hh");
                        _fileCreateId = dt.time().hour();
                    }
                        break;
                    case MonthOne:{
                        auto dt = QDate::currentDate();
                        fname += "_";
                        fname += dt.toString("yyyy-MM");
                        _fileCreateId = dt.month();
                    }
                        break;
                    default:
                        break;
                    }
                    fname += ".logs";
                    file_ = new QFile(fname,this);
                    file_->open(QFile::Append);
                    ts_.setDevice(file_);
                }
            }
            break;
        default:
            break;
        }
        if (ts_.device() != nullptr) {
            ts_ << ev->getString();
            ts_.flush();
        }
        }
    }
    event->accept();

}

}//namespace PQLog
