/*============================================================================
  CMake - Cross Platform Makefile Generator
  Copyright 2000-2009 Kitware, Inc., Insight Software Consortium

  Distributed under the OSI-approved BSD License (the "License");
  see accompanying file Copyright.txt for details.

  This software is distributed WITHOUT ANY WARRANTY; without even the
  implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
  See the License for more information.
============================================================================*/
#pragma once

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

#include <utility>

#include "cmGlobalCommonGenerator.h"
#include "cmStateTypes.h"

#define FASTBUILD_DOLLAR_TAG "FASTBUILD_DOLLAR_TAG"

class cmGlobalGeneratorFactory;
class cmFASTBuildTargetGenerator;
class cmGeneratedFileStream;
class cmDocumentationEntry;

/** \class cmGlobalFASTBuildGenerator
 * \brief Class for global FASTBuild generator.
 */
class cmGlobalFASTBuildGenerator : public cmGlobalCommonGenerator
{
public:
  /// The default name of FASTBuild's build file. Typically: fbuild.bff.
  static const char* FASTBUILD_BUILD_FILE;

  /// The indentation string used when generating FASTBuild's build file.
  static const char* INDENT;

  /// Constructor
  /// cm CMake invocation instance
  cmGlobalFASTBuildGenerator(cmake* cm);

  /// Destructor
  ~cmGlobalFASTBuildGenerator() override;

  /** \brief Creates the cmGlobalFASTBuildGenerator's factory.
   * \return unique_ptr containing the factory's instance.
   */
  static std::unique_ptr<cmGlobalGeneratorFactory> NewFactory();

  /// Override to return the right generator name.
  std::string GetName() const override
  {
    return cmGlobalFASTBuildGenerator::GetActualName();
  }

  /// Returns the actual name of the global generator
  static std::string GetActualName() { return "FASTBuild"; }

  /// Return the minimal requiered version of FASTBuild.
  static std::string RequiredFASTBuildVersion() { return "1.00"; }

  // Setup target names
  const char* GetAllTargetName() const override { return "all"; }
  const char* GetInstallTargetName() const override { return "install"; }
  const char* GetCleanTargetName() const override { return "clean"; }
  const char* GetInstallLocalTargetName() const override
  {
    return "install/local";
  }
  const char* GetInstallStripTargetName() const override
  {
    return "install/strip";
  }
  const char* GetTestTargetName() const override { return "test"; }
  const char* GetPackageSourceTargetName() const override
  {
    return "package_source";
  }

  /// Returns @c cmDocumentationEntry with generator documentation. @see cmGlobalGeneratorSimpleFactory::GetDocumentation()
  static cmDocumentationEntry GetDocumentation();

  /**
   * Utilized by the generator factory to determine if this generator
   * supports toolsets.
   */
  static bool SupportsToolset() { return false; }

  /**
   * Utilized by the generator factory to determine if this generator
   * supports platforms.
   */
  static bool SupportsPlatform() { return false; }

  /// Override to support IPO as many generators (ex: Makefile, Ninja)
  bool IsIPOSupported() const override { return true; }

  /// Override to find FASTBuild executable and its version
  bool FindMakeProgram(cmMakefile* mf) override;

  /// Override to write the build file, validate FASTBuild version.
  void Generate() override;

  virtual std::string ConfigDirectory(const std::string& /*config*/) const
  {
    return "";
  }

  /// Override to generate the command to build using FASTBuild.
  std::vector<GeneratedMakeCommand> GenerateBuildCommand(
    const std::string& makeProgram, const std::string& projectName,
    const std::string& projectDir, std::vector<std::string> const& targetNames,
    const std::string& config, int jobs, bool verbose,
    const cmBuildOptions& buildOptions = cmBuildOptions(),
    std::vector<std::string> const& makeOptions = std::vector<std::string>()) override;

  ///! create the correct local generator
  std::unique_ptr<cmLocalGenerator> CreateLocalGenerator(cmMakefile* makefile) override;

  /// Computes the output object directory for a target.
  void ComputeTargetObjectDirectory(cmGeneratorTarget*) const override;

  /// Appends the subdirectory for the given configuration.
  void AppendDirectoryForConfig(const std::string& prefix,
                                const std::string& config,
                                const std::string& suffix,
                                std::string& dir) override;

  /// Override to open Visual Studio on Windows
  bool Open(const std::string& bindir, const std::string& projectName,
            bool dryRun) override;

  /// Returns the filestream of the generated build file.
  cmGeneratedFileStream* GetBuildFileStream() const
  {
    return this->BuildFileStream;
  }

  // Makes a path relative to directory containing the build file if possible.
  std::string ConvertToFASTBuildPath(const std::string& path) const;

