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

#include "cmConfigure.h"  // IWYU pragma: keep

#include "cmDepends.h"
#include "cmLocalCommonGenerator.h"

#include <iosfwd>
#include <map>
#include <set>
#include <string>
#include <vector>

class cmCustomCommand;
class cmCustomCommandGenerator;
class cmGeneratorTarget;
class cmGlobalGenerator;
class cmMakefile;

/** \class cmLocalUnixMakefileGenerator3
 * \brief Write a LocalUnix makefiles.
 *
 * cmLocalUnixMakefileGenerator3 produces a LocalUnix makefile from its
 * member Makefile.
 */
class cmLocalUnixMakefileGenerator3 : public cmLocalCommonGenerator
{
public:
    cmLocalUnixMakefileGenerator3(cmGlobalGenerator* gg, cmMakefile* mf);
    ~cmLocalUnixMakefileGenerator3() override;

    void ComputeHomeRelativeOutputPath() override;

    /**
     * Generate the makefile for this directory.
     */
    void Generate() override;

    // this returns the relative path between the HomeOutputDirectory and this
    // local generators StartOutputDirectory
    const std::string& GetHomeRelativeOutputPath();

    // Write out a make rule
    void WriteMakeRule(std::ostream& os, const char* comment,
                       const std::string&              target,
                       const std::vector<std::string>& depends,
                       const std::vector<std::string>& commands, bool symbolic,
                       bool in_help = false);

    // write the main variables used by the makefiles
    void WriteMakeVariables(std::ostream& makefileStream);

    /**
     * Set max makefile variable size, default is 0 which means unlimited.
     */
    void SetMakefileVariableSize(int s) { this->MakefileVariableSize = s; }

    /**
     * Set whether passing a make target on a command line requires an
     * extra level of escapes.
     */
    void SetMakeCommandEscapeTargetTwice(bool b)
    {
        this->MakeCommandEscapeTargetTwice = b;
    }

    /**
     * Set whether the Borland curly brace command line hack should be
     * applied.
     */
    void SetBorlandMakeCurlyHack(bool b) { this->BorlandMakeCurlyHack = b; }

    // used in writing out Cmake files such as WriteDirectoryInformation
    static void WriteCMakeArgument(std::ostream& os, const char* s);

    /** creates the common disclaimer text at the top of each makefile */
    void WriteDisclaimer(std::ostream& os);

    // write a  comment line #====... in the stream
    void WriteDivider(std::ostream& os);

    /** used to create a recursive make call */
    std::string GetRecursiveMakeCall(const char*        makefile,
                                     const std::string& tgt);

    // append flags to a string
    void AppendFlags(std::string&       flags,
                     const std::string& newFlags) const override;
    void AppendFlags(std::string& flags, const char* newFlags) const override;

    // append an echo command
    enum EchoColor
    {
        EchoNormal,
        EchoDepend,
        EchoBuild,
        EchoLink,
        EchoGenerate,
        EchoGlobal
    };
    struct EchoProgress
    {
        std::string Dir;
        std::string Arg;
    };
    void AppendEcho(std::vector<std::string>& commands, std::string const& text,
                    EchoColor color     = EchoNormal,
                    EchoProgress const* = nullptr);

    /** Get whether the makefile is to have color.  */
    bool GetColorMakefile() const { return this->ColorMakefile; }

    std::string GetTargetDirectory(
        cmGeneratorTarget const* target) const override;

    // create a command that cds to the start dir then runs the commands
    void CreateCDCommand(std::vector<std::string>& commands,
                         std::string const&        targetDir,
                         std::string const&        relDir);

    static std::string ConvertToQuotedOutputPath(const char* p,
                                                 bool        useWatcomQuote);

    std::string CreateMakeVariable(const std::string& sin,
                                   const std::string& s2in);

    /** Called from command-line hook to bring dependencies up to date
        for a target.  */
    bool UpdateDependencies(const char* tgtInfo, bool verbose,
                            bool color) override;

    /** Called from command-line hook to clear dependencies.  */
    void ClearDependencies(cmMakefile* mf, bool verbose) override;

    /** write some extra rules such as make test etc */
    void WriteSpecialTargetsTop(std::ostream& makefileStream);
    void WriteSpecialTargetsBottom(std::ostream& makefileStream);

    std::string GetRelativeTargetDirectory(cmGeneratorTarget* target);

    // File pairs for implicit dependency scanning.  The key of the map
    // is the depender and the value is the explicit dependee.
    struct ImplicitDependFileMap
    : public std::map<std::string, cmDepends::DependencyVector>
    {};
    struct ImplicitDependLanguageMap
    : public std::map<std::string, ImplicitDependFileMap>
    {};
    struct ImplicitDependTargetMap
    : public std::map<std::string, ImplicitDependLanguageMap>
    {};
    ImplicitDependLanguageMap const& GetImplicitDepends(
        cmGeneratorTarget const* tgt);

    void AddImplicitDepends(cmGeneratorTarget const* tgt,
                            const std::string& lang, const char* obj,
                            const char* src);

    // write the target rules for the local Makefile into the stream
    void WriteLocalAllRules(std::ostream& ruleFileStream);

