// Copyright 2021 the V8 project 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 V8_TORQUE_CPP_BUILDER_H_
#define V8_TORQUE_CPP_BUILDER_H_

#include <stack>

#include "src/torque/ast.h"
#include "src/torque/types.h"

namespace v8 {
namespace internal {
namespace torque {
namespace cpp {

struct TemplateParameter {
  explicit TemplateParameter(std::string name) : name(std::move(name)) {}
  TemplateParameter(std::string type, std::string name)
      : name(std::move(name)), type(std::move(type)) {}

  std::string name;
  std::string type;
};

class Class {
 public:
  explicit Class(std::string name) : name_(std::move(name)) {}
  Class(std::vector<TemplateParameter> template_parameters, std::string name)
      : template_parameters_(std::move(template_parameters)),
        name_(std::move(name)) {}

  std::string GetName() const { return name_; }
  std::vector<TemplateParameter> GetTemplateParameters() const {
    return template_parameters_;
  }

 private:
  std::vector<TemplateParameter> template_parameters_;
  std::string name_;
};

#define FUNCTION_FLAG_LIST(V) \
  V(Inline, 0x01)             \
  V(V8Inline, 0x03)           \
  V(Const, 0x04)              \
  V(Constexpr, 0x08)          \
  V(Export, 0x10)             \
  V(Static, 0x20)             \
  V(Override, 0x40)

class Function {
 public:
  enum FunctionFlag {
#define ENTRY(name, value) k##name = value,
    FUNCTION_FLAG_LIST(ENTRY)
#undef ENTRY
  };

  struct Parameter {
    std::string type;
    std::string name;
    std::string default_value;

    Parameter(std::string type, std::string name,
              std::string default_value = {})
        : type(std::move(type)),
          name(std::move(name)),
          default_value(std::move(default_value)) {}
  };

  explicit Function(std::string name)
      : pos_(CurrentSourcePosition::Get()),
        owning_class_(nullptr),
        name_(std::move(name)) {}
  Function(Class* owning_class, std::string name)
      : pos_(CurrentSourcePosition::Get()),
        owning_class_(owning_class),
        name_(std::move(name)) {}
  ~Function() = default;

  static Function DefaultGetter(std::string return_type, Class* owner,
                                std::string name) {
    Function getter(owner, std::move(name));
    getter.SetReturnType(std::move(return_type));
    getter.SetInline();
    getter.SetConst();
    return getter;
  }

  static Function DefaultSetter(Class* owner, std::string name,
                                std::string parameter_type,
                                std::string parameter_name) {
    Function setter(owner, std::move(name));
    setter.SetReturnType("void");
    setter.AddParameter(std::move(parameter_type), std::move(parameter_name));
    setter.SetInline();
    return setter;
  }

  void SetFlag(FunctionFlag flag, bool value = true) {
    if (value) {
      flags_ = flags_ | flag;
    } else {
      flags_ = flags_.without(flag);
    }
  }
  void SetFlags(base::Flags<FunctionFlag> flags, bool value = true) {
    if (value) {
      flags_ |= flags;
    } else {
      flags_ &= ~flags;
    }
  }
  bool HasFlag(FunctionFlag flag) const { return (flags_ & flag) == flag; }
#define ACCESSOR(name, value)                            \
  void Set##name(bool v = true) { SetFlag(k##name, v); } \
  bool Is##name() const { return HasFlag(k##name); }
  FUNCTION_FLAG_LIST(ACCESSOR)
#undef ACCESSOR

  void SetDescription(std::string description) {
    description_ = std::move(description);
  }
  void SetName(std::string name) { name_ = std::move(name); }
  void SetReturnType(std::string return_type) {
    return_type_ = std::move(return_type);
  }
  void AddParameter(std::string type, std::string name = {},
                    std::string default_value = {}) {
    parameters_.emplace_back(std::move(type), std::move(name),
                             std::move(default_value));
  }
  void InsertParameter(int index, std::string type, std::string name = {},
                       std::string default_value = {}) {
    DCHECK_GE(index, 0);
    DCHECK_LE(index, parameters_.size());
    parameters_.insert(
        parameters_.begin() + index,
        Parameter(std::move(type), std::move(name), std::move(default_value)));
  }
  std::vector<Parameter> GetParameters() const { return parameters_; }
  std::vector<std::string> GetParameterNames() const {
    std::vector<std::string> names;
    std::transform(parameters_.begin(), parameters_.end(),
                   std::back_inserter(names),
                   [](const Parameter& p) { return p.name; });
    return names;
  }

  static constexpr int kAutomaticIndentation = -1;
  void PrintDeclaration(std::ostream& stream,
                        int indentation = kAutomaticIndentation) const;
  void PrintDefinition(std::ostream& stream,
                       const std::function<void(std::ostream&)>& builder,
                       int indentation = 0) const;
  void PrintInlineDefinition(std::ostream& stream,
                             const std::function<void(std::ostream&)>& builder,
                             int indentation = 2) const;
  void PrintBeginDefinition(std::ostream& stream, int indentation = 0) const;
  void PrintEndDefinition(std::ostream& stream, int indentation = 0) const;

 protected:
  void PrintDeclarationHeader(std::ostream& stream, int indentation) const;

 private:
  SourcePosition pos_;
  Class* owning_class_;
  std::string description_;
  std::string name_;
  std::string return_type_;
  std::vector<Parameter> parameters_;
  base::Flags<FunctionFlag> flags_;
};

DEFINE_OPERATORS_FOR_FLAGS(base::Flags<Function::FunctionFlag>)
#undef FUNCTION_FLAG_LIST

class File {
 public:
  explicit File(std::ostream& stream) : stream_(&stream) {}

  void BeginIncludeGuard(const std::string& name);
  void EndIncludeGuard(const std::string& name);
  void BeginNamespace(std::string name);
  void BeginNamespace(std::string name0, std::string name1);
  void EndNamespace(const std::string& name);
  void EndNamespace(const std::string& name0, const std::string& name1);

  void AddInclude(std::string include) { includes_.insert(std::move(include)); }

  template <typename T>
  File& operator<<(const T& value) {
    s() << value;
    return *this;
  }

 protected:
  std::ostream& s() { return *stream_; }

 private:
  std::ostream* stream_;
  std::set<std::string> includes_;
  std::stack<std::string> namespaces_;
};

class IncludeGuardScope {
 public:
  explicit IncludeGuardScope(File* file, std::string name)
      : file_(file), name_(std::move(name)) {
    file_->BeginIncludeGuard(name_);
  }
  IncludeGuardScope(const IncludeGuardScope&) = delete;
  IncludeGuardScope(IncludeGuardScope&& other) V8_NOEXCEPT : file_(nullptr),
                                                             name_() {
    std::swap(file_, other.file_);
    std::swap(name_, other.name_);
  }
  ~IncludeGuardScope() {
    if (file_) {
      file_->EndIncludeGuard(name_);
    }
  }
  IncludeGuardScope& operator=(const IncludeGuardScope&) = delete;
  IncludeGuardScope& operator=(IncludeGuardScope&& other) V8_NOEXCEPT {
    if (this != &other) {
      DCHECK_NULL(file_);
      DCHECK(name_.empty());
      std::swap(file_, other.file_);
      std::swap(name_, other.name_);
    }
    return *this;
  }

 private:
  File* file_;
  std::string name_;
};

}  // namespace cpp
}  // namespace torque
}  // namespace internal
}  // namespace v8

#endif  // V8_TORQUE_CPP_BUILDER_H_