// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef TOOLS_GN_XCODE_OBJECT_H_
#define TOOLS_GN_XCODE_OBJECT_H_

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

// Helper classes to generate Xcode project files.
//
// This code is based on gyp xcodeproj_file.py generator. It does not support
// all features of Xcode project but instead just enough to implement a hybrid
// mode where Xcode uses external scripts to perform the compilation steps.
//
// See
// https://chromium.googlesource.com/external/gyp/+/master/pylib/gyp/xcodeproj_file.py
// for more information on Xcode project file format.

// PBXObjectClass -------------------------------------------------------------

enum PBXObjectClass {
  // Those values needs to stay sorted in alphabetic order.
  PBXAggregateTargetClass,
  PBXBuildFileClass,
  PBXContainerItemProxyClass,
  PBXFileReferenceClass,
  PBXFrameworksBuildPhaseClass,
  PBXGroupClass,
  PBXNativeTargetClass,
  PBXProjectClass,
  PBXResourcesBuildPhaseClass,
  PBXShellScriptBuildPhaseClass,
  PBXSourcesBuildPhaseClass,
  PBXTargetDependencyClass,
  XCBuildConfigurationClass,
  XCConfigurationListClass,
};

const char* ToString(PBXObjectClass cls);

// Forward-declarations -------------------------------------------------------

class PBXAggregateTarget;
class PBXBuildFile;
class PBXBuildPhase;
class PBXContainerItemProxy;
class PBXFileReference;
class PBXFrameworksBuildPhase;
class PBXGroup;
class PBXNativeTarget;
class PBXObject;
class PBXProject;
class PBXResourcesBuildPhase;
class PBXShellScriptBuildPhase;
class PBXSourcesBuildPhase;
class PBXTarget;
class PBXTargetDependency;
class XCBuildConfiguration;
class XCConfigurationList;

using PBXAttributes = std::map<std::string, std::string>;

// PBXObjectVisitor -----------------------------------------------------------

class PBXObjectVisitor {
 public:
  PBXObjectVisitor();
  virtual ~PBXObjectVisitor();
  virtual void Visit(PBXObject* object) = 0;

 private:
  PBXObjectVisitor(const PBXObjectVisitor&) = delete;
  PBXObjectVisitor& operator=(const PBXObjectVisitor&) = delete;
};

// PBXObjectVisitorConst ------------------------------------------------------

class PBXObjectVisitorConst {
 public:
  PBXObjectVisitorConst();
  virtual ~PBXObjectVisitorConst();
  virtual void Visit(const PBXObject* object) = 0;

 private:
  PBXObjectVisitorConst(const PBXObjectVisitorConst&) = delete;
  PBXObjectVisitorConst& operator=(const PBXObjectVisitorConst&) = delete;
};

// PBXObject ------------------------------------------------------------------

class PBXObject {
 public:
  PBXObject();
  virtual ~PBXObject();

  const std::string id() const { return id_; }
  void SetId(const std::string& id);

  std::string Reference() const;

  virtual PBXObjectClass Class() const = 0;
  virtual std::string Name() const = 0;
  virtual std::string Comment() const;
  virtual void Visit(PBXObjectVisitor& visitor);
  virtual void Visit(PBXObjectVisitorConst& visitor) const;
  virtual void Print(std::ostream& out, unsigned indent) const = 0;

 private:
  std::string id_;

  PBXObject(const PBXObject&) = delete;
  PBXObject& operator=(const PBXObject&) = delete;
};

// PBXBuildPhase --------------------------------------------------------------

class PBXBuildPhase : public PBXObject {
 public:
  PBXBuildPhase();
  ~PBXBuildPhase() override;

  void AddBuildFile(std::unique_ptr<PBXBuildFile> build_file);

  // PBXObject implementation.
  void Visit(PBXObjectVisitor& visitor) override;
  void Visit(PBXObjectVisitorConst& visitor) const override;

 protected:
  std::vector<std::unique_ptr<PBXBuildFile>> files_;

 private:
  PBXBuildPhase(const PBXBuildPhase&) = delete;
  PBXBuildPhase& operator=(const PBXBuildPhase&) = delete;
};

// PBXTarget ------------------------------------------------------------------

class PBXTarget : public PBXObject {
 public:
  PBXTarget(const std::string& name,
            const std::string& shell_script,
            const std::vector<std::string>& configs,
            const PBXAttributes& attributes);
  ~PBXTarget() override;