    std::vector<std::string> const& GetLocalHelp() { return this->LocalHelp; }

    /** Get whether to create rules to generate preprocessed and
        assembly sources.  This could be converted to a variable lookup
        later.  */
    bool GetCreatePreprocessedSourceRules()
    {
        return !this->SkipPreprocessedSourceRules;
    }
    bool GetCreateAssemblySourceRules()
    {
        return !this->SkipAssemblySourceRules;
    }

    // Fill the vector with the target names for the object files,
    // preprocessed files and assembly files. Currently only used by the
    // Eclipse generator.
    void GetIndividualFileTargets(std::vector<std::string>& targets);

    std::string MaybeConvertToRelativePath(std::string const& base,
                                           std::string const& path);

protected:
    void WriteLocalMakefile();

    // write the target rules for the local Makefile into the stream
    void WriteLocalMakefileTargets(std::ostream&          ruleFileStream,
                                   std::set<std::string>& emitted);

    // this method Writes the Directory information files
    void WriteDirectoryInformationFile();

    // write the depend info
    void WriteDependLanguageInfo(std::ostream&      cmakefileStream,
                                 cmGeneratorTarget* tgt);

    // write the local help rule
    void WriteHelpRule(std::ostream& ruleFileStream);

    // this converts a file name that is relative to the StartOuputDirectory
    // into a full path
    std::string ConvertToFullPath(const std::string& localPath);

    void WriteConvenienceRule(std::ostream&      ruleFileStream,
                              const std::string& realTarget,
                              const std::string& helpTarget);

    void WriteTargetDependRule(std::ostream&      ruleFileStream,
                               cmGeneratorTarget* target);
    void WriteTargetCleanRule(std::ostream&                   ruleFileStream,
                              cmGeneratorTarget*              target,
                              const std::vector<std::string>& files);
    void WriteTargetRequiresRule(std::ostream&                   ruleFileStream,
                                 cmGeneratorTarget*              target,
                                 const std::vector<std::string>& objects);

    void AppendRuleDepend(std::vector<std::string>& depends,
                          const char*               ruleFileName);
    void AppendRuleDepends(std::vector<std::string>&       depends,
                           std::vector<std::string> const& ruleFiles);
    void AppendCustomDepends(std::vector<std::string>&           depends,
                             const std::vector<cmCustomCommand>& ccs);
    void AppendCustomDepend(std::vector<std::string>&       depends,
                            cmCustomCommandGenerator const& cc);
    void AppendCustomCommands(std::vector<std::string>&           commands,
                              const std::vector<cmCustomCommand>& ccs,
                              cmGeneratorTarget*                  target,
                              std::string const&                  relative);
    void AppendCustomCommand(std::vector<std::string>&       commands,
                             cmCustomCommandGenerator const& ccg,
                             cmGeneratorTarget*              target,
                             std::string const&              relative,
                             bool          echo_comment = false,
                             std::ostream* content      = nullptr);
    void AppendCleanCommand(std::vector<std::string>&       commands,
                            const std::vector<std::string>& files,
                            cmGeneratorTarget*              target,
                            const char*                     filename = nullptr);

    // Helper methods for dependency updates.
    bool ScanDependencies(
        const char*                                         targetDir,
        std::map<std::string, cmDepends::DependencyVector>& validDeps);
    void CheckMultipleOutputs(bool verbose);

private:
    std::string MaybeConvertWatcomShellCommand(std::string const& cmd);

    friend class cmMakefileTargetGenerator;
    friend class cmMakefileExecutableTargetGenerator;
    friend class cmMakefileLibraryTargetGenerator;
    friend class cmMakefileUtilityTargetGenerator;
    friend class cmGlobalUnixMakefileGenerator3;

    ImplicitDependTargetMap ImplicitDepends;

    std::string HomeRelativeOutputPath;

    struct LocalObjectEntry
    {
        cmGeneratorTarget* Target;
        std::string        Language;
        LocalObjectEntry()
        : Target(nullptr)
        , Language()
        {}
        LocalObjectEntry(cmGeneratorTarget* t, const std::string& lang)
        : Target(t)
        , Language(lang)
        {}
    };
    struct LocalObjectInfo : public std::vector<LocalObjectEntry>
    {
        bool HasSourceExtension;
        bool HasPreprocessRule;
        bool HasAssembleRule;
        LocalObjectInfo()
        : HasSourceExtension(false)
        , HasPreprocessRule(false)
        , HasAssembleRule(false)
        {}
    };
    void GetLocalObjectFiles(
        std::map<std::string, LocalObjectInfo>& localObjectFiles);

    void WriteObjectConvenienceRule(std::ostream& ruleFileStream,
                                    const char* comment, const char* output,
                                    LocalObjectInfo const& info);

    std::vector<std::string> LocalHelp;

    /* does the work for each target */
    std::map<std::string, std::string> MakeVariableMap;
    std::map<std::string, std::string> ShortMakeVariableMap;

    int  MakefileVariableSize;
    bool MakeCommandEscapeTargetTwice;
    bool BorlandMakeCurlyHack;
    bool ColorMakefile;
    bool SkipPreprocessedSourceRules;
    bool SkipAssemblySourceRules;
};

#endif
