// Protocol Buffers - Google's data interchange format
// Copyright 2008 Google Inc.  All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file or at
// https://developers.google.com/open-source/licenses/bsd

// Author: haberman@google.com (Josh Haberman)

#include "google/protobuf/pyext/map_container.h"

#include <Python.h>

#include <cstddef>
#include <cstdint>
#include <memory>
#include <string>

#include "google/protobuf/map.h"
#include "google/protobuf/map_field.h"
#include "google/protobuf/message.h"
#include "google/protobuf/pyext/message.h"
#include "google/protobuf/pyext/message_factory.h"
#include "google/protobuf/pyext/repeated_composite_container.h"
#include "google/protobuf/pyext/scoped_pyobject_ptr.h"

namespace google {
namespace protobuf {
namespace python {

// Functions that need access to map reflection functionality.
// They need to be contained in this class because it is friended.
class MapReflectionFriend {
 public:
  // Methods that are in common between the map types.
  static int Contains(PyObject* _self, PyObject* key);
  static Py_ssize_t Length(PyObject* _self);
  static PyObject* GetIterator(PyObject* _self);
  static PyObject* IterNext(PyObject* _self);
  static PyObject* MergeFrom(PyObject* _self, PyObject* arg);

  // Methods that differ between the map types.
  static PyObject* ScalarMapGetItem(PyObject* _self, PyObject* key);
  static PyObject* MessageMapGetItem(PyObject* _self, PyObject* key);
  static int ScalarMapSetItem(PyObject* _self, PyObject* key, PyObject* v);
  static int MessageMapSetItem(PyObject* _self, PyObject* key, PyObject* v);
  static PyObject* ScalarMapToStr(PyObject* _self);
  static PyObject* MessageMapToStr(PyObject* _self);
};

struct MapIterator {
  // clang-format off
  PyObject_HEAD

  std::unique_ptr<::google::protobuf::MapIterator> iter;
  // clang-format on

  // A pointer back to the container, so we can notice changes to the version.
  // We own a ref on this.
  MapContainer* container;

