/*
 * Copyright 2020 Bloomberg Finance LP
 *
 * 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 <buildboxcommon_exception.h>
#include <buildboxcommon_logging.h>
#include <buildboxcommon_objectsubdirsfdstore.h>
#include <buildboxcommon_permissions.h>

#include <fcntl.h>
#include <iomanip>
#include <iostream>
#include <string>
#include <sys/stat.h>
#include <sys/types.h>
#include <system_error>
#include <unistd.h>
#include <vector>

using namespace buildboxcommon;

ObjectSubDirsFDStore::ObjectSubDirsFDStore(int hash_prefix_length,
                                           const std::string &obj_dir_path)
    : d_hash_prefix_length(hash_prefix_length),
      d_num_sub_dirs(1 << 4 * hash_prefix_length), d_obj_dir_path(obj_dir_path)
{
    d_objects_dirfd.reserve(d_num_sub_dirs);
    buildboxcommon::FileDescriptor objects_dirfd(
        open(d_obj_dir_path.c_str(), O_DIRECTORY | O_RDONLY | O_CLOEXEC));
    if (objects_dirfd.get() < 0) {
        BUILDBOXCOMMON_THROW_SYSTEM_EXCEPTION(
            std::system_error, errno, std::system_category,
            "Could not open directory " << d_obj_dir_path);
    }
    createAndStoreObjectSubDirFDs(objects_dirfd.get());
}

int ObjectSubDirsFDStore::getSubDirFDFromHash(const std::string &hash) const
{
    const std::string directory_name = hash.substr(0, d_hash_prefix_length);
    int directory_fd = 0;
    try {
        const int directory_index = std::stoi(directory_name, nullptr, 16);
        directory_fd = d_objects_dirfd.at(directory_index).get();
    }
    catch (const std::invalid_argument &ia) {
        directory_fd = -1;
    }
    return directory_fd;
}

void ObjectSubDirsFDStore::createAndStoreObjectSubDirFDs(int objects_dirfd)
{
    for (int prefix = 0; prefix < d_num_sub_dirs; prefix++) {
        std::ostringstream prefix_ss;
        prefix_ss << std::hex << std::setw(2) << std::setfill('0') << prefix;
        const std::string prefix_string = prefix_ss.str();

        if (mkdirat(objects_dirfd, prefix_string.c_str(), PERMISSION_RWXALL) <
                0 &&
            errno != EEXIST) {
            BUILDBOXCOMMON_THROW_SYSTEM_EXCEPTION(
                std::system_error, errno, std::system_category,
                "Could not create directory " << d_obj_dir_path << "/"
                                              << prefix_string);
        }
        int objects_subdirfd = openat(objects_dirfd, prefix_string.c_str(),
                                      O_DIRECTORY | O_RDONLY | O_CLOEXEC);

        if (objects_subdirfd < 0) {
            BUILDBOXCOMMON_THROW_SYSTEM_EXCEPTION(
                std::system_error, errno, std::system_category,
                "Could not open directory " << d_obj_dir_path << "/"
                                            << prefix_string);
        }
        d_objects_dirfd.emplace_back(objects_subdirfd);
    }
}