  // Makes paths contained in a container relative to directory containing the build file if possible.
  template <typename T>
  std::vector<std::string> ConvertToFASTBuildPath(const T& container) const
  {
    std::vector<std::string> ret;
    for (const auto& path : container)
      ret.push_back(ConvertToFASTBuildPath(path));
    return ret;
  }

  /**
   * Puts @c str between @c quotation
   * @c quotation is escaped if found
   * FASTBUILD_DOLLAR_TAG is replaced by $ if found
   */
  static std::string Quote(const std::string& str,
                           const std::string& quotation = "'");

  /// Write @a count times INDENT level to output stream @a os.
  static void Indent(std::ostream& os, int count);

  //@{
  /**
   * Write functions
   */
  static void WriteDivider(std::ostream& os);
  static void WriteComment(std::ostream& os, const std::string& comment,
                           int indent = 0);
  static void WriteVariable(std::ostream& os, const std::string& key,
                            const std::string& value, const std::string& op,
                            int indent = 0);
  static void WriteVariable(std::ostream& os, const std::string& key,
                            const std::string& value, int indent = 0);
  static void WriteCommand(std::ostream& os, const std::string& command,
                           const std::string& value = std::string(),
                           int indent = 0);
  //@}

  /// Sorts targets by dependencies
  template <typename T>
  static void SortByDependencies(
    std::vector<T>& source, const std::unordered_multimap<T, T>& dependencies)
  {
    std::vector<T> output;

    std::unordered_multimap<T, T> reverseDependencies;
    for (auto it = dependencies.begin(); it != dependencies.end(); ++it) {
      auto range = dependencies.equal_range(it->first);
      for (auto jt = range.first; jt != range.second; ++jt) {
        reverseDependencies.emplace(jt->second, jt->first);
      }
    }

    auto forwardDependencies = dependencies;
    while (!source.empty()) {
      for (auto it = source.begin(); it != source.end();) {
        auto targetName = *it;
        if (forwardDependencies.find(targetName) ==
            forwardDependencies.end()) {
          output.push_back(targetName);
          it = source.erase(it);

          auto range = reverseDependencies.equal_range(targetName);
          for (auto rt = range.first; rt != range.second; ++rt) {
            // Fetch the list of deps on that target
            auto frange = forwardDependencies.equal_range(rt->second);
            for (auto ft = frange.first; ft != frange.second;) {
              if (ft->second == targetName) {
                ft = forwardDependencies.erase(ft);
              } else {
                ++ft;
              }
            }
          }
        } else {
          ++it;
        }
      }
    }

    std::swap(output, source);
  }

  /// Generates target name from a target generator
  std::string GetTargetName(const cmGeneratorTarget* GeneratorTarget) const;

  /// Conveniant function to check if a target is excluded from 'all'
  bool IsExcluded(cmGeneratorTarget* target);

  /// Adds a new compiler to the build file. Do nothing if already added.
  void AddCompiler(const std::string& lang, cmMakefile* mf);

  /// Adds a compiler launcher (e.g <LANG>_COMPILER_LAUNCHER)
  std::string AddLauncher(const std::string& launcher, const std::string& lang,
                          cmMakefile* mf);

  /**
   * Struct representing ObjectList function
   * see https://www.fastbuild.org/docs/functions/objectlist.html
   */
  struct FASTBuildObjectListNode
  {
    std::string Name;
    std::string Compiler;
    std::string CompilerOptions;
    std::string CompilerOutputPath;
    std::string CompilerOutputExtension;
    std::string PCHInputFile;
    std::string PCHOutputFile;
    std::string PCHOptions;

    std::vector<std::string> CompilerInputFiles;
    std::set<std::string> PreBuildDependencies;

    std::vector<std::string> ObjectDependencies;
    std::vector<std::string> ObjectOutputs;
  };

  /**
   * Struct representing VCXProject function
   * see https://www.fastbuild.org/docs/functions/vcxproject.html
   */
  struct FASTBuildVCXProject
  {
    std::string Name;
    std::string Folder;
    std::string UserProps;
    std::string LocalDebuggerCommand;
    std::string LocalDebuggerCommandArguments;
    std::string ProjectOutput;
    std::map<std::string, std::vector<std::string>> ProjectFiles;
    std::string Platform;
    std::string Config;
    std::string Target;
    std::string ProjectBuildCommand;
    std::string ProjectRebuildCommand;
  };

  /**
   * Struct representing a Link step
   * Contains all information to use right FASTBuild functions to build
   * the right artifact.
   */
  struct FASTBuildLinkerNode
  {
    enum
    {
      EXECUTABLE,
      SHARED_LIBRARY,
      STATIC_LIBRARY
    } Type;