  // We need to keep a ref on the parent Message too, because
  // MapIterator::~MapIterator() accesses it.  Normally this would be ok because
  // the ref on container (above) would guarantee outlive semantics.  However in
  // the case of ClearField(), the MapContainer points to a different message,
  // a copy of the original.  But our iterator still points to the original,
  // which could now get deleted before us.
  //
  // To prevent this, we ensure that the Message will always stay alive as long
  // as this iterator does.  This is solely for the benefit of the MapIterator
  // destructor -- we should never actually access the iterator in this state
  // except to delete it.
  CMessage* parent;
  // The version of the map when we took the iterator to it.
  //
  // We store this so that if the map is modified during iteration we can throw
  // an error.
  uint64_t version;
};

Message* MapContainer::GetMutableMessage() {
  cmessage::AssureWritable(parent);
  return parent->message;
}

// Consumes a reference on the Python string object.
static bool PyStringToSTL(PyObject* py_string, std::string* stl_string) {
  char* value;
  Py_ssize_t value_len;

  if (!py_string) {
    return false;
  }
  if (PyBytes_AsStringAndSize(py_string, &value, &value_len) < 0) {
    Py_DECREF(py_string);
    return false;
  } else {
    stl_string->assign(value, value_len);
    Py_DECREF(py_string);
    return true;
  }
}

static bool PythonToMapKey(MapContainer* self, PyObject* obj, MapKey* key,
                           std::string* key_string) {
  const FieldDescriptor* field_descriptor =
      self->parent_field_descriptor->message_type()->map_key();
  switch (field_descriptor->cpp_type()) {
    case FieldDescriptor::CPPTYPE_INT32: {
      PROTOBUF_CHECK_GET_INT32(obj, value, false);
      CheckIntegerWithBool(obj, field_descriptor);
      key->SetInt32Value(value);
      break;
    }
    case FieldDescriptor::CPPTYPE_INT64: {
      PROTOBUF_CHECK_GET_INT64(obj, value, false);
      CheckIntegerWithBool(obj, field_descriptor);
      key->SetInt64Value(value);
      break;
    }
    case FieldDescriptor::CPPTYPE_UINT32: {
      PROTOBUF_CHECK_GET_UINT32(obj, value, false);
      CheckIntegerWithBool(obj, field_descriptor);
      key->SetUInt32Value(value);
      break;
    }
    case FieldDescriptor::CPPTYPE_UINT64: {
      PROTOBUF_CHECK_GET_UINT64(obj, value, false);
      CheckIntegerWithBool(obj, field_descriptor);
      key->SetUInt64Value(value);
      break;
    }
    case FieldDescriptor::CPPTYPE_BOOL: {
      PROTOBUF_CHECK_GET_BOOL(obj, value, false);
      key->SetBoolValue(value);
      break;
    }
    case FieldDescriptor::CPPTYPE_STRING: {
      if (!PyStringToSTL(CheckString(obj, field_descriptor), key_string)) {
        return false;
      }
      key->SetStringValue(*key_string);
      break;
    }
    default:
      PyErr_Format(PyExc_SystemError, "Type %d cannot be a map key",
                   field_descriptor->cpp_type());
      return false;
  }
  return true;
}

static PyObject* MapKeyToPython(MapContainer* self, const MapKey& key) {
  const FieldDescriptor* field_descriptor =
      self->parent_field_descriptor->message_type()->map_key();
  switch (field_descriptor->cpp_type()) {
    case FieldDescriptor::CPPTYPE_INT32:
      return PyLong_FromLong(key.GetInt32Value());
    case FieldDescriptor::CPPTYPE_INT64:
      return PyLong_FromLongLong(key.GetInt64Value());
    case FieldDescriptor::CPPTYPE_UINT32:
      return PyLong_FromSize_t(key.GetUInt32Value());
    case FieldDescriptor::CPPTYPE_UINT64:
      return PyLong_FromUnsignedLongLong(key.GetUInt64Value());
    case FieldDescriptor::CPPTYPE_BOOL:
      return PyBool_FromLong(key.GetBoolValue());
    case FieldDescriptor::CPPTYPE_STRING:
      return ToStringObject(field_descriptor, key.GetStringValue());
    default:
      PyErr_Format(PyExc_SystemError, "Couldn't convert type %d to value",
                   field_descriptor->cpp_type());
      return nullptr;
  }
}

// This is only used for ScalarMap, so we don't need to handle the
// CPPTYPE_MESSAGE case.
PyObject* MapValueRefToPython(MapContainer* self, const MapValueRef& value) {
  const FieldDescriptor* field_descriptor =
      self->parent_field_descriptor->message_type()->map_value();
  switch (field_descriptor->cpp_type()) {
    case FieldDescriptor::CPPTYPE_INT32:
      return PyLong_FromLong(value.GetInt32Value());
    case FieldDescriptor::CPPTYPE_INT64:
      return PyLong_FromLongLong(value.GetInt64Value());
    case FieldDescriptor::CPPTYPE_UINT32:
      return PyLong_FromSize_t(value.GetUInt32Value());
    case FieldDescriptor::CPPTYPE_UINT64:
      return PyLong_FromUnsignedLongLong(value.GetUInt64Value());
    case FieldDescriptor::CPPTYPE_FLOAT:
      return PyFloat_FromDouble(value.GetFloatValue());
    case FieldDescriptor::CPPTYPE_DOUBLE:
      return PyFloat_FromDouble(value.GetDoubleValue());
    case FieldDescriptor::CPPTYPE_BOOL:
      return PyBool_FromLong(value.GetBoolValue());
    case FieldDescriptor::CPPTYPE_STRING:
      return ToStringObject(field_descriptor, value.GetStringValue());
    case FieldDescriptor::CPPTYPE_ENUM:
      return PyLong_FromLong(value.GetEnumValue());
    default:
      PyErr_Format(PyExc_SystemError, "Couldn't convert type %d to value",
                   field_descriptor->cpp_type());
      return nullptr;
  }
}

// This is only used for ScalarMap, so we don't need to handle the
// CPPTYPE_MESSAGE case.
static bool PythonToMapValueRef(MapContainer* self, PyObject* obj,
                                bool allow_unknown_enum_values,
                                MapValueRef* value_ref) {
  const FieldDescriptor* field_descriptor =
      self->parent_field_descriptor->message_type()->map_value();
  switch (field_descriptor->cpp_type()) {
    case FieldDescriptor::CPPTYPE_INT32: {
      PROTOBUF_CHECK_GET_INT32(obj, value, false);
      CheckIntegerWithBool(obj, field_descriptor);
      value_ref->SetInt32Value(value);
      return true;
    }
    case FieldDescriptor::CPPTYPE_INT64: {
      PROTOBUF_CHECK_GET_INT64(obj, value, false);
      CheckIntegerWithBool(obj, field_descriptor);
      value_ref->SetInt64Value(value);
      return true;
    }
    case FieldDescriptor::CPPTYPE_UINT32: {
      PROTOBUF_CHECK_GET_UINT32(obj, value, false);
      CheckIntegerWithBool(obj, field_descriptor);
      value_ref->SetUInt32Value(value);
      return true;
    }
    case FieldDescriptor::CPPTYPE_UINT64: {
      PROTOBUF_CHECK_GET_UINT64(obj, value, false);
      CheckIntegerWithBool(obj, field_descriptor);
      value_ref->SetUInt64Value(value);
      return true;
    }
    case FieldDescriptor::CPPTYPE_FLOAT: {
      PROTOBUF_CHECK_GET_FLOAT(obj, value, false);
      value_ref->SetFloatValue(value);
      return true;
    }
    case FieldDescriptor::CPPTYPE_DOUBLE: {
      PROTOBUF_CHECK_GET_DOUBLE(obj, value, false);
      value_ref->SetDoubleValue(value);
      return true;
    }
    case FieldDescriptor::CPPTYPE_BOOL: {
      PROTOBUF_CHECK_GET_BOOL(obj, value, false);
      value_ref->SetBoolValue(value);
      return true;
    }
    case FieldDescriptor::CPPTYPE_STRING: {
      std::string str;
      if (!PyStringToSTL(CheckString(obj, field_descriptor), &str)) {
        return false;
      }
      value_ref->SetStringValue(str);
      return true;
    }
    case FieldDescriptor::CPPTYPE_ENUM: {
      PROTOBUF_CHECK_GET_INT32(obj, value, false);
      CheckIntegerWithBool(obj, field_descriptor);
      if (allow_unknown_enum_values) {
        value_ref->SetEnumValue(value);
        return true;
      } else {
        const EnumDescriptor* enum_descriptor = field_descriptor->enum_type();
        const EnumValueDescriptor* enum_value =
            enum_descriptor->FindValueByNumber(value);
        if (enum_value != nullptr) {
          value_ref->SetEnumValue(value);
          return true;
        } else {
          PyErr_Format(PyExc_ValueError, "Unknown enum value: %d", value);
          return false;
        }
      }
      break;
    }
    default:
      PyErr_Format(PyExc_SystemError,
                   "Setting value to a field of unknown type %d",
                   field_descriptor->cpp_type());
      return false;
  }
}

// Map methods common to ScalarMap and MessageMap //////////////////////////////

static MapContainer* GetMap(PyObject* obj) {
  return reinterpret_cast<MapContainer*>(obj);
}

Py_ssize_t MapReflectionFriend::Length(PyObject* _self) {
  MapContainer* self = GetMap(_self);
  const google::protobuf::Message* message = self->parent->message;
  return message->GetReflection()->MapSize(*message,
                                           self->parent_field_descriptor);
}

PyObject* Clear(PyObject* _self) {
  MapContainer* self = GetMap(_self);
  Message* message = self->GetMutableMessage();
  const Reflection* reflection = message->GetReflection();

  reflection->ClearField(message, self->parent_field_descriptor);

  Py_RETURN_NONE;
}

PyObject* GetEntryClass(PyObject* _self) {
  MapContainer* self = GetMap(_self);
  CMessageClass* message_class = message_factory::GetMessageClass(
      cmessage::GetFactoryForMessage(self->parent),
      self->parent_field_descriptor->message_type());
  Py_XINCREF(message_class);
  return reinterpret_cast<PyObject*>(message_class);
}

PyObject* MapReflectionFriend::MergeFrom(PyObject* _self, PyObject* arg) {
  MapContainer* self = GetMap(_self);
  if (!PyObject_TypeCheck(arg, ScalarMapContainer_Type) &&
      !PyObject_TypeCheck(arg, MessageMapContainer_Type)) {
    PyErr_SetString(PyExc_AttributeError, "Not a map field");
    return nullptr;
  }
  MapContainer* other_map = GetMap(arg);
  Message* message = self->GetMutableMessage();
  const Message* other_message = other_map->parent->message;
  const Reflection* reflection = message->GetReflection();
  const Reflection* other_reflection = other_message->GetReflection();
  internal::MapFieldBase* field =
      reflection->MutableMapData(message, self->parent_field_descriptor);
  const internal::MapFieldBase* other_field = other_reflection->GetMapData(
      *other_message, other_map->parent_field_descriptor);
  field->MergeFrom(*other_field);
  self->version++;
  Py_RETURN_NONE;
}

int MapReflectionFriend::Contains(PyObject* _self, PyObject* key) {
  MapContainer* self = GetMap(_self);

  const Message* message = self->parent->message;
  const Reflection* reflection = message->GetReflection();
  std::string map_key_string;
  MapKey map_key;

  if (!PythonToMapKey(self, key, &map_key, &map_key_string)) {
    return -1;
  }

  if (reflection->ContainsMapKey(*message, self->parent_field_descriptor,
                                 map_key)) {
    return 1;
  } else {
    return 0;
  }
}

// ScalarMap ///////////////////////////////////////////////////////////////////

MapContainer* NewScalarMapContainer(
    CMessage* parent, const google::protobuf::FieldDescriptor* parent_field_descriptor) {
  if (!CheckFieldBelongsToMessage(parent_field_descriptor, parent->message)) {
    return nullptr;
  }

  PyObject* obj(PyType_GenericAlloc(ScalarMapContainer_Type, 0));
  if (obj == nullptr) {
    PyErr_Format(PyExc_RuntimeError, "Could not allocate new container.");
    return nullptr;
  }

  MapContainer* self = GetMap(obj);

  Py_INCREF(parent);
  self->parent = parent;
  self->parent_field_descriptor = parent_field_descriptor;
  self->version = 0;

  return self;
}

PyObject* MapReflectionFriend::ScalarMapGetItem(PyObject* _self,
                                                PyObject* key) {
  MapContainer* self = GetMap(_self);

  Message* message = self->GetMutableMessage();
  const Reflection* reflection = message->GetReflection();
  std::string map_key_string;
  MapKey map_key;
  MapValueRef value;

  if (!PythonToMapKey(self, key, &map_key, &map_key_string)) {
    return nullptr;
  }

  if (reflection->InsertOrLookupMapValue(message, self->parent_field_descriptor,
                                         map_key, &value)) {
    self->version++;
  }

  return MapValueRefToPython(self, value);
}

int MapReflectionFriend::ScalarMapSetItem(PyObject* _self, PyObject* key,
                                          PyObject* v) {
  MapContainer* self = GetMap(_self);

  Message* message = self->GetMutableMessage();
  const Reflection* reflection = message->GetReflection();
  std::string map_key_string;
  MapKey map_key;
  MapValueRef value;

  if (!PythonToMapKey(self, key, &map_key, &map_key_string)) {
    return -1;
  }

  if (v) {
    // Set item to v.
    if (reflection->InsertOrLookupMapValue(
            message, self->parent_field_descriptor, map_key, &value)) {
      self->version++;
    }

    if (!PythonToMapValueRef(self, v,
                             !self->parent_field_descriptor->message_type()
                                  ->map_value()
                                  ->legacy_enum_field_treated_as_closed(),
                             &value)) {
      return -1;
    }
    return 0;
  } else {
    // Delete key from map.
    if (reflection->DeleteMapValue(message, self->parent_field_descriptor,
                                   map_key)) {
      self->version++;
      return 0;
    } else {
      PyErr_Format(PyExc_KeyError, "Key not present in map");
      return -1;
    }
  }
}

static PyObject* ScalarMapSetdefault(PyObject* self, PyObject* args) {
  PyObject* key = nullptr;
  PyObject* default_value = Py_None;

  if (!PyArg_UnpackTuple(args, "setdefault", 1, 2, &key, &default_value)) {
    return nullptr;
  }

  if (default_value == Py_None) {
    PyErr_Format(PyExc_ValueError,
                 "The value for scalar map setdefault must be set.");
    return nullptr;
  }

  int is_present = MapReflectionFriend::Contains(self, key);
  if (is_present < 0) {
    return nullptr;
  }
  if (is_present) {
    return MapReflectionFriend::ScalarMapGetItem(self, key);
  }

  if (MapReflectionFriend::ScalarMapSetItem(self, key, default_value) < 0) {
    return nullptr;
  }
  Py_INCREF(default_value);
  return default_value;
}

static PyObject* ScalarMapGet(PyObject* self, PyObject* args,
                              PyObject* kwargs) {
  static const char* kwlist[] = {"key", "default", nullptr};
  PyObject* key;
  PyObject* default_value = nullptr;
  if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O|O",
                                   const_cast<char**>(kwlist), &key,
                                   &default_value)) {
    return nullptr;
  }

