// Copyright 2018 Bloomberg Finance L.P
//
// 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.

#ifndef INCLUDED_PARSEDCOMMAND
#define INCLUDED_PARSEDCOMMAND

#include <buildboxcommon_temporaryfile.h>
#include <env.h>

#include <fileutils.h>
#include <list>
#include <memory>
#include <set>
#include <string>
#include <vector>

namespace recc {

/**
 * Represents the result of parsing a compiler command.
 * NOTE: THIS CLASS SHOULD BE TREATED AS PRIVATE, USAGE SHOULD GO THROUGH
 * PARSEDCOMMMANDFACTORY.H
 */
class ParsedCommand {
  public:
    ParsedCommand(const std::vector<std::string> &command,
                  const std::string &workingDirectory);
    ParsedCommand() {}

    /**
     * Returns true if the given command is a supported compiler command.
     */
    bool is_compiler_command() const { return d_compilerCommand; }

    /**
     * Returns true if the given command is a supported linker command.
     */
    bool is_linker_command() const { return d_linkerCommand; }

    /**
     * Returns true if this is a gcc command.
     */
    bool is_gcc() const { return d_isGcc; }

    /**
     * Returns true if this is a clang command.
     */
    bool is_clang() const { return d_isClang; }

    /**
     * Returns true if this is a Sun Studio command.
     */
    bool is_sun_studio() const { return d_isSunStudio; }

    /**
     * Returns the original command that was passed to the constructor,
     * with absolute paths replaced with equivalent relative paths.
     */
    std::vector<std::string> get_command() const { return d_command; }

    /**
     * Return a command that prints this command's dependencies in Makefile
     * format. If this command is not a supported compiler command, the
     * result is undefined.
     */
    std::vector<std::string> get_dependencies_command() const
    {
        return d_dependenciesCommand;
    }

    /**
     * Return the list of input files for the command
     */

    std::vector<std::string> get_input_files() const { return d_inputFiles; }

    /**
     * Return compiler basename specified from the command.
     */
    std::string get_compiler() const { return d_compiler; }

    /**
     * Return the non deps output files specified in the command arguments.
     *
     * This is not necessarily all of the files the command will create.
     * (For example, if no output files are specified, many compilers will
     * write to a.out by default.)
     */
    std::set<std::string> get_products() const { return d_commandProducts; }

    /**
     * Return the deps output files specified in the command arguments.
     */
    std::set<std::string> get_deps_products() const
    {
        return d_commandDepsProducts;
    }

    /**
     * Return the coverage output files explicitly specified in the command
     * arguments.
     */
    std::set<std::string> get_coverage_products() const
    {
        return d_commandCoverageProducts;
    }

    /**
     * If true, the dependencies command will produce nonstandard Sun-style
     * make rules where one dependency is listed per line and spaces aren't
     * escaped.
     */
    bool produces_sun_make_rules() const { return d_producesSunMakeRules; }

    /**
     * Converts a command path (e.g. "/usr/bin/gcc-4.7") to a command name
     * (e.g. "gcc")
     */
    static std::string commandBasename(const std::string &path,
                                       int symlinks = 0);

    /**
     * Wrapper function to intercept push_back to d_command based on whether
     * we are in a response file or not.
     */
    void push_back_command(const std::string &arg)
    {
        if (d_tmpRspFileContents.empty()) {
            d_command.push_back(arg);
        }
        else {
            d_tmpRspFileContents.back().push_back(arg);
        }
    }

    /**
     * Wrapper function to intercept push_back to d_dependenciesCommand based
     * on whether we are in a response file or not. If in a response file, the
     * argument is added to d_tmpRspFileContents instead which later get
     * written to a temporary response file.
     */
    void push_back_dependencies_command(const std::string &arg)
    {
        if (!d_tmpDepRspFileContents.empty()) {
            d_tmpDepRspFileContents.back().push_back(arg);
        }
        else {
            d_dependenciesCommand.push_back(arg);
        }
    }

    /**
     * Adds a temporary response file to the command and dependencies command.
     */
    void add_tmp_rsp_file(const std::string &rspToken,
                          const std::string &workingDirectory);

    void store_tmp_rsp_file();

    bool in_rsp_file() const
    {
        return !d_tmpRspFileContents.empty() &&
               !d_tmpDepRspFileContents.empty();
    }

    std::string get_tmp_rsp_file_merkle_path(std::string tmpRspFile)
    {
        return d_tmpRspMerklePathMap[tmpRspFile];
    }

    bool containsRspFile() const
    {
        return !d_tmpRspFiles.empty() || !d_tmpDepRspFiles.empty() ||
               !d_tmpRspFilesStore.empty() || !d_tmpDepRspFilesStore.empty();
    }

    bool d_compilerCommand = false;
    bool d_linkerCommand = false;
    bool d_md_option_set = false;
    bool d_coverage_option_set = false;
    bool d_split_dwarf_option_set = false;
    bool d_isGcc = false;
    bool d_isClang = false;
    bool d_isSunStudio = false;
    bool d_producesSunMakeRules = false;
    bool d_containsUnsupportedOptions = false;
    bool d_upload_all_include_dirs = false;
    bool d_bstatic = false;
    std::string d_compiler;
    std::list<std::string> d_originalCommand;
    std::vector<std::string> d_defaultDepsCommand;
    std::vector<std::string> d_preProcessorOptions;
    std::vector<std::string> d_command;
    std::vector<std::string> d_dependenciesCommand;
    std::vector<std::string> d_inputFiles;
    std::vector<std::string> d_auxInputFiles;
    std::vector<std::string> d_libraryDirs;
    std::vector<std::string> d_rpathLinkDirs;
    std::vector<std::string> d_rpathDirs;
    std::vector<std::string> d_defaultLibraryDirs;
    std::set<std::string> d_libraries;
    std::set<std::string> d_staticLibraries;
    std::set<std::string> d_commandProducts;
    std::set<std::string> d_commandDepsProducts;
    std::set<std::string> d_commandCoverageProducts;
    std::set<std::string> d_includeDirs;
    std::vector<bool> d_bstaticStack;
    // Temporary response files being processed and populated
    std::vector<std::shared_ptr<buildboxcommon::TemporaryFile>>
        d_tmpDepRspFiles;
    std::vector<std::shared_ptr<buildboxcommon::TemporaryFile>> d_tmpRspFiles;
    // Stored temporary response files to keep them alive
    std::vector<std::shared_ptr<buildboxcommon::TemporaryFile>>
        d_tmpDepRspFilesStore;
    std::vector<std::shared_ptr<buildboxcommon::TemporaryFile>>
        d_tmpRspFilesStore;
    // Rewritten contents of each temporary response file with the inner vector
    // being the rewritten arguments of an individual response file
    std::vector<std::vector<std::string>> d_tmpDepRspFileContents;
    std::vector<std::vector<std::string>> d_tmpRspFileContents;
    std::map<std::string, std::string> d_tmpRspMerklePathMap;
};

} // namespace recc

#endif
