/**
 * Copyright (c) 2022 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 "mergeProgram.h"

#if defined(PANDA_TARGET_WINDOWS)
#include <io.h>
#else
#include <dirent.h>
#endif

namespace panda::proto {
#if PANDA_TARGET_WINDOWS
bool MergeProgram::EnumerateFilesWindows(const std::string &protoBinPath, const std::string &protoBinSuffix,
                                         std::vector<std::string> &directoryFiles)
{
    int handle = 0;
    struct _finddata_t fileInfo;
    std::string path;
    if ((handle = _findfirst(path.assign(protoBinPath).append("\\*").c_str(), &fileInfo)) == -1) {
        return false;
    }
    do {
        if (fileInfo.attrib & _A_SUBDIR) {
            if ((!strncmp(fileInfo.name, ".", 1)) || (!strncmp(fileInfo.name, "..", 2))) {
                continue;
            }
            if (!GetProtoFiles(path.assign(protoBinPath).append("\\").append(fileInfo.name), protoBinSuffix,
                               directoryFiles)) {
                _findclose(handle);
                return false;
            }
        } else {
            std::string fileName(fileInfo.name);
            if (fileName.substr(fileName.find_last_of(".") + 1).compare(protoBinSuffix) == 0) {
                directoryFiles.emplace_back(path.assign(protoBinPath).append("\\").append(fileName));
            }
        }
    } while (_findnext(handle, &fileInfo) == 0);
    _findclose(handle);
    return true;
}

#elif PANDA_TARGET_UNIX
bool MergeProgram::EnumerateFilesUnix(const std::string &protoBinPath, const std::string &protoBinSuffix,
                                      std::vector<std::string> &directoryFiles)
{
    DIR *protoBin = opendir(protoBinPath.c_str());
    if (protoBin == nullptr) {
        return false;
    }
    dirent *dir = nullptr;
    std::string pathPrefix = protoBinPath + "/";
    while ((dir = readdir(protoBin)) != nullptr) {
        if ((!strncmp(dir->d_name, ".", 1)) || (!strncmp(dir->d_name, "..", 2))) {
            continue;
        }
        if (dir->d_type == DT_DIR) {
            std::string subDirName = pathPrefix + dir->d_name;
            if (!GetProtoFiles(subDirName, protoBinSuffix, directoryFiles)) {
                closedir(protoBin);
                return false;
            }
        } else {
            std::string fileName = pathPrefix + dir->d_name;
            if (fileName.substr(fileName.find_last_of(".") + 1).compare(protoBinSuffix) == 0) {
                directoryFiles.emplace_back(fileName);
            }
        }
    }
    closedir(protoBin);
    return true;
}
#endif

bool MergeProgram::GetProtoFiles(const std::string &protoBinPath, const std::string &protoBinSuffix,
                                 std::vector<std::string> &directoryFiles)
{
#if PANDA_TARGET_WINDOWS
    return EnumerateFilesWindows(protoBinPath, protoBinSuffix, directoryFiles);
#elif PANDA_TARGET_UNIX
    return EnumerateFilesUnix(protoBinPath, protoBinSuffix, directoryFiles);
#endif
}

bool MergeProgram::AppendProtoFiles(const std::string &filePath, const std::string &protoBinSuffix,
                                    std::vector<std::string> &protoFiles)
{
    auto inputAbs = panda::os::file::File::GetAbsolutePath(filePath);
    if (!inputAbs) {
        std::cerr << "Failed to open: " << filePath << std::endl;
        return false;
    }

    auto fPath = inputAbs.Value();
    if (panda::os::file::File::IsRegularFile(fPath)) {
        if (filePath.substr(filePath.find_last_of(".") + 1).compare(protoBinSuffix) == 0) {
            protoFiles.emplace_back(fPath);
        }
    } else if (panda::os::file::File::IsDirectory(fPath)) {
        std::vector<std::string> directoryFiles;
        if (!GetProtoFiles(fPath, protoBinSuffix, directoryFiles)) {
            return false;
        }
        protoFiles.insert(protoFiles.end(), directoryFiles.begin(), directoryFiles.end());
    } else {
        std::cerr << "The input path: " << fPath << " must be either a regular file or directory." << std::endl;
        return false;
    }

    return true;
}

bool MergeProgram::CollectProtoFiles(std::string &input, const std::string &protoBinSuffix,
                                     std::vector<std::string> &protoFiles)
{
    constexpr const char DOGGY = '@';
    std::vector<std::string> inputs;
    bool isList = false;

    if (input[0] == DOGGY) {
        input.erase(input.begin() + 0);
        isList = true;
    }

    auto inputAbs = panda::os::file::File::GetAbsolutePath(input);
    if (!inputAbs) {
        std::cerr << "Failed to open: " << input << std::endl;
        return false;
    }
    if (isList) {
        std::ifstream in(panda::os::file::File::GetExtendedFilePath(inputAbs.Value()));
        std::string line;
        constexpr const char CARRIAGE = '\r';
        while (getline(in, line)) {
            // erase front spaces
            line.erase(line.begin(),
                       std::find_if(line.begin(), line.end(), [](unsigned char ch) { return std::isspace(ch) == 0; }));
            // erase carrige return symbol (Windows workaround)
            line.erase(std::find_if(line.rbegin(), line.rend(), [](unsigned char ch) { return ch != CARRIAGE; }).base(),
                       line.end());
            if (!line.empty()) {
                inputs.push_back(line);
            }
        }
        in.close();
    } else {
        inputs.push_back(inputAbs.Value());
    }

    protoFiles.reserve(inputs.size());
    for (auto &filePath : inputs) {
        if (!AppendProtoFiles(filePath, protoBinSuffix, protoFiles)) {
            return false;
        }
    }

    return true;
}
} // namespace panda::proto