  int is_present = MapReflectionFriend::Contains(self, key);
  if (is_present < 0) {
    return nullptr;
  }

  if (is_present) {
    return MapReflectionFriend::ScalarMapGetItem(self, key);
  } else {
    if (default_value != nullptr) {
      Py_INCREF(default_value);
      return default_value;
    } else {
      Py_RETURN_NONE;
    }
  }
}

PyObject* MapReflectionFriend::ScalarMapToStr(PyObject* _self) {
  ScopedPyObjectPtr dict(PyDict_New());
  if (dict == nullptr) {
    return nullptr;
  }
  ScopedPyObjectPtr key;
  ScopedPyObjectPtr value;

  MapContainer* self = GetMap(_self);
  Message* message = self->GetMutableMessage();
  const Reflection* reflection = message->GetReflection();
  for (google::protobuf::MapIterator it =
           reflection->MapBegin(message, self->parent_field_descriptor);
       it != reflection->MapEnd(message, self->parent_field_descriptor); ++it) {
    key.reset(MapKeyToPython(self, it.GetKey()));
    if (key == nullptr) {
      return nullptr;
    }
    value.reset(MapValueRefToPython(self, it.GetValueRef()));
    if (value == nullptr) {
      return nullptr;
    }
    if (PyDict_SetItem(dict.get(), key.get(), value.get()) < 0) {
      return nullptr;
    }
  }
  return PyObject_Repr(dict.get());
}

static void ScalarMapDealloc(PyObject* _self) {
  MapContainer* self = GetMap(_self);
  self->RemoveFromParentCache();
  PyTypeObject* type = Py_TYPE(_self);
  type->tp_free(_self);
  if (type->tp_flags & Py_TPFLAGS_HEAPTYPE) {
    // With Python3, the Map class is not static, and must be managed.
    Py_DECREF(type);
  }
}

static PyMethodDef ScalarMapMethods[] = {
    {"clear", (PyCFunction)Clear, METH_NOARGS,
     "Removes all elements from the map."},
    {"setdefault", (PyCFunction)ScalarMapSetdefault, METH_VARARGS,
     "If the key does not exist, insert the key, with the specified value"},
    {"get", (PyCFunction)ScalarMapGet, METH_VARARGS | METH_KEYWORDS,
     "Gets the value for the given key if present, or otherwise a default"},
    {"GetEntryClass", (PyCFunction)GetEntryClass, METH_NOARGS,
     "Return the class used to build Entries of (key, value) pairs."},
    {"MergeFrom", (PyCFunction)MapReflectionFriend::MergeFrom, METH_O,
     "Merges a map into the current map."},
    /*
    { "__deepcopy__", (PyCFunction)DeepCopy, METH_VARARGS,
      "Makes a deep copy of the class." },
    { "__reduce__", (PyCFunction)Reduce, METH_NOARGS,
      "Outputs picklable representation of the repeated field." },
    */
    {nullptr, nullptr},
};

PyTypeObject* ScalarMapContainer_Type;
static PyType_Slot ScalarMapContainer_Type_slots[] = {
    {Py_tp_dealloc, (void*)ScalarMapDealloc},
    {Py_mp_length, (void*)MapReflectionFriend::Length},
    {Py_mp_subscript, (void*)MapReflectionFriend::ScalarMapGetItem},
    {Py_mp_ass_subscript, (void*)MapReflectionFriend::ScalarMapSetItem},
    {Py_sq_contains, (void*)MapReflectionFriend::Contains},
    {Py_tp_methods, (void*)ScalarMapMethods},
    {Py_tp_iter, (void*)MapReflectionFriend::GetIterator},
    {Py_tp_repr, (void*)MapReflectionFriend::ScalarMapToStr},
    {0, nullptr},
};

PyType_Spec ScalarMapContainer_Type_spec = {
    FULL_MODULE_NAME ".ScalarMapContainer", sizeof(MapContainer), 0,
    Py_TPFLAGS_DEFAULT, ScalarMapContainer_Type_slots};

// MessageMap //////////////////////////////////////////////////////////////////

static MessageMapContainer* GetMessageMap(PyObject* obj) {
  return reinterpret_cast<MessageMapContainer*>(obj);
}

static PyObject* GetCMessage(MessageMapContainer* self, Message* message) {
  // Get or create the CMessage object corresponding to this message.
  return self->parent
      ->BuildSubMessageFromPointer(self->parent_field_descriptor, message,
                                   self->message_class)
      ->AsPyObject();
}

MessageMapContainer* NewMessageMapContainer(
    CMessage* parent, const google::protobuf::FieldDescriptor* parent_field_descriptor,
    CMessageClass* message_class) {
  if (!CheckFieldBelongsToMessage(parent_field_descriptor, parent->message)) {
    return nullptr;
  }

  PyObject* obj = PyType_GenericAlloc(MessageMapContainer_Type, 0);
  if (obj == nullptr) {
    PyErr_SetString(PyExc_RuntimeError, "Could not allocate new container.");
    return nullptr;
  }

  MessageMapContainer* self = GetMessageMap(obj);

  Py_INCREF(parent);
  self->parent = parent;
  self->parent_field_descriptor = parent_field_descriptor;
  self->version = 0;

  Py_INCREF(message_class);
  self->message_class = message_class;

  return self;
}

int MapReflectionFriend::MessageMapSetItem(PyObject* _self, PyObject* key,
                                           PyObject* v) {
  if (v) {
    PyErr_Format(PyExc_ValueError,
                 "Direct assignment of submessage not allowed");
    return -1;
  }

  // Now we know that this is a delete, not a set.

  MessageMapContainer* self = GetMessageMap(_self);
  Message* message = self->GetMutableMessage();
  const Reflection* reflection = message->GetReflection();
  std::string map_key_string;
  MapKey map_key;
  MapValueRef value;

  self->version++;

  if (!PythonToMapKey(self, key, &map_key, &map_key_string)) {
    return -1;
  }

  // Delete key from map.
  if (reflection->ContainsMapKey(*message, self->parent_field_descriptor,
                                 map_key)) {
    // Delete key from CMessage dict.
    MapValueRef value;
    reflection->InsertOrLookupMapValue(message, self->parent_field_descriptor,
                                       map_key, &value);
    Message* sub_message = value.MutableMessageValue();
    // If there is a living weak reference to an item, we "Release" it,
    // otherwise we just discard the C++ value.
    if (CMessage* released =
            self->parent->MaybeReleaseSubMessage(sub_message)) {
      Message* msg = released->message;
      released->message = msg->New();
      msg->GetReflection()->Swap(msg, released->message);
    }

    // Delete key from map.
    reflection->DeleteMapValue(message, self->parent_field_descriptor, map_key);
    return 0;
  } else {
    PyErr_Format(PyExc_KeyError, "Key not present in map");
    return -1;
  }
}

PyObject* MapReflectionFriend::MessageMapGetItem(PyObject* _self,
                                                 PyObject* key) {
  MessageMapContainer* self = GetMessageMap(_self);

  Message* message = self->GetMutableMessage();
  const Reflection* reflection = message->GetReflection();
  std::string map_key_string;
  MapKey map_key;
  MapValueRef value;

  if (!PythonToMapKey(self, key, &map_key, &map_key_string)) {
    return nullptr;
  }

  if (reflection->InsertOrLookupMapValue(message, self->parent_field_descriptor,
                                         map_key, &value)) {
    self->version++;
  }

  return GetCMessage(self, value.MutableMessageValue());
}

PyObject* MapReflectionFriend::MessageMapToStr(PyObject* _self) {
  ScopedPyObjectPtr dict(PyDict_New());
  if (dict == nullptr) {
    return nullptr;
  }
  ScopedPyObjectPtr key;
  ScopedPyObjectPtr value;

  MessageMapContainer* self = GetMessageMap(_self);
  Message* message = self->GetMutableMessage();
  const Reflection* reflection = message->GetReflection();
  for (google::protobuf::MapIterator it =
           reflection->MapBegin(message, self->parent_field_descriptor);
       it != reflection->MapEnd(message, self->parent_field_descriptor); ++it) {
    key.reset(MapKeyToPython(self, it.GetKey()));
    if (key == nullptr) {
      return nullptr;
    }
    value.reset(GetCMessage(self, it.MutableValueRef()->MutableMessageValue()));
    if (value == nullptr) {
      return nullptr;
    }
    if (PyDict_SetItem(dict.get(), key.get(), value.get()) < 0) {
      return nullptr;
    }
  }
  return PyObject_Repr(dict.get());
}

static PyObject* MessageMapSetdefault(PyObject* self, PyObject* args) {
  PyErr_Format(PyExc_NotImplementedError,
               "Set message map value directly is not supported.");
  return nullptr;
}

PyObject* MessageMapGet(PyObject* self, PyObject* args, PyObject* kwargs) {
  static const char* kwlist[] = {"key", "default", nullptr};
  PyObject* key;
  PyObject* default_value = nullptr;
  if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O|O",
                                   const_cast<char**>(kwlist), &key,
                                   &default_value)) {
    return nullptr;
  }

  int is_present = MapReflectionFriend::Contains(self, key);
  if (is_present < 0) {
    return nullptr;
  }

  if (is_present) {
    return MapReflectionFriend::MessageMapGetItem(self, key);
  } else {
    if (default_value != nullptr) {
      Py_INCREF(default_value);
      return default_value;
    } else {
      Py_RETURN_NONE;
    }
  }
}

