/*
 * Copyright (c) 2023 Huawei Device Co., Ltd.
 * 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 "texgine/utils/logger.h"

#include <fstream>
#include <iomanip>
#include <iostream>
#include <unistd.h>

#ifdef BUILD_NON_SDK_VER
#include <sys/syscall.h>
#define GET_TID() syscall(__NR_gettid)
#else
#ifdef _WIN32
#include <windows.h>
#define GET_TID GetCurrentThreadId
#endif

#if defined(BUILD_SDK_MAC) || defined(BUILD_SDK_IOS)
#include <stdlib.h>
#include <sys/syscall.h>
#define GET_TID() syscall(SYS_thread_selfid)
#elif defined(BUILD_SDK_ANDROID)
#define GET_TID() gettid()
#else
#ifdef __gnu_linux__
#include <sys/types.h>
#include <sys/syscall.h>
#define GET_TID() syscall(SYS_gettid)
#endif
#endif

#ifdef __APPLE__
#define getpid getpid
#endif

#ifdef ERROR
#undef ERROR
#endif
#endif

#ifdef LOGGER_NO_COLOR
#define IF_COLOR(x)
#else
#define IF_COLOR(x) x
#endif

namespace OHOS {
namespace Rosen {
namespace TextEngine {
namespace {
const char *GetLevelStr(enum Logger::LOG_LEVEL level)
{
    switch (level) {
        case Logger::LOG_LEVEL::DEBUG:
            return IF_COLOR("\033[37m") "D" IF_COLOR("\033[0m");
        case Logger::LOG_LEVEL::INFO:
            return IF_COLOR("\033[36m") "I" IF_COLOR("\033[0m");
        case Logger::LOG_LEVEL::WARN:
            return IF_COLOR("\033[33m") "W" IF_COLOR("\033[0m");
        case Logger::LOG_LEVEL::ERROR:
            return IF_COLOR("\033[31m") "E" IF_COLOR("\033[0m");
        case Logger::LOG_LEVEL::FATAL:
            return IF_COLOR("\033[4;31m") "F" IF_COLOR("\033[0m");
    }
    return "?";
}
} // namespace

void Logger::SetToNoReturn(Logger &logger, enum LOG_PHASE phase)
{
    logger.return_ = false;
}

void Logger::SetToContinue(Logger &logger, enum LOG_PHASE phase)
{
    logger.continue_ = true;
}

void Logger::OutputByStdout(Logger &logger, enum LOG_PHASE phase)
{
    if (phase == LOG_PHASE::BEGIN) {
        return;
    }

    // LOG_PHASE::END
    if (logger.continue_ == false) {
        std::cout << GetLevelStr(logger.GetLevel()) << " ";
    }

    std::cout << logger.str();
    if (logger.return_) {
        std::cout << std::endl;
    }
}

void Logger::OutputByStderr(Logger &logger, enum LOG_PHASE phase)
{
    if (phase == LOG_PHASE::BEGIN) {
        return;
    }

    // LOG_PHASE::END
    if (logger.continue_ == false) {
        std::cerr << GetLevelStr(logger.GetLevel()) << " ";
    }

    std::cerr << logger.str();
    if (logger.return_) {
        std::cerr << std::endl;
    }
}

void Logger::OutputByFileLog(Logger &logger, enum LOG_PHASE phase)
{
    struct FileLogData {
        const char *filename;
    };
    auto data = logger.GetData<struct FileLogData>();
    if (phase == LOG_PHASE::BEGIN) {
        auto filename = va_arg(logger.GetVariousArgument(), const char *);
        data->filename = filename;
        return;
    }

    // LOG_PHASE::END
    std::ofstream ofs(data->filename, std::ofstream::out | std::ofstream::app);
    if (!ofs) {
        // open failed, errno
        return;
    }

    if (logger.continue_ == false) {
        ofs << GetLevelStr(logger.GetLevel()) << " ";
    }

    ofs << logger.str();
    if (logger.return_) {
        ofs << std::endl;
    }
    ofs.close();
}

void Logger::AppendFunc(Logger &logger, enum LOG_PHASE phase)
{
    if (phase == LOG_PHASE::BEGIN) {
        logger << IF_COLOR("\033[34m");
        logger.AlignFunc();
        logger << logger.GetFunc() << IF_COLOR("\033[0m") " ";
    }
}

void Logger::AppendFuncLine(Logger &logger, enum LOG_PHASE phase)
{
    if (phase == LOG_PHASE::BEGIN) {
        logger << IF_COLOR("\033[34m");
        logger.AlignFunc();
        logger << logger.GetFunc() << " ";
        logger.AlignLine();
        logger << IF_COLOR("\033[35m") "+" << logger.GetLine() << IF_COLOR("\033[0m") " ";
    }
}

void Logger::AppendFileLine(Logger &logger, enum LOG_PHASE phase)
{
    if (phase == LOG_PHASE::BEGIN) {
        logger << IF_COLOR("\033[34m") << logger.GetFile() << " ";
        logger.AlignLine();
        logger << IF_COLOR("\033[35m") "+" << logger.GetLine() << IF_COLOR("\033[0m") " ";
    }
}

void Logger::AppendFileFuncLine(Logger &logger, enum LOG_PHASE phase)
{
    if (phase == LOG_PHASE::BEGIN) {
        logger << IF_COLOR("\033[34m") << logger.GetFile() << " ";
        logger.AlignLine();
        logger << IF_COLOR("\033[35m") "+" << logger.GetLine() << " ";
        logger.AlignFunc();
        logger << logger.GetFunc() << IF_COLOR("\033[0m") " ";
    }
}

void Logger::AppendPidTid(Logger &logger, enum LOG_PHASE phase)
{
    if (phase == LOG_PHASE::BEGIN) {
        logger << getpid() << ":" << GET_TID() << " ";
    }
}

void Logger::SetScopeParam(int func, int line)
{
    alignFunc = func;
    alignLine = line;
}

void Logger::EnterScope()
{
    std::lock_guard<std::mutex> lock(scopeMutex_);
    scope_++;
}

void Logger::ExitScope()
{
    std::lock_guard<std::mutex> lock(scopeMutex_);
    scope_--;
}

Logger::Logger(const std::string &file, const std::string &func, int line, enum LOG_LEVEL level, ...)
{
    *this << std::boolalpha;
    file_ = file;
    func_ = func;
    line_ = line;
    level_ = level;
    va_start(vl_, level);

    while (true) {
        LoggerWrapperFunc f = va_arg(vl_, LoggerWrapperFunc);
        if (f == nullptr) {
            break;
        }

        f(*this, LOG_PHASE::BEGIN);
        wrappers_.push_back(f);
    }

#ifdef LOGGER_ENABLE_SCOPE
    {
        std::lock_guard<std::mutex> lock(scopeMutex_);
        // The number of space if enable scope
        Align(scope_ * 2);  // 2 means multiple
    }
#endif
}

Logger::Logger(const Logger &logger)
{
    file_ = logger.file_;
    func_ = logger.func_;
    line_ = logger.line_;
    level_ = logger.level_;
    data_ = logger.data_;
    wrappers_ = logger.wrappers_;
    *this << logger.str();
}

Logger::Logger(Logger &&logger)
{
    file_ = logger.file_;
    func_ = logger.func_;
    line_ = logger.line_;
    level_ = logger.level_;
    data_ = logger.data_;
    wrappers_ = logger.wrappers_;
    *this << logger.str();

    logger.wrappers_.clear();
}

Logger::~Logger()
{
    for (const auto &wrapper : wrappers_) {
        wrapper(*this, LOG_PHASE::END);
    }
}

const std::string &Logger::GetFile() const
{
    return file_;
}

const std::string &Logger::GetFunc() const
{
    return func_;
}

int Logger::GetLine() const
{
    return line_;
}

enum Logger::LOG_LEVEL Logger::GetLevel() const
{
    return level_;
}

va_list &Logger::GetVariousArgument()
{
    return vl_;
}

void Logger::Align(int num)
{
    if (continue_) {
        return;
    }

    for (int32_t i = 0; i < num; i++) {
        *this << " ";
    }
}

void Logger::AlignLine()
{
    if (alignLine) {
        auto line = GetLine();
        auto num = line == 0 ? 1 : 0;
        while (line) {
            // 10 is to calculate the number of bits in the row where the function is located
            line /= 10;
            num++;
        }
        Align(alignLine - num);
    }
}

void Logger::AlignFunc()
{
    if (alignFunc) {
        Align(alignFunc - GetFunc().size());
    }
}

ScopedLogger::ScopedLogger(NoLogger &&logger)
{
}

ScopedLogger::ScopedLogger(NoLogger &&logger, const std::string &name)
{
}

ScopedLogger::ScopedLogger(Logger &&logger)
    : ScopedLogger(std::move(logger), "")
{
}

ScopedLogger::ScopedLogger(Logger &&logger, const std::string &name)
{
#ifdef LOGGER_ENABLE_SCOPE
    logger_ = new Logger(logger);
    *logger_ << "} " << name;
    logger << "{ ";
#endif
    logger << name;
    Logger::EnterScope();
}

ScopedLogger::~ScopedLogger()
{
    Finish();
}

void ScopedLogger::Finish()
{
    if (logger_) {
        Logger::ExitScope();
        delete logger_;
        logger_ = nullptr;
    }
}
} // namespace TextEngine
} // namespace Rosen
} // namespace OHOS