/*
 * Copyright (c) 2021 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 <cstdio>
#include <dirent.h>
#include <fstream>
#include <iostream>
#include <securec.h>
#include <sstream>
#include <sys/stat.h>
#include <sys/time.h>
#include <unistd.h>

#include "log_persister_rotator.h"

constexpr uint8_t MAX_TIME_BUF_SIZE = 32;
constexpr uint8_t MAX_LOG_INDEX_LEN = 4;

namespace OHOS {
namespace HiviewDFX {
std::string GetFileNameIndex(const int index)
{
    char res[MAX_LOG_INDEX_LEN];
    (void)snprintf_s(res, sizeof(res), sizeof(res) - 1, "%03d", index % MAX_LOG_FILE_NUM);
    std::string fileNameIndex(res);
    return fileNameIndex;
}

bool LogPersisterRotator::IsOldFile(const std::string& logName, const int index)
{
    std::string fileNameHead = m_logsPath.substr(strlen(HILOG_FILE_DIR), m_logsPath.size());
    fileNameHead = fileNameHead + "." + GetFileNameIndex(index + 1 - m_maxLogFileNum);
    if (logName.find(fileNameHead) == std::string::npos) {
        return false;
    }
    return true;
}


LogPersisterRotator::LogPersisterRotator(const std::string& logsPath, uint32_t id, uint32_t maxFiles,
    const std::string& fileNameSuffix)
    : m_maxLogFileNum(maxFiles), m_logsPath(logsPath), m_fileNameSuffix(fileNameSuffix), m_id(id)
{
}

LogPersisterRotator::~LogPersisterRotator()
{
    m_infoFile.close();
    remove(m_infoFilePath.c_str());
}

int LogPersisterRotator::Init(const PersistRecoveryInfo& info, bool restore)
{
    if (!m_infoFile.is_open()) {
        if (int result = OpenInfoFile(); result != RET_SUCCESS) {
            return result;
        }
    }

    m_info = info;
    SetFileIndex(m_info.index, restore);
    UpdateRotateNumber();
    return RET_SUCCESS;
}

int LogPersisterRotator::OpenInfoFile()
{
    auto lastSeparatorIdx = m_logsPath.find_last_of('/');
    std::string parentDirPath = m_logsPath.substr(0, lastSeparatorIdx);
    if (access(parentDirPath.c_str(), F_OK) != 0) {
        if (errno == ENOENT) {
            mkdir(parentDirPath.c_str(), S_IRUSR | S_IWUSR | S_IXUSR | S_IRWXG | S_IRWXO);
        }
    }
    std::string infoFileName = std::string(".") + AUXILLARY_PERSISTER_PREFIX + std::to_string(m_id) + ".info";
    m_infoFilePath = parentDirPath + "/" + infoFileName;
    m_infoFile.open(m_infoFilePath, std::ios::binary | std::ios::out | std::ios::trunc);
    return m_infoFile.is_open() ? RET_SUCCESS : RET_FAIL;
}

int LogPersisterRotator::Input(const char *buf, uint32_t length)
{
    if (length <= 0 || buf == nullptr) {
        return ERR_LOG_PERSIST_COMPRESS_BUFFER_EXP;
    }
    if (m_needRotate) {
        Rotate();
        m_needRotate = false;
    } else if ((access(m_currentLogFileName.c_str(), F_OK) != 0) || !m_currentLogOutput.is_open()) {
        CreateLogFile();
    }
    m_currentLogOutput.write(buf, length);
    m_currentLogOutput.flush();
    return 0;
}

void LogPersisterRotator::RemoveOldFile()
{
    DIR *dir = nullptr;
    struct dirent *ent = nullptr;
    if ((dir = opendir(HILOG_FILE_DIR)) != nullptr) {
        while ((ent = readdir(dir)) != nullptr) {
            size_t length = strlen(ent->d_name);
            std::string pPath(ent->d_name, length);
            if (IsOldFile(pPath, m_currentLogFileIdx)) {
                remove((HILOG_FILE_DIR + pPath).c_str());
                break;
            }
        }
    }
    if (dir != nullptr) {
        closedir(dir);
    }
}

void LogPersisterRotator::Rotate()
{
    std::cout << __PRETTY_FUNCTION__ << "\n";
    if (m_currentLogFileIdx + 1 >= m_maxLogFileNum) {
        RemoveOldFile();
    }
    m_currentLogFileIdx++;
    CreateLogFile();
    UpdateRotateNumber();
}

void LogPersisterRotator::CreateLogFile()
{
    std::cout << __PRETTY_FUNCTION__ << "\n";
    time_t tnow = time(nullptr);
    struct tm *tmNow = localtime(&tnow);
    char timeBuf[MAX_TIME_BUF_SIZE] = {0};
    if (tmNow != nullptr) {
        strftime(timeBuf, sizeof(timeBuf), "%Y%m%d-%H%M%S", tmNow);
    }
    std::stringstream newFile;
    newFile << m_logsPath << "." << GetFileNameIndex(m_currentLogFileIdx) << "." << timeBuf << m_fileNameSuffix;
    std::cout << "Filename: " << newFile.str() << std::endl;
    m_currentLogFileName = newFile.str();
    if (m_currentLogOutput.is_open()) {
        m_currentLogOutput.close();
    }
    m_currentLogOutput.open(newFile.str(), std::ios::out | std::ios::trunc);
}

void LogPersisterRotator::UpdateRotateNumber()
{
    m_info.index = static_cast<uint32_t>(m_currentLogFileIdx);
    WriteRecoveryInfo();
}

void LogPersisterRotator::FinishInput()
{
    std::cout << __PRETTY_FUNCTION__ << "\n";

    m_currentLogOutput.close();
    m_needRotate = true;
}

void LogPersisterRotator::SetFileIndex(uint32_t index, bool forceRotate)
{
    m_currentLogOutput.close();
    m_currentLogFileIdx = index;
    if (forceRotate) {
        m_needRotate = true;
    }
}

void LogPersisterRotator::WriteRecoveryInfo()
{
    if (!m_infoFile.is_open()) {
        std::cerr << "LogPersisterRotator has not been initialized!\n";
        return;
    }

    std::cout << "Save Info file!\n";
    uint64_t hash = GenerateHash(reinterpret_cast<char *>(&m_info), sizeof(PersistRecoveryInfo));

    m_infoFile.seekp(0);
    m_infoFile.write(reinterpret_cast<const char*>(&m_info), sizeof(m_info));
    m_infoFile.write(reinterpret_cast<const char*>(&hash), sizeof(hash));
    m_infoFile.flush();
    m_infoFile.sync();
}
} // namespace HiviewDFX
} // namespace OHOS