//===-- PythonDataObjects.h--------------------------------------*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

#ifndef LLDB_PLUGINS_SCRIPTINTERPRETER_PYTHON_PYTHONDATAOBJECTS_H
#define LLDB_PLUGINS_SCRIPTINTERPRETER_PYTHON_PYTHONDATAOBJECTS_H

#ifndef LLDB_DISABLE_PYTHON

// LLDB Python header must be included first
#include "lldb-python.h"

#include "lldb/Host/File.h"
#include "lldb/Utility/StructuredData.h"

#include "llvm/ADT/ArrayRef.h"

namespace lldb_private {

class PythonBytes;
class PythonString;
class PythonList;
class PythonDictionary;
class PythonInteger;

class StructuredPythonObject : public StructuredData::Generic {
public:
  StructuredPythonObject() : StructuredData::Generic() {}

  StructuredPythonObject(void *obj) : StructuredData::Generic(obj) {
    Py_XINCREF(GetValue());
  }

  ~StructuredPythonObject() override {
    if (Py_IsInitialized())
      Py_XDECREF(GetValue());
    SetValue(nullptr);
  }

  bool IsValid() const override { return GetValue() && GetValue() != Py_None; }

  void Dump(Stream &s, bool pretty_print = true) const override;

private:
  DISALLOW_COPY_AND_ASSIGN(StructuredPythonObject);
};

enum class PyObjectType {
  Unknown,
  None,
  Boolean,
  Integer,
  Dictionary,
  List,
  String,
  Bytes,
  ByteArray,
  Module,
  Callable,
  Tuple,
  File
};

enum class PyRefType {
  Borrowed, // We are not given ownership of the incoming PyObject.
            // We cannot safely hold it without calling Py_INCREF.
  Owned     // We have ownership of the incoming PyObject.  We should
            // not call Py_INCREF.
};

enum class PyInitialValue { Invalid, Empty };

class PythonObject {
public:
  PythonObject() : m_py_obj(nullptr) {}

  PythonObject(PyRefType type, PyObject *py_obj) : m_py_obj(nullptr) {
    Reset(type, py_obj);
  }

  PythonObject(const PythonObject &rhs) : m_py_obj(nullptr) { Reset(rhs); }

  virtual ~PythonObject() { Reset(); }

  void Reset() {
    // Avoid calling the virtual method since it's not necessary
    // to actually validate the type of the PyObject if we're
    // just setting to null.
    if (Py_IsInitialized())
      Py_XDECREF(m_py_obj);
    m_py_obj = nullptr;
  }

  void Reset(const PythonObject &rhs) {
    // Avoid calling the virtual method if it's not necessary
    // to actually validate the type of the PyObject.
    if (!rhs.IsValid())
      Reset();
    else
      Reset(PyRefType::Borrowed, rhs.m_py_obj);
  }

  // PythonObject is implicitly convertible to PyObject *, which will call the
  // wrong overload.  We want to explicitly disallow this, since a PyObject
  // *always* owns its reference.  Therefore the overload which takes a
  // PyRefType doesn't make sense, and the copy constructor should be used.
  void Reset(PyRefType type, const PythonObject &ref) = delete;

  virtual void Reset(PyRefType type, PyObject *py_obj) {
    if (py_obj == m_py_obj)
      return;

    if (Py_IsInitialized())
      Py_XDECREF(m_py_obj);

    m_py_obj = py_obj;

    // If this is a borrowed reference, we need to convert it to
    // an owned reference by incrementing it.  If it is an owned
    // reference (for example the caller allocated it with PyDict_New()
    // then we must *not* increment it.
    if (Py_IsInitialized() && type == PyRefType::Borrowed)
      Py_XINCREF(m_py_obj);
  }

  void Dump() const {
    if (m_py_obj)
      _PyObject_Dump(m_py_obj);
    else
      puts("NULL");
  }

  void Dump(Stream &strm) const;

  PyObject *get() const { return m_py_obj; }

  PyObject *release() {
    PyObject *result = m_py_obj;
    m_py_obj = nullptr;
    return result;
  }

  PythonObject &operator=(const PythonObject &other) {
    Reset(PyRefType::Borrowed, other.get());
    return *this;
  }

  PyObjectType GetObjectType() const;

  PythonString Repr() const;

  PythonString Str() const;