static void MessageMapDealloc(PyObject* _self) {
  MessageMapContainer* self = GetMessageMap(_self);
  self->RemoveFromParentCache();
  Py_DECREF(self->message_class);
  PyTypeObject* type = Py_TYPE(_self);
  type->tp_free(_self);
  if (type->tp_flags & Py_TPFLAGS_HEAPTYPE) {
    // With Python3, the Map class is not static, and must be managed.
    Py_DECREF(type);
  }
}

static PyMethodDef MessageMapMethods[] = {
    {"clear", (PyCFunction)Clear, METH_NOARGS,
     "Removes all elements from the map."},
    {"setdefault", (PyCFunction)MessageMapSetdefault, METH_VARARGS,
     "setdefault is disallowed in MessageMap."},
    {"get", (PyCFunction)MessageMapGet, METH_VARARGS | METH_KEYWORDS,
     "Gets the value for the given key if present, or otherwise a default"},
    {"get_or_create", MapReflectionFriend::MessageMapGetItem, METH_O,
     "Alias for getitem, useful to make explicit that the map is mutated."},
    {"GetEntryClass", (PyCFunction)GetEntryClass, METH_NOARGS,
     "Return the class used to build Entries of (key, value) pairs."},
    {"MergeFrom", (PyCFunction)MapReflectionFriend::MergeFrom, METH_O,
     "Merges a map into the current map."},
    /*
    { "__deepcopy__", (PyCFunction)DeepCopy, METH_VARARGS,
      "Makes a deep copy of the class." },
    { "__reduce__", (PyCFunction)Reduce, METH_NOARGS,
      "Outputs picklable representation of the repeated field." },
    */
    {nullptr, nullptr},
};

