/*
 * Copyright 2019 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 <buildboxrun_commandfilemanager.h>

#include <buildboxcommon_exception.h>
#include <buildboxcommon_fileutils.h>
#include <buildboxcommon_logging.h>
#include <buildboxcommon_permissions.h>
#include <buildboxcommon_temporaryfile.h>

#include <fstream>
#include <sstream>
#include <string>
#include <sys/types.h>
#include <vector>

namespace buildboxcommon {
namespace buildboxrun {
namespace userchroot {

namespace {

// Add proper shell quoting to string
std::string shellQuoteString(const std::string &expression)
{
    std::ostringstream replace;
    auto replaceSingleQuotes = [](const char c) {
        return (c == '\'') ? "'\"'\"'" : std::string(1, c);
    };
    std::transform(expression.begin(), expression.end(),
                   std::ostream_iterator<std::string>(replace),
                   replaceSingleQuotes);

    return "'" + replace.str() + "'";
}

} // unnamed namespace

CommandFileManager::CommandFileManager(const Command &command,
                                       const std::string &chroot_location,
                                       const std::string &location_in_chroot,
                                       const std::string &file_prefix)
    : d_command(command)
{
    // construct temporary file
    const std::string abs_dir_location = FileUtils::joinPathSegmentsNoEscape(
        chroot_location, location_in_chroot, true);

    d_tmp_command_file = std::make_unique<TemporaryFile>(
        abs_dir_location.c_str(), file_prefix.c_str(),
        PERMISSION_RWXUSR_RXALL);

    // get absolute path
    d_file_abs_path = d_tmp_command_file->name();

    // get absolute path inside chroot
    const std::string file_location =
        FileUtils::pathBasename(d_file_abs_path.c_str());
    d_file_abs_path_in_chroot = FileUtils::joinPathSegmentsNoEscape(
        location_in_chroot, file_location, true);

    // write command to file
    writeCommandToFile();

    // close file to allow execution
    d_tmp_command_file->close();
}

std::string CommandFileManager::getFilePathInChroot() const
{
    return d_file_abs_path_in_chroot;
}

std::string CommandFileManager::getAbsoluteFilePath() const
{
    return d_file_abs_path;
}

void CommandFileManager::writeCommandToFile()
{
    auto &environment = d_command.environment_variables();
    auto &arguments = d_command.arguments();
    std::string working_dir = d_command.working_directory();

    std::ofstream commandFile;
    commandFile.open(d_file_abs_path, std::ios_base::app);
    if (commandFile.fail()) {
        BUILDBOXCOMMON_THROW_SYSTEM_EXCEPTION(
            std::system_error, errno, std::system_category,
            "Failed to open file \"" << d_file_abs_path << "\"");
    }
    commandFile << "#!/bin/sh\n";

    // add environment variables
    for (const auto &envVar : environment) {
        commandFile << "export " << envVar.name() << "="
                    << shellQuoteString(envVar.value()) << ";\n";
    }

    // change to working directory
    commandFile << "cd " << shellQuoteString("/" + working_dir) << ";\n";
    // remove file before executing command
    commandFile << "rm $0;\n";

    // add arguments
    commandFile << "exec";
    for (auto argVar = arguments.cbegin(); argVar != arguments.cend();
         ++argVar) {
        commandFile << " " << shellQuoteString(*argVar);
    }
    commandFile.close();
}

} // namespace userchroot
} // namespace buildboxrun
} // namespace buildboxcommon