  void AddDependency(std::unique_ptr<PBXTargetDependency> dependency);

  // PBXObject implementation.
  std::string Name() const override;
  void Visit(PBXObjectVisitor& visitor) override;
  void Visit(PBXObjectVisitorConst& visitor) const override;

 protected:
  std::unique_ptr<XCConfigurationList> configurations_;
  std::vector<std::unique_ptr<PBXBuildPhase>> build_phases_;
  std::vector<std::unique_ptr<PBXTargetDependency>> dependencies_;
  PBXSourcesBuildPhase* source_build_phase_ = nullptr;
  PBXResourcesBuildPhase* resource_build_phase_ = nullptr;
  std::string name_;

 private:
  PBXTarget(const PBXTarget&) = delete;
  PBXTarget& operator=(const PBXTarget&) = delete;
};

// PBXAggregateTarget ---------------------------------------------------------

class PBXAggregateTarget : public PBXTarget {
 public:
  PBXAggregateTarget(const std::string& name,
                     const std::string& shell_script,
                     const std::vector<std::string>& configs,
                     const PBXAttributes& attributes);
  ~PBXAggregateTarget() override;

  // PBXObject implementation.
  PBXObjectClass Class() const override;
  void Print(std::ostream& out, unsigned indent) const override;

 private:
  PBXAggregateTarget(const PBXAggregateTarget&) = delete;
  PBXAggregateTarget& operator=(const PBXAggregateTarget&) = delete;
};

// PBXBuildFile ---------------------------------------------------------------

class PBXBuildFile : public PBXObject {
 public:
  PBXBuildFile(const PBXFileReference* file_reference,
               const PBXBuildPhase* build_phase);
  ~PBXBuildFile() override;

  // PBXObject implementation.
  PBXObjectClass Class() const override;
  std::string Name() const override;
  void Print(std::ostream& out, unsigned indent) const override;

 private:
  const PBXFileReference* file_reference_ = nullptr;
  const PBXBuildPhase* build_phase_ = nullptr;

  PBXBuildFile(const PBXBuildFile&) = delete;
  PBXBuildFile& operator=(const PBXBuildFile&) = delete;
};

// PBXContainerItemProxy ------------------------------------------------------
class PBXContainerItemProxy : public PBXObject {
 public:
  PBXContainerItemProxy(const PBXProject* project, const PBXTarget* target);
  ~PBXContainerItemProxy() override;

  // PBXObject implementation.
  PBXObjectClass Class() const override;
  std::string Name() const override;
  void Print(std::ostream& out, unsigned indent) const override;

 private:
  const PBXProject* project_ = nullptr;
  const PBXTarget* target_ = nullptr;

  PBXContainerItemProxy(const PBXContainerItemProxy&) = delete;
  PBXContainerItemProxy& operator=(const PBXContainerItemProxy&) = delete;
};

// PBXFileReference -----------------------------------------------------------

class PBXFileReference : public PBXObject {
 public:
  PBXFileReference(const std::string& name,
                   const std::string& path,
                   const std::string& type);
  ~PBXFileReference() override;

  // PBXObject implementation.
  PBXObjectClass Class() const override;
  std::string Name() const override;
  std::string Comment() const override;
  void Print(std::ostream& out, unsigned indent) const override;

  const std::string& path() const { return path_; }

 private:
  std::string name_;
  std::string path_;
  std::string type_;

  PBXFileReference(const PBXFileReference&) = delete;
  PBXFileReference& operator=(const PBXFileReference&) = delete;
};

// PBXFrameworksBuildPhase ----------------------------------------------------

class PBXFrameworksBuildPhase : public PBXBuildPhase {
 public:
  PBXFrameworksBuildPhase();
  ~PBXFrameworksBuildPhase() override;

  // PBXObject implementation.
  PBXObjectClass Class() const override;
  std::string Name() const override;
  void Print(std::ostream& out, unsigned indent) const override;

 private:
  PBXFrameworksBuildPhase(const PBXFrameworksBuildPhase&) = delete;
  PBXFrameworksBuildPhase& operator=(const PBXFrameworksBuildPhase&) = delete;
};

// PBXGroup -------------------------------------------------------------------

class PBXGroup : public PBXObject {
 public:
  explicit PBXGroup(const std::string& path = std::string(),
                    const std::string& name = std::string());
  ~PBXGroup() override;

  const std::string& path() const { return path_; }
  const std::string& name() const { return name_; }