PyTypeObject* MessageMapContainer_Type;
static PyType_Slot MessageMapContainer_Type_slots[] = {
    {Py_tp_dealloc, (void*)MessageMapDealloc},
    {Py_mp_length, (void*)MapReflectionFriend::Length},
    {Py_mp_subscript, (void*)MapReflectionFriend::MessageMapGetItem},
    {Py_mp_ass_subscript, (void*)MapReflectionFriend::MessageMapSetItem},
    {Py_sq_contains, (void*)MapReflectionFriend::Contains},
    {Py_tp_methods, (void*)MessageMapMethods},
    {Py_tp_iter, (void*)MapReflectionFriend::GetIterator},
    {Py_tp_repr, (void*)MapReflectionFriend::MessageMapToStr},
    {0, nullptr}};

PyType_Spec MessageMapContainer_Type_spec = {
    FULL_MODULE_NAME ".MessageMapContainer", sizeof(MessageMapContainer), 0,
    Py_TPFLAGS_DEFAULT, MessageMapContainer_Type_slots};

// MapIterator /////////////////////////////////////////////////////////////////

static MapIterator* GetIter(PyObject* obj) {
  return reinterpret_cast<MapIterator*>(obj);
}

PyObject* MapReflectionFriend::GetIterator(PyObject* _self) {
  MapContainer* self = GetMap(_self);

  ScopedPyObjectPtr obj(PyType_GenericAlloc(&MapIterator_Type, 0));
  if (obj == nullptr) {
    return PyErr_Format(PyExc_KeyError, "Could not allocate iterator");
  }

  MapIterator* iter = GetIter(obj.get());

  Py_INCREF(self);
  iter->container = self;
  iter->version = self->version;
  Py_INCREF(self->parent);
  iter->parent = self->parent;

  if (MapReflectionFriend::Length(_self) > 0) {
    Message* message = self->GetMutableMessage();
    const Reflection* reflection = message->GetReflection();

    iter->iter.reset(new ::google::protobuf::MapIterator(
        reflection->MapBegin(message, self->parent_field_descriptor)));
  }

  return obj.release();
}

