// Copyright David Abrahams 2004. Distributed under the Boost
// Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
#include <boost/python/dict.hpp>
#include <boost/python/extract.hpp>

namespace boost { namespace python { namespace detail {
namespace
{
  // When returning list objects from methods, it may turn out that the
  // derived class is returning something else, perhaps something not
  // even derived from list. Since it is generally harmless for a
  // Boost.Python wrapper object to hold an object of a different
  // type, and because calling list() with an object may in fact
  // perform a conversion, the least-bad alternative is to assume that
  // we have a Python list object and stuff it into the list result.
  list assume_list(object const& o)
  {
      return list(detail::borrowed_reference(o.ptr()));
  }

  // No PyDict_CheckExact; roll our own.
  inline bool check_exact(dict_base const* p)
  {
      return  p->ptr()->ob_type == &PyDict_Type;
  }
}

detail::new_reference dict_base::call(object const& arg_)
{
    return (detail::new_reference)PyObject_CallFunction(
        (PyObject*)&PyDict_Type, "(O)", 
        arg_.ptr());
}

dict_base::dict_base()
    : object(detail::new_reference(PyDict_New()))
{}
    
dict_base::dict_base(object_cref data)
    : object(call(data))
{}
    
void dict_base::clear()
{
    if (check_exact(this))
        PyDict_Clear(this->ptr());
    else
        this->attr("clear")();
}

dict dict_base::copy()
{
    if (check_exact(this))
    {
        return dict(detail::new_reference(
                        PyDict_Copy(this->ptr())));
    }
    else
    {
        return dict(detail::borrowed_reference(
                        this->attr("copy")().ptr()
                        ));
    }
}

object dict_base::get(object_cref k) const
{
    if (check_exact(this))
    {
        PyObject* result = PyDict_GetItem(this->ptr(),k.ptr());
        return object(detail::borrowed_reference(result ? result : Py_None));
    }
    else
    {
        return this->attr("get")(k);
    }
}

object dict_base::get(object_cref k, object_cref d) const
{
    return this->attr("get")(k,d);
}

bool dict_base::has_key(object_cref k) const
{
    return extract<bool>(this->attr("has_key")(k)); 
}

list dict_base::items() const
{
    if (check_exact(this))
    {
        return list(detail::new_reference(
                        PyDict_Items(this->ptr())));
    }
    else
    {
        return assume_list(this->attr("items")());
    }
}

object dict_base::iteritems() const
{
    return this->attr("iteritems")();
}

object dict_base::iterkeys() const
{
    return this->attr("iterkeys")();
}

object dict_base::itervalues() const
{
    return this->attr("itervalues")();
}

list dict_base::keys() const
{
    if (check_exact(this))
    {
        return list(detail::new_reference(
                        PyDict_Keys(this->ptr())));
    }
    else
    {
        return assume_list(this->attr("keys")());
    }
}

tuple dict_base::popitem()
{
    return tuple(detail::borrowed_reference(
                     this->attr("popitem")().ptr()
                     ));
}

object dict_base::setdefault(object_cref k)
{
    return this->attr("setdefault")(k);
}

object dict_base::setdefault(object_cref k, object_cref d)
{
    return this->attr("setdefault")(k,d);
}

void dict_base::update(object_cref other)
{
    if (check_exact(this))
    {
        if (PyDict_Update(this->ptr(),other.ptr()) == -1)
            throw_error_already_set();
    }
    else
    {
        this->attr("update")(other);
    }
}

list dict_base::values() const
{
    if (check_exact(this))
    {
        return list(detail::new_reference(
                        PyDict_Values(this->ptr())));
    }
    else
    {
        return assume_list(this->attr("values")());
    }
}

}}}  // namespace boost::python