  static PythonObject ResolveNameWithDictionary(llvm::StringRef name,
                                                const PythonDictionary &dict);

  template <typename T>
  static T ResolveNameWithDictionary(llvm::StringRef name,
                                     const PythonDictionary &dict) {
    return ResolveNameWithDictionary(name, dict).AsType<T>();
  }

  PythonObject ResolveName(llvm::StringRef name) const;

  template <typename T> T ResolveName(llvm::StringRef name) const {
    return ResolveName(name).AsType<T>();
  }

  bool HasAttribute(llvm::StringRef attribute) const;

  PythonObject GetAttributeValue(llvm::StringRef attribute) const;

  bool IsValid() const;

  bool IsAllocated() const;

  bool IsNone() const;

  template <typename T> T AsType() const {
    if (!T::Check(m_py_obj))
      return T();
    return T(PyRefType::Borrowed, m_py_obj);
  }

  StructuredData::ObjectSP CreateStructuredObject() const;

protected:
  PyObject *m_py_obj;
};

class PythonBytes : public PythonObject {
public:
  PythonBytes();
  explicit PythonBytes(llvm::ArrayRef<uint8_t> bytes);
  PythonBytes(const uint8_t *bytes, size_t length);
  PythonBytes(PyRefType type, PyObject *o);

  ~PythonBytes() override;

  static bool Check(PyObject *py_obj);

  // Bring in the no-argument base class version
  using PythonObject::Reset;

  void Reset(PyRefType type, PyObject *py_obj) override;

  llvm::ArrayRef<uint8_t> GetBytes() const;

  size_t GetSize() const;

  void SetBytes(llvm::ArrayRef<uint8_t> stringbytes);

  StructuredData::StringSP CreateStructuredString() const;
};

class PythonByteArray : public PythonObject {
public:
  PythonByteArray();
  explicit PythonByteArray(llvm::ArrayRef<uint8_t> bytes);
  PythonByteArray(const uint8_t *bytes, size_t length);
  PythonByteArray(PyRefType type, PyObject *o);
  PythonByteArray(const PythonBytes &object);

  ~PythonByteArray() override;

  static bool Check(PyObject *py_obj);

  // Bring in the no-argument base class version
  using PythonObject::Reset;

  void Reset(PyRefType type, PyObject *py_obj) override;

  llvm::ArrayRef<uint8_t> GetBytes() const;

  size_t GetSize() const;

  void SetBytes(llvm::ArrayRef<uint8_t> stringbytes);

  StructuredData::StringSP CreateStructuredString() const;
};

class PythonString : public PythonObject {
public:
  PythonString();
  explicit PythonString(llvm::StringRef string);
  explicit PythonString(const char *string);
  PythonString(PyRefType type, PyObject *o);

  ~PythonString() override;

  static bool Check(PyObject *py_obj);

  // Bring in the no-argument base class version
  using PythonObject::Reset;

  void Reset(PyRefType type, PyObject *py_obj) override;

  llvm::StringRef GetString() const;

  size_t GetSize() const;

  void SetString(llvm::StringRef string);

  StructuredData::StringSP CreateStructuredString() const;
};

class PythonInteger : public PythonObject {
public:
  PythonInteger();
  explicit PythonInteger(int64_t value);
  PythonInteger(PyRefType type, PyObject *o);

  ~PythonInteger() override;

  static bool Check(PyObject *py_obj);

  // Bring in the no-argument base class version
  using PythonObject::Reset;

  void Reset(PyRefType type, PyObject *py_obj) override;

  int64_t GetInteger() const;

  void SetInteger(int64_t value);

  StructuredData::IntegerSP CreateStructuredInteger() const;
};

class PythonBoolean : public PythonObject {
public:
  PythonBoolean() = default;
  explicit PythonBoolean(bool value);
  PythonBoolean(PyRefType type, PyObject *o);

  ~PythonBoolean() override = default;

  static bool Check(PyObject *py_obj);

  // Bring in the no-argument base class version
  using PythonObject::Reset;

  void Reset(PyRefType type, PyObject *py_obj) override;

  bool GetValue() const;

  void SetValue(bool value);

  StructuredData::BooleanSP CreateStructuredBoolean() const;
};

class PythonList : public PythonObject {
public:
  PythonList() {}
  explicit PythonList(PyInitialValue value);
  explicit PythonList(int list_size);
  PythonList(PyRefType type, PyObject *o);