    std::string Name;
    std::string Compiler;
    std::string CompilerOptions;
    std::string Linker;
    std::string LinkerType;
    std::string LinkerOutput;
    std::string LinkerOptions;
    std::vector<std::string> Libraries;
  };

  /**
   * Struct representing VCXProject function
   * see https://www.fastbuild.org/docs/functions/exec.html
   */
  struct FASTBuildExecNode
  {
    std::string Name;
    std::string ExecExecutable;
    std::string ExecArguments;
    std::string ExecWorkingDir;
    bool ExecUseStdOutAsOutput = false;
    std::string ExecOutput;
    std::vector<std::string> ExecInput;
    std::set<std::string> PreBuildDependencies;
    bool ExecAlways = false;
    bool IsNoop = false;
  };

  /**
   * Struct representing Alias function
   * see https://www.fastbuild.org/docs/functions/alias.html
   */
  struct FASTBuildAliasNode
  {
    std::string Name;
    std::set<std::string> Targets;
  };

  /**
   * Struct representing a Target
   * Contains all information to use right FASTBuild functions to build it
   */
  struct FASTBuildTarget
  {
    std::string Name;
    std::map<std::string, std::string> Variables;
    std::vector<FASTBuildObjectListNode> ObjectListNodes;
    std::vector<FASTBuildLinkerNode> LinkerNodes;
    std::vector<FASTBuildVCXProject> VCXProjects;
    std::vector<FASTBuildExecNode> PreBuildExecNodes, PreLinkExecNodes,
      PostBuildExecNodes, ExecNodes;
    std::vector<FASTBuildAliasNode> AliasNodes;
    std::vector<std::string> Dependencies;
    bool IsGlobal = false;
    bool IsExcluded = false;
  };

  /// Adds a target to write. Target name must be unique
  void AddTarget(const FASTBuildTarget& target);

private:
  // Open filestream of the generated build file.
  void OpenBuildFileStream();
  // Close filestream of the generated build file.
  void CloseBuildFileStream();

  /// Concatenates all strings with prefix/suffix. Dollars are escaped by default
  static std::vector<std::string> Wrap(const std::vector<std::string>& in,
                                       const std::string& prefix = "'",
                                       const std::string& suffix = "'",
                                       const bool escape_dollar = true);
  static std::vector<std::string> Wrap(const std::set<std::string>& in,
                                       const std::string& prefix = "'",
                                       const std::string& suffix = "'",
                                       const bool escape_dollar = true);

  /// Write the common disclaimer text at the top of each build file.
  void WriteDisclaimer(std::ostream& os);
  //@{
  /**
   * Write functions
   */
  void WriteBuildFileTop(std::ostream& os);
  void WriteCompilers(std::ostream& os);
  void WriteTargets(std::ostream& os);
  std::set<std::string> WriteExecs(const std::vector<FASTBuildExecNode>&,
                                   const std::set<std::string>&);
  std::set<std::string> WriteObjectLists(
    const std::vector<FASTBuildObjectListNode>&, const std::set<std::string>&);
  std::set<std::string> WriteLinker(const std::vector<FASTBuildLinkerNode>&,
                                    const std::set<std::string>&);
  void WriteAlias(const std::string&, const std::set<std::string>&);
  void WriteAlias(const std::string&, const std::vector<std::string>&);
  static void WriteArray(std::ostream& os, const std::string& key,
                         const std::vector<std::string>& values,
                         int indent = 0);
  static void WriteArray(std::ostream& os, const std::string& key,
                         const std::vector<std::string>& values,
                         const std::string& op, int indent = 0);
  //@}

  /**
   * Struct representing Compiler function
   * see https://www.fastbuild.org/docs/functions/compiler.html
   */
  struct FASTBuildCompiler
  {
    std::vector<std::pair<std::string, std::string>> extraVariables;
    std::string name;
    std::string executable;
    std::string cmakeCompilerID;
    std::string cmakeCompilerVersion;
    std::string language;
    cmList extraFiles;
    bool useLightCache = false;
  };

  // The set of compilers added to the generated build system.
  std::map<std::string, FASTBuildCompiler> Compilers;
  // The set of targets added to the generated build system.
  std::map<std::string, FASTBuildTarget> FASTBuildTargets;

  // The file containing the build statement.
  cmGeneratedFileStream* BuildFileStream;

  // FASTBuild executable command
  std::string FASTBuildCommand;
  // FASTBuild executable version
  std::string FASTBuildVersion;

  // The set of target generators used.
  std::map<std::string, std::unique_ptr<cmFASTBuildTargetGenerator>> Targets;
  // Map containing all dependencies for a target.
  std::unordered_multimap<std::string, std::string> TargetDependencies;
};
