/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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 "paimon/common/utils/path_util.h"

#include <unistd.h>

#include <cerrno>
#include <cstddef>
#include <cstdint>
#include <cstdlib>
#include <cstring>
#include <utility>

#include "fmt/format.h"
#include "paimon/common/utils/string_utils.h"
#include "paimon/common/utils/uuid.h"
#include "paimon/status.h"

namespace paimon {
std::string Path::ToString() const {
    std::string ret;
    if (!scheme.empty()) {
        ret = scheme + ":";
    }
    if (!authority.empty()) {
        ret += "//";
        ret += authority;
    }
    if (!path.empty()) {
        ret += path;
    }
    return ret;
}

std::string PathUtil::JoinPath(const std::string& path, const std::string& name) noexcept {
    if (path.empty()) {
        return name;
    }
    if (name.empty()) {
        return path;
    }
    int32_t slash_cnt = (*(path.rbegin()) == '/') + (*(name.begin()) == '/');
    if (!slash_cnt) {
        return path + "/" + name;
    } else if (slash_cnt == 2) {
        return path + name.substr(1);
    }
    return path + name;
}

std::string PathUtil::NormalizeInnerPath(const std::string& path) noexcept {
    if (path.empty()) {
        return path;
    }
    std::string ret;
    ret.reserve(path.size());
    char last_char = path[0];
    ret.append(1, last_char);
    for (size_t i = 1; i < path.size(); ++i) {
        if (last_char == '/' && path[i] == '/') {
            continue;
        }
        last_char = path[i];
        ret.append(1, last_char);
    }
    TrimLastDelim(&ret);
    return ret;
}

Result<std::string> PathUtil::NormalizePath(const std::string& path_str) noexcept {
    PAIMON_ASSIGN_OR_RAISE(Path path, ToPath(path_str));
    return path.ToString();
}

Result<Path> PathUtil::ToPath(const std::string& path) noexcept {
    // TODO(yonghao.fyh): support Windows Driver
    if (path.empty()) {
        return Status::Invalid("path is an empty string.");
    }
    std::string scheme;
    std::string authority;
    int32_t start = 0;

    // parse scheme
    auto colon = path.find(':');
    auto slash = path.find('/');
    if ((colon != std::string::npos) && (slash == std::string::npos || colon < slash)) {
        // has a scheme
        scheme.append(path, 0, colon);
        start = colon + 1;
    }

    // parse authority
    if (StringUtils::StartsWith(path, "//", start) && (path.length() - start > 2)) {
        // has authority
        int32_t next_slash = path.find('/', start + 2);
        int32_t auth_end = next_slash > 0 ? next_slash : path.length();
        authority = path.substr(start + 2, auth_end - start - 2);
        start = auth_end;
    }

    // parse path in uri
    std::string inner_path = NormalizeInnerPath(path.substr(start));
    return Path(scheme, authority, inner_path);
}

std::string PathUtil::GetParentDirPath(const std::string& path) noexcept {
    std::string::const_reverse_iterator it;
    for (it = path.rbegin(); it != path.rend() && *it == '/'; it++) {
    }
    for (; it != path.rend() && *it != '/'; it++) {
    }
    for (; it != path.rend() && *it == '/'; it++) {
    }
    return path.substr(0, path.rend() - it);
}

std::string PathUtil::GetName(const std::string& path) noexcept {
    std::string dir_path = path;
    TrimLastDelim(&dir_path);
    std::string::const_reverse_iterator it;
    for (it = dir_path.rbegin(); it != dir_path.rend() && *it != '/'; it++) {
    }
    return dir_path.substr(dir_path.rend() - it);
}

void PathUtil::TrimLastDelim(std::string* dir_path) noexcept {
    if (dir_path == nullptr || dir_path->empty()) {
        return;
    }
    if (dir_path->length() > 1 && *(dir_path->rbegin()) == '/') {
        dir_path->erase(dir_path->size() - 1, 1);
    }
}

Result<std::string> PathUtil::GetWorkingDirectory() noexcept {
    char* path = getcwd(nullptr, 0);
    if (path != nullptr) {
        std::string ret(path);
        free(path);
        return ret;
    }
    return Status::IOError(
        fmt::format("get working directory failed, ec: {}", std::strerror(errno)));
}

Result<std::string> PathUtil::CreateTempPath(const std::string& path) noexcept {
    std::string uuid;
    if (!UUID::Generate(&uuid)) {
        return Status::Invalid("generate uuid failed");
    }
    return JoinPath(GetParentDirPath(path), fmt::format(".{}.{}.tmp", GetName(path), uuid));
}

}  // namespace paimon
