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

#include "cmCustomCommand.h"
#include "cmCustomCommandGenerator.h"
#include "cmGeneratedFileStream.h"
#include "cmGeneratorTarget.h"
#include "cmGlobalNinjaGenerator.h"
#include "cmLocalNinjaGenerator.h"
#include "cmMakefile.h"
#include "cmNinjaTypes.h"
#include "cmOutputConverter.h"
#include "cmSourceFile.h"
#include "cmStateTypes.h"
#include "cmSystemTools.h"
#include "cmake.h"

#include <algorithm>
#include <iterator>
#include <string>
#include <vector>

cmNinjaUtilityTargetGenerator::cmNinjaUtilityTargetGenerator(
    cmGeneratorTarget* target)
: cmNinjaTargetGenerator(target)
{}

cmNinjaUtilityTargetGenerator::~cmNinjaUtilityTargetGenerator() {}

void
cmNinjaUtilityTargetGenerator::Generate()
{
    std::string utilCommandName =
        this->GetLocalGenerator()->GetCurrentBinaryDirectory();
    utilCommandName += cmake::GetCMakeFilesDirectory();
    utilCommandName += "/";
    utilCommandName += this->GetTargetName() + ".util";
    utilCommandName = this->ConvertToNinjaPath(utilCommandName);

    std::vector<std::string> commands;
    cmNinjaDeps              deps, outputs, util_outputs(1, utilCommandName);

    const std::vector<cmCustomCommand>* cmdLists[2] = {
        &this->GetGeneratorTarget()->GetPreBuildCommands(),
        &this->GetGeneratorTarget()->GetPostBuildCommands()
    };

    bool uses_terminal = false;

    for(unsigned i = 0; i != 2; ++i)
    {
        for(cmCustomCommand const& ci : *cmdLists[i])
        {
            cmCustomCommandGenerator ccg(ci, this->GetConfigName(),
                                         this->GetLocalGenerator());
            this->GetLocalGenerator()->AppendCustomCommandDeps(ccg, deps);
            this->GetLocalGenerator()->AppendCustomCommandLines(ccg, commands);
            std::vector<std::string> const& ccByproducts = ccg.GetByproducts();
            std::transform(ccByproducts.begin(), ccByproducts.end(),
                           std::back_inserter(util_outputs), MapToNinjaPath());
            if(ci.GetUsesTerminal())
            {
                uses_terminal = true;
            }
        }
    }

    std::vector<cmSourceFile*> sources;
    std::string                config =
        this->GetMakefile()->GetSafeDefinition("CMAKE_BUILD_TYPE");
    this->GetGeneratorTarget()->GetSourceFiles(sources, config);
    for(cmSourceFile const* source : sources)
    {
        if(cmCustomCommand const* cc = source->GetCustomCommand())
        {
            cmCustomCommandGenerator ccg(*cc, this->GetConfigName(),
                                         this->GetLocalGenerator());
            this->GetLocalGenerator()->AddCustomCommandTarget(
                cc, this->GetGeneratorTarget());

            // Depend on all custom command outputs.
            const std::vector<std::string>& ccOutputs    = ccg.GetOutputs();
            const std::vector<std::string>& ccByproducts = ccg.GetByproducts();
            std::transform(ccOutputs.begin(), ccOutputs.end(),
                           std::back_inserter(deps), MapToNinjaPath());
            std::transform(ccByproducts.begin(), ccByproducts.end(),
                           std::back_inserter(deps), MapToNinjaPath());
        }
    }

    this->GetLocalGenerator()->AppendTargetOutputs(this->GetGeneratorTarget(),
                                                   outputs);
    this->GetLocalGenerator()->AppendTargetDepends(this->GetGeneratorTarget(),
                                                   deps);

    if(commands.empty())
    {
        this->GetGlobalGenerator()->WritePhonyBuild(
            this->GetBuildFileStream(),
            "Utility command for " + this->GetTargetName(), outputs, deps);
    } else
    {
        std::string command = this->GetLocalGenerator()->BuildCommandLine(
            commands, "utility", this->GeneratorTarget);
        const char* echoStr =
            this->GetGeneratorTarget()->GetProperty("EchoString");
        std::string desc;
        if(echoStr)
        {
            desc = echoStr;
        } else
        {
            desc = "Running utility command for " + this->GetTargetName();
        }

        // TODO: fix problematic global targets.  For now, search and replace
        // the makefile vars.
        cmSystemTools::ReplaceString(
            command, "$(CMAKE_SOURCE_DIR)",
            this->GetLocalGenerator()
                ->ConvertToOutputFormat(
                    this->GetLocalGenerator()->GetSourceDirectory(),
                    cmOutputConverter::SHELL)
                .c_str());
        cmSystemTools::ReplaceString(
            command, "$(CMAKE_BINARY_DIR)",
            this->GetLocalGenerator()
                ->ConvertToOutputFormat(
                    this->GetLocalGenerator()->GetBinaryDirectory(),
                    cmOutputConverter::SHELL)
                .c_str());
        cmSystemTools::ReplaceString(command, "$(ARGS)", "");

        if(command.find('$') != std::string::npos)
        {
            return;
        }

        for(std::string const& util_output : util_outputs)
        {
            this->GetGlobalGenerator()->SeenCustomCommandOutput(util_output);
        }

        this->GetGlobalGenerator()->WriteCustomCommandBuild(
            command, desc, "Utility command for " + this->GetTargetName(),
            /*depfile*/ "", uses_terminal,
            /*restat*/ true, util_outputs, deps);

        this->GetGlobalGenerator()->WritePhonyBuild(
            this->GetBuildFileStream(), "", outputs,
            cmNinjaDeps(1, utilCommandName));
    }

    // Add an alias for the logical target name regardless of what directory
    // contains it.  Skip this for GLOBAL_TARGET because they are meant to
    // be per-directory and have one at the top-level anyway.
    if(this->GetGeneratorTarget()->GetType() != cmStateEnums::GLOBAL_TARGET)
    {
        this->GetGlobalGenerator()->AddTargetAlias(this->GetTargetName(),
                                                   this->GetGeneratorTarget());
    }
}
