/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
   file LICENSE.rst or https://cmake.org/licensing for details.  */
#pragma once

#include <memory>
#include <set>
#include <string>
#include <unordered_map>
#include <utility>
#include <vector>

#include "cmsys/FStream.hxx"

#include "cmCommonTargetGenerator.h"
#include "cmGeneratorTarget.h"
#include "cmGlobalFastbuildGenerator.h"
#include "cmGlobalGenerator.h"
#include "cmOSXBundleGenerator.h"

class cmCustomCommand;
class cmCustomCommandGenerator;
class cmLocalFastbuildGenerator;
class cmMakefile;
class cmSourceFile;

enum class FastbuildBuildStep
{
  PRE_BUILD,
  PRE_LINK,
  POST_BUILD,
  REST,
};

class cmFastbuildTargetGenerator : public cmCommonTargetGenerator
{
public:
  /// Create a cmFastbuildTargetGenerator according to the @a target's type and
  /// config.
  static cmFastbuildTargetGenerator* New(cmGeneratorTarget* target,
                                         std::string config);

  cmFastbuildTargetGenerator(cmGeneratorTarget* target, std::string config);

  virtual void Generate() {}

  std::string GetClangTidyReplacementsFilePath(
    std::string const& directory, cmSourceFile const& source,
    std::string const& Config) const override;

  void AddIncludeFlags(std::string& languageFlags, std::string const& language,
                       std::string const&) override;

  cmGeneratorTarget::Names DetectOutput() const;

  void AddObjectDependencies(FastbuildTarget& fastbuildTarget,
                             std::vector<std::string>& allObjectDepends) const;
  void AddLinkerNodeDependencies(FastbuildTarget& fastbuildTarget);

  std::string ConvertToFastbuildPath(std::string const& path) const;

  cmGlobalFastbuildGenerator* GetGlobalGenerator() const;

  std::string GetName();

  cmMakefile* GetMakefile() const { return this->Makefile; }

  cmGeneratorTarget* GetGeneratorTarget() const
  {
    return this->GeneratorTarget;
  }

  void LogMessage(std::string const& m) const;

private:
  std::string GetUtilityAliasFromBuildStep(FastbuildBuildStep step) const;

protected:
  cmLocalFastbuildGenerator* GetLocalGenerator() const
  {
    return this->LocalGenerator;
  }

  std::string GetTargetName() const;
  std::string GetCdCommand(cmCustomCommandGenerator const& ccg) const;
  std::string GetScriptWorkingDir(cmCustomCommandGenerator const& ccg) const;
  std::string GetScriptFilename(std::string const& utilityTargetName) const;
  void GetDepends(cmCustomCommandGenerator const& ccg,
                  std::string const& currentCCName,
                  std::vector<std::string>& fileLevelDeps,
                  std::set<FastbuildTargetDep>& targetDep) const;

  void AddCommentPrinting(std::vector<std::string>& cmdLines,
                          cmCustomCommandGenerator const& ccg) const;

  void WriteCmdsToFile(cmsys::ofstream& file,
                       std::vector<std::string> const& cmds) const;

  void AddOutput(cmCustomCommandGenerator const& ccg, FastbuildExecNode& exec);

  void AddExecArguments(FastbuildExecNode& exec,
                        std::string const& scriptFilename) const;

  void ReplaceProblematicMakeVars(std::string& command) const;

  FastbuildExecNodes GenerateCommands(FastbuildBuildStep buildStep);
  FastbuildExecNode GetAppleTextStubCommand() const;
  FastbuildExecNode GetDepsCheckExec(FastbuildExecNode const& depender);

  std::string MakeCustomLauncher(cmCustomCommandGenerator const& ccg);

  std::string GetCustomCommandTargetName(cmCustomCommand const& cc,
                                         FastbuildBuildStep step) const;

  void WriteScriptProlog(cmsys::ofstream& file) const;
  void WriteScriptEpilog(cmsys::ofstream& file) const;

  void AdditionalCleanFiles();

  // write rules for Mac OS X Application Bundle content.
  struct MacOSXContentGeneratorType
    : cmOSXBundleGenerator::MacOSXContentGeneratorType
  {
    MacOSXContentGeneratorType(cmFastbuildTargetGenerator* g, std::string cfg)
      : Generator(g)
      , Config(std::move(cfg))
    {
    }

    void operator()(cmSourceFile const& source, char const* pkgloc,
                    std::string const& config) override;

  private:
    cmFastbuildTargetGenerator* Generator;
    std::string const Config;
  };
  friend struct MacOSXContentGeneratorType;

  // "MacOSXContentGenerator" has to be per-config once multiconfig generator
  // is implemented.
  std::unique_ptr<MacOSXContentGeneratorType> MacOSXContentGenerator;
  std::unique_ptr<cmOSXBundleGenerator> OSXBundleGenerator;
  std::set<std::string> MacContentFolders;

  std::vector<FastbuildCopyNode> CopyNodes;

  cmLocalFastbuildGenerator* LocalGenerator;
  cmGlobalGenerator::TargetDependSet const TargetDirectDependencies;
  std::string const Config;

  // Sometimes CMake adds equivalent custom commands to different targets.
  // Not really supported by FBuild (can't have different exec nodes producing
  // same output). So, in such cases we need to "re-map" the exec to produce a
  // "dummy" output (and update all deps within the target).
  // TODO: potentially 1 map should be enough?
  std::unordered_map<std::string, std::string> OutputsToReplace;
  std::unordered_map<std::string, std::string> OutputToExecName;
};