PyObject* MapReflectionFriend::IterNext(PyObject* _self) {
  MapIterator* self = GetIter(_self);

  // This won't catch mutations to the map performed by MergeFrom(); no easy way
  // to address that.
  if (self->version != self->container->version) {
    return PyErr_Format(PyExc_RuntimeError, "Map modified during iteration.");
  }
  if (self->parent != self->container->parent) {
    return PyErr_Format(PyExc_RuntimeError, "Map cleared during iteration.");
  }

  if (self->iter == nullptr) {
    return nullptr;
  }

  Message* message = self->container->GetMutableMessage();
  const Reflection* reflection = message->GetReflection();

  if (*self->iter ==
      reflection->MapEnd(message, self->container->parent_field_descriptor)) {
    return nullptr;
  }

  PyObject* ret = MapKeyToPython(self->container, self->iter->GetKey());

  ++(*self->iter);

  return ret;
}

static void DeallocMapIterator(PyObject* _self) {
  MapIterator* self = GetIter(_self);
  self->iter.reset();
  Py_CLEAR(self->container);
  Py_CLEAR(self->parent);
  Py_TYPE(_self)->tp_free(_self);
}

PyTypeObject MapIterator_Type = {
    PyVarObject_HEAD_INIT(&PyType_Type, 0) FULL_MODULE_NAME
    ".MapIterator",       //  tp_name
    sizeof(MapIterator),  //  tp_basicsize
    0,                    //  tp_itemsize
    DeallocMapIterator,   //  tp_dealloc
#if PY_VERSION_HEX < 0x03080000
    nullptr,  // tp_print
#else
    0,  // tp_vectorcall_offset
#endif
    nullptr,                        //  tp_getattr
    nullptr,                        //  tp_setattr
    nullptr,                        //  tp_compare
    nullptr,                        //  tp_repr
    nullptr,                        //  tp_as_number
    nullptr,                        //  tp_as_sequence
    nullptr,                        //  tp_as_mapping
    nullptr,                        //  tp_hash
    nullptr,                        //  tp_call
    nullptr,                        //  tp_str
    nullptr,                        //  tp_getattro
    nullptr,                        //  tp_setattro
    nullptr,                        //  tp_as_buffer
    Py_TPFLAGS_DEFAULT,             //  tp_flags
    "A scalar map iterator",        //  tp_doc
    nullptr,                        //  tp_traverse
    nullptr,                        //  tp_clear
    nullptr,                        //  tp_richcompare
    0,                              //  tp_weaklistoffset
    PyObject_SelfIter,              //  tp_iter
    MapReflectionFriend::IterNext,  //  tp_iternext
    nullptr,                        //  tp_methods
    nullptr,                        //  tp_members
    nullptr,                        //  tp_getset
    nullptr,                        //  tp_base
    nullptr,                        //  tp_dict
    nullptr,                        //  tp_descr_get
    nullptr,                        //  tp_descr_set
    0,                              //  tp_dictoffset
    nullptr,                        //  tp_init
};