  PBXFileReference* AddSourceFile(const std::string& navigator_path,
                                  const std::string& source_path);

  template <typename T, typename... Args>
  T* CreateChild(Args&&... args) {
    return static_cast<T*>(
        AddChildImpl(std::make_unique<T>(std::forward<Args>(args)...)));
  }

  // PBXObject implementation.
  PBXObjectClass Class() const override;
  std::string Name() const override;
  void Visit(PBXObjectVisitor& visitor) override;
  void Visit(PBXObjectVisitorConst& visitor) const override;
  void Print(std::ostream& out, unsigned indent) const override;

  // Returns whether the current PBXGroup should sort last when sorting
  // children of a PBXGroup. This should only be used for the "Products"
  // group which is hidden in Xcode UI when it is the last children of
  // the main PBXProject group.
  virtual bool SortLast() const;

 private:
  PBXObject* AddChildImpl(std::unique_ptr<PBXObject> child);

  std::vector<std::unique_ptr<PBXObject>> children_;
  std::string name_;
  std::string path_;

  PBXGroup(const PBXGroup&) = delete;
  PBXGroup& operator=(const PBXGroup&) = delete;
};

// PBXMainGroup ---------------------------------------------------------------

class PBXMainGroup : public PBXGroup {
 public:
  explicit PBXMainGroup(const std::string& source_path);
  ~PBXMainGroup() override;

  std::string Name() const override;
};

// PBXProductsGroup -----------------------------------------------------------

class PBXProductsGroup : public PBXGroup {
 public:
  explicit PBXProductsGroup();
  ~PBXProductsGroup() override;

  bool SortLast() const override;
};

// PBXNativeTarget ------------------------------------------------------------

class PBXNativeTarget : public PBXTarget {
 public:
  PBXNativeTarget(const std::string& name,
                  const std::string& shell_script,
                  const std::vector<std::string>& configs,
                  const PBXAttributes& attributes,
                  const std::string& product_type,
                  const std::string& product_name,
                  const PBXFileReference* product_reference);
  ~PBXNativeTarget() override;

  void AddResourceFile(const PBXFileReference* file_reference);

  void AddFileForIndexing(const PBXFileReference* file_reference);

  // PBXObject implementation.
  PBXObjectClass Class() const override;
  void Print(std::ostream& out, unsigned indent) const override;

 private:
  const PBXFileReference* product_reference_ = nullptr;
  std::string product_type_;
  std::string product_name_;

  PBXNativeTarget(const PBXNativeTarget&) = delete;
  PBXNativeTarget& operator=(const PBXNativeTarget&) = delete;
};

// PBXProject -----------------------------------------------------------------

class PBXProject : public PBXObject {
 public:
  PBXProject(const std::string& name,
             std::vector<std::string> configs,
             const std::string& source_path,
             const PBXAttributes& attributes);
  ~PBXProject() override;

  void AddSourceFileToIndexingTarget(const std::string& navigator_path,
                                     const std::string& source_path);
  void AddSourceFile(const std::string& navigator_path,
                     const std::string& source_path,
                     PBXNativeTarget* target);
  void AddAggregateTarget(const std::string& name,
                          const std::string& output_dir,
                          const std::string& shell_script);
  void AddIndexingTarget();
  PBXNativeTarget* AddNativeTarget(
      const std::string& name,
      const std::string& type,
      const std::string& output_dir,
      const std::string& output_name,
      const std::string& output_type,
      const std::string& shell_script,
      const PBXAttributes& extra_attributes = PBXAttributes());

  void SetProjectDirPath(const std::string& project_dir_path);
  void SetProjectRoot(const std::string& project_root);
  void AddTarget(std::unique_ptr<PBXTarget> target);

  // PBXObject implementation.
  PBXObjectClass Class() const override;
  std::string Name() const override;
  std::string Comment() const override;
  void Visit(PBXObjectVisitor& visitor) override;
  void Visit(PBXObjectVisitorConst& visitor) const override;
  void Print(std::ostream& out, unsigned indent) const override;

 private:
  PBXAttributes attributes_;
  std::unique_ptr<XCConfigurationList> configurations_;
  std::unique_ptr<PBXGroup> main_group_;
  std::string project_dir_path_;
  std::string project_root_;
  std::vector<std::unique_ptr<PBXTarget>> targets_;
  std::string name_;
  std::vector<std::string> configs_;

  PBXGroup* products_ = nullptr;
  PBXNativeTarget* target_for_indexing_ = nullptr;