  ~PythonList() override;

  static bool Check(PyObject *py_obj);

  // Bring in the no-argument base class version
  using PythonObject::Reset;

  void Reset(PyRefType type, PyObject *py_obj) override;

  uint32_t GetSize() const;

  PythonObject GetItemAtIndex(uint32_t index) const;

  void SetItemAtIndex(uint32_t index, const PythonObject &object);

  void AppendItem(const PythonObject &object);

  StructuredData::ArraySP CreateStructuredArray() const;
};

class PythonTuple : public PythonObject {
public:
  PythonTuple() {}
  explicit PythonTuple(PyInitialValue value);
  explicit PythonTuple(int tuple_size);
  PythonTuple(PyRefType type, PyObject *o);
  PythonTuple(std::initializer_list<PythonObject> objects);
  PythonTuple(std::initializer_list<PyObject *> objects);

  ~PythonTuple() override;

  static bool Check(PyObject *py_obj);

  // Bring in the no-argument base class version
  using PythonObject::Reset;

  void Reset(PyRefType type, PyObject *py_obj) override;

  uint32_t GetSize() const;

  PythonObject GetItemAtIndex(uint32_t index) const;

  void SetItemAtIndex(uint32_t index, const PythonObject &object);

  StructuredData::ArraySP CreateStructuredArray() const;
};

class PythonDictionary : public PythonObject {
public:
  PythonDictionary() {}
  explicit PythonDictionary(PyInitialValue value);
  PythonDictionary(PyRefType type, PyObject *o);

  ~PythonDictionary() override;

  static bool Check(PyObject *py_obj);

  // Bring in the no-argument base class version
  using PythonObject::Reset;

  void Reset(PyRefType type, PyObject *py_obj) override;

  uint32_t GetSize() const;

  PythonList GetKeys() const;

  PythonObject GetItemForKey(const PythonObject &key) const;
  void SetItemForKey(const PythonObject &key, const PythonObject &value);

  StructuredData::DictionarySP CreateStructuredDictionary() const;
};

class PythonModule : public PythonObject {
public:
  PythonModule();
  PythonModule(PyRefType type, PyObject *o);

  ~PythonModule() override;

  static bool Check(PyObject *py_obj);

  static PythonModule BuiltinsModule();

  static PythonModule MainModule();

  static PythonModule AddModule(llvm::StringRef module);

  static PythonModule ImportModule(llvm::StringRef module);

  // Bring in the no-argument base class version
  using PythonObject::Reset;

  void Reset(PyRefType type, PyObject *py_obj) override;

  PythonDictionary GetDictionary() const;
};

class PythonCallable : public PythonObject {
public:
  struct ArgInfo {
    size_t count;
    bool is_bound_method : 1;
    bool has_varargs : 1;
    bool has_kwargs : 1;
  };

  PythonCallable();
  PythonCallable(PyRefType type, PyObject *o);

  ~PythonCallable() override;

  static bool Check(PyObject *py_obj);

  // Bring in the no-argument base class version
  using PythonObject::Reset;

  void Reset(PyRefType type, PyObject *py_obj) override;

  ArgInfo GetNumArguments() const;

  PythonObject operator()();

  PythonObject operator()(std::initializer_list<PyObject *> args);

  PythonObject operator()(std::initializer_list<PythonObject> args);

  template <typename Arg, typename... Args>
  PythonObject operator()(const Arg &arg, Args... args) {
    return operator()({arg, args...});
  }
};

class PythonFile : public PythonObject {
public:
  PythonFile();
  PythonFile(File &file, const char *mode);
  PythonFile(const char *path, const char *mode);
  PythonFile(PyRefType type, PyObject *o);

  ~PythonFile() override;

  static bool Check(PyObject *py_obj);

  using PythonObject::Reset;

  void Reset(PyRefType type, PyObject *py_obj) override;
  void Reset(File &file, const char *mode);

  static uint32_t GetOptionsFromMode(llvm::StringRef mode);

  bool GetUnderlyingFile(File &file) const;
};

} // namespace lldb_private

#endif

#endif // LLDB_PLUGINS_SCRIPTINTERPRETER_PYTHON_PYTHONDATAOBJECTS_H