PyTypeObject* Py_AddClassWithRegister(PyType_Spec* spec, PyObject* virtual_base,
                                      const char** methods) {
  PyObject* type = PyType_FromSpec(spec);
  PyObject* ret1 = PyObject_CallMethod(virtual_base, "register", "O", type);
  if (!ret1) {
    Py_XDECREF(type);
    return nullptr;
  }
  for (size_t i = 0; methods[i] != nullptr; i++) {
    PyObject* method = PyObject_GetAttrString(virtual_base, methods[i]);
    if (!method) {
      Py_XDECREF(type);
      return nullptr;
    }
    int ret2 = PyObject_SetAttrString(type, methods[i], method);
    if (ret2 < 0) {
      Py_XDECREF(type);
      return nullptr;
    }
  }

  return (PyTypeObject*)type;
}

bool InitMapContainers() {
  // ScalarMapContainer_Type derives from our MutableMapping type.
  ScopedPyObjectPtr abc(PyImport_ImportModule("collections.abc"));
  if (abc == nullptr) {
    return false;
  }

  ScopedPyObjectPtr mutable_mapping(
      PyObject_GetAttrString(abc.get(), "MutableMapping"));
  if (mutable_mapping == nullptr) {
    return false;
  }

  const char* methods[] = {"keys", "items",   "values", "__eq__", "__ne__",
                           "pop",  "popitem", "update", nullptr};

  ScalarMapContainer_Type =
      reinterpret_cast<PyTypeObject*>(Py_AddClassWithRegister(
          &ScalarMapContainer_Type_spec, mutable_mapping.get(), methods));

  if (PyType_Ready(&MapIterator_Type) < 0) {
    return false;
  }

  MessageMapContainer_Type =
      reinterpret_cast<PyTypeObject*>(Py_AddClassWithRegister(
          &MessageMapContainer_Type_spec, mutable_mapping.get(), methods));
  return true;
}

}  // namespace python
}  // namespace protobuf
}  // namespace google
