/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
   file Copyright.txt or https://cmake.org/licensing for details.  */

#include "cmLinkLineComputer.h"

#include <sstream>
#include <vector>

#include "cmComputeLinkInformation.h"
#include "cmGeneratorTarget.h"
#include "cmOutputConverter.h"
#include "cmStateDirectory.h"
#include "cmStateTypes.h"

cmLinkLineComputer::cmLinkLineComputer(cmOutputConverter*      outputConverter,
                                       cmStateDirectory const& stateDir)
: StateDir(stateDir)
, OutputConverter(outputConverter)
, ForResponse(false)
, UseWatcomQuote(false)
, Relink(false)
{}

cmLinkLineComputer::~cmLinkLineComputer() {}

void
cmLinkLineComputer::SetUseWatcomQuote(bool useWatcomQuote)
{
    this->UseWatcomQuote = useWatcomQuote;
}

void
cmLinkLineComputer::SetForResponse(bool forResponse)
{
    this->ForResponse = forResponse;
}

void
cmLinkLineComputer::SetRelink(bool relink)
{
    this->Relink = relink;
}

std::string
cmLinkLineComputer::ConvertToLinkReference(std::string const& lib) const
{
    std::string relLib = lib;

    if(cmOutputConverter::ContainedInDirectory(
           this->StateDir.GetCurrentBinary(), lib, this->StateDir))
    {
        relLib = cmOutputConverter::ForceToRelativePath(
            this->StateDir.GetCurrentBinary(), lib);
    }
    return relLib;
}

std::string
cmLinkLineComputer::ComputeLinkLibs(cmComputeLinkInformation& cli)
{
    std::string                                  linkLibs;
    typedef cmComputeLinkInformation::ItemVector ItemVector;
    ItemVector const&                            items = cli.GetItems();
    for(auto const& item : items)
    {
        if(item.Target &&
           item.Target->GetType() == cmStateEnums::INTERFACE_LIBRARY)
        {
            continue;
        }
        if(item.IsPath)
        {
            linkLibs += this->ConvertToOutputFormat(
                this->ConvertToLinkReference(item.Value));
        } else
        {
            linkLibs += item.Value;
        }
        linkLibs += " ";
    }
    return linkLibs;
}

std::string
cmLinkLineComputer::ConvertToOutputFormat(std::string const& input)
{
    cmOutputConverter::OutputFormat shellFormat =
        (this->ForResponse)
            ? cmOutputConverter::RESPONSE
            : ((this->UseWatcomQuote) ? cmOutputConverter::WATCOMQUOTE
                                      : cmOutputConverter::SHELL);

    return this->OutputConverter->ConvertToOutputFormat(input, shellFormat);
}

std::string
cmLinkLineComputer::ConvertToOutputForExisting(std::string const& input)
{
    cmOutputConverter::OutputFormat shellFormat =
        (this->ForResponse)
            ? cmOutputConverter::RESPONSE
            : ((this->UseWatcomQuote) ? cmOutputConverter::WATCOMQUOTE
                                      : cmOutputConverter::SHELL);

    return this->OutputConverter->ConvertToOutputForExisting(input,
                                                             shellFormat);
}

std::string
cmLinkLineComputer::ComputeLinkPath(cmComputeLinkInformation& cli,
                                    std::string const&        libPathFlag,
                                    std::string const&        libPathTerminator)
{
    std::string                     linkPath;
    std::vector<std::string> const& libDirs = cli.GetDirectories();
    for(std::string const& libDir : libDirs)
    {
        std::string libpath = this->ConvertToOutputForExisting(libDir);
        linkPath += " " + libPathFlag;
        linkPath += libpath;
        linkPath += libPathTerminator;
        linkPath += " ";
    }
    return linkPath;
}

std::string
cmLinkLineComputer::ComputeRPath(cmComputeLinkInformation& cli)
{
    std::string rpath;
    // Check what kind of rpath flags to use.
    if(cli.GetRuntimeSep().empty())
    {
        // Each rpath entry gets its own option ("-R a -R b -R c")
        std::vector<std::string> runtimeDirs;
        cli.GetRPath(runtimeDirs, this->Relink);

        for(std::string const& rd : runtimeDirs)
        {
            rpath += cli.GetRuntimeFlag();
            rpath += this->ConvertToOutputFormat(rd);
            rpath += " ";
        }
    } else
    {
        // All rpath entries are combined ("-Wl,-rpath,a:b:c").
        std::string rpathString = cli.GetRPathString(this->Relink);

        // Store the rpath option in the stream.
        if(!rpathString.empty())
        {
            rpath += cli.GetRuntimeFlag();
            rpath += this->OutputConverter->EscapeForShell(rpathString,
                                                           !this->ForResponse);
            rpath += " ";
        }
    }
    return rpath;
}

std::string
cmLinkLineComputer::ComputeFrameworkPath(cmComputeLinkInformation& cli,
                                         std::string const&        fwSearchFlag)
{
    std::string frameworkPath;
    if(!fwSearchFlag.empty())
    {
        std::vector<std::string> const& fwDirs = cli.GetFrameworkPaths();
        for(std::string const& fd : fwDirs)
        {
            frameworkPath += fwSearchFlag;
            frameworkPath += this->ConvertToOutputFormat(fd);
            frameworkPath += " ";
        }
    }
    return frameworkPath;
}

std::string
cmLinkLineComputer::ComputeLinkLibraries(cmComputeLinkInformation& cli,
                                         std::string const&        stdLibString)
{
    std::ostringstream fout;
    fout << this->ComputeRPath(cli);

    // Write the library flags to the build rule.
    fout << this->ComputeLinkLibs(cli);

    // Add the linker runtime search path if any.
    std::string rpath_link = cli.GetRPathLinkString();
    if(!cli.GetRPathLinkFlag().empty() && !rpath_link.empty())
    {
        fout << cli.GetRPathLinkFlag();
        fout << this->OutputConverter->EscapeForShell(rpath_link,
                                                      !this->ForResponse);
        fout << " ";
    }

    if(!stdLibString.empty())
    {
        fout << stdLibString << " ";
    }

    return fout.str();
}

std::string
cmLinkLineComputer::GetLinkerLanguage(cmGeneratorTarget* target,
                                      std::string const& config)
{
    return target->GetLinkerLanguage(config);
}