  PBXProject(const PBXProject&) = delete;
  PBXProject& operator=(const PBXProject&) = delete;
};

// PBXResourcesBuildPhase -----------------------------------------------------

class PBXResourcesBuildPhase : public PBXBuildPhase {
 public:
  PBXResourcesBuildPhase();
  ~PBXResourcesBuildPhase() override;

  // PBXObject implementation.
  PBXObjectClass Class() const override;
  std::string Name() const override;
  void Print(std::ostream& out, unsigned indent) const override;

 private:
  PBXResourcesBuildPhase(const PBXResourcesBuildPhase&) = delete;
  PBXResourcesBuildPhase& operator=(const PBXResourcesBuildPhase&) = delete;
};

// PBXShellScriptBuildPhase ---------------------------------------------------

class PBXShellScriptBuildPhase : public PBXBuildPhase {
 public:
  PBXShellScriptBuildPhase(const std::string& name,
                           const std::string& shell_script);
  ~PBXShellScriptBuildPhase() override;

  // PBXObject implementation.
  PBXObjectClass Class() const override;
  std::string Name() const override;
  void Print(std::ostream& out, unsigned indent) const override;

 private:
  std::string name_;
  std::string shell_script_;

  PBXShellScriptBuildPhase(const PBXShellScriptBuildPhase&) = delete;
  PBXShellScriptBuildPhase& operator=(const PBXShellScriptBuildPhase&) = delete;
};

// PBXSourcesBuildPhase -------------------------------------------------------

class PBXSourcesBuildPhase : public PBXBuildPhase {
 public:
  PBXSourcesBuildPhase();
  ~PBXSourcesBuildPhase() override;

  // PBXObject implementation.
  PBXObjectClass Class() const override;
  std::string Name() const override;
  void Print(std::ostream& out, unsigned indent) const override;

 private:
  PBXSourcesBuildPhase(const PBXSourcesBuildPhase&) = delete;
  PBXSourcesBuildPhase& operator=(const PBXSourcesBuildPhase&) = delete;
};

// PBXTargetDependency -----------------------------------------------------
class PBXTargetDependency : public PBXObject {
 public:
  PBXTargetDependency(
      const PBXTarget* target,
      std::unique_ptr<PBXContainerItemProxy> container_item_proxy);
  ~PBXTargetDependency() override;

  // PBXObject implementation.
  PBXObjectClass Class() const override;
  std::string Name() const override;
  void Visit(PBXObjectVisitor& visitor) override;
  void Visit(PBXObjectVisitorConst& visitor) const override;
  void Print(std::ostream& out, unsigned indent) const override;

 private:
  const PBXTarget* target_ = nullptr;
  std::unique_ptr<PBXContainerItemProxy> container_item_proxy_;

  PBXTargetDependency(const PBXTargetDependency&) = delete;
  PBXTargetDependency& operator=(const PBXTargetDependency&) = delete;
};

// XCBuildConfiguration -------------------------------------------------------

class XCBuildConfiguration : public PBXObject {
 public:
  XCBuildConfiguration(const std::string& name,
                       const PBXAttributes& attributes);
  ~XCBuildConfiguration() override;

  // PBXObject implementation.
  PBXObjectClass Class() const override;
  std::string Name() const override;
  void Print(std::ostream& out, unsigned indent) const override;

 private:
  PBXAttributes attributes_;
  std::string name_;

  XCBuildConfiguration(const XCBuildConfiguration&) = delete;
  XCBuildConfiguration& operator=(const XCBuildConfiguration&) = delete;
};

// XCConfigurationList --------------------------------------------------------

class XCConfigurationList : public PBXObject {
 public:
  XCConfigurationList(const std::vector<std::string>& configs,
                      const PBXAttributes& attributes,
                      const PBXObject* owner_reference);
  ~XCConfigurationList() override;

  // PBXObject implementation.
  PBXObjectClass Class() const override;
  std::string Name() const override;
  void Visit(PBXObjectVisitor& visitor) override;
  void Visit(PBXObjectVisitorConst& visitor) const override;
  void Print(std::ostream& out, unsigned indent) const override;

 private:
  std::vector<std::unique_ptr<XCBuildConfiguration>> configurations_;
  const PBXObject* owner_reference_ = nullptr;

  XCConfigurationList(const XCConfigurationList&) = delete;
  XCConfigurationList& operator=(const XCConfigurationList&) = delete;
};

#endif  // TOOLS_GN_XCODE_OBJECT_H_