//
// Copyright 2016 Pixar
//
// Licensed under the Apache License, Version 2.0 (the "Apache License")
// with the following modification; you may not use this file except in
// compliance with the Apache License and the following modification to it:
// Section 6. Trademarks. is deleted and replaced with:
//
// 6. Trademarks. This License does not grant permission to use the trade
//    names, trademarks, service marks, or product names of the Licensor
//    and its affiliates, except as required to comply with Section 4(c) of
//    the License and to reproduce the content of the NOTICE file.
//
// You may obtain a copy of the Apache License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the Apache License with the above modification is
// distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the Apache License for the specific
// language governing permissions and limitations under the Apache License.
//
#ifndef TF_PYOBJWRAPPER_H
#define TF_PYOBJWRAPPER_H

#include <boost/functional/hash.hpp>
#include <boost/python/object_fwd.hpp>
#include <boost/python/object_operators.hpp>
#include <boost/shared_ptr.hpp>

#include <iosfwd>

/*! \class TfPyObjWrapper
 *
 * Provides a wrapper around boost::python::object that works correctly for the
 * following basic operations regardless of the GIL state: default construction,
 * copy construction, assignment, (in)equality comparison, hash_value(), and
 * destruction.
 *
 * None of those work correctly in the presence of an unlocked GIL for
 * boost::python::object.  This class only actually acquires the GIL for default
 * construction, destruction and for some (in)equality comparisons.  The other
 * operations do not require taking the GIL.
 *
 * This is primarily useful in cases where a boost::python::object might be
 * destroyed without a locked GIL by a client blind to that fact.  This occurs
 * when a registry, for example, holds type-erased objects.  If one
 * of the type-erased objects in the registry happens to hold a
 * boost::python::object, that type-erased object must be destroyed while the
 * GIL is held but it's unreasonable to require that the registry know that.
 * This class helps solve that problem.
 *
 * This class also provides many of the operators that boost::python::object
 * provides, by virtue of deriving from boost::python::api::object_operators<T>.
 * However it is important to note that callers must ensure the GIL is held
 * before using these operators!
 */
class TfPyObjWrapper
    : public boost::python::api::object_operators<TfPyObjWrapper>
{
    typedef boost::python::object object;

public:

    //! \brief Default construct a TfPyObjWrapper holding a reference to python
    //! None.  The GIL need not be held by the caller.
    TfPyObjWrapper();

    //! \brief Construct a TfPyObjectWrapper wrapping \a obj.  The GIL must be
    //! held by the caller.  Note, allowing the implicit conversion is intended
    //! here.
    TfPyObjWrapper(object obj);

    //! \brief Underlying object access.  This method returns a reference, so
    //! technically, the GIL need not be held to call this.  However, the caller
    //! is strongly advised to ensure the GIL is held, since assigning this
    //! object to another or otherwise operating on the returned object requires
    //! it.
    object const &Get() const {
        return *_objectPtr;
    }

    //! \brief Underlying PyObject* access.  This method returns a pointer, so
    //! technically, the GIL need not be held to call this.  However, the caller
    //! is strongly advised to ensure the GIL is held, since assigning this
    //! object to another or otherwise operating on the returned object requires
    //! it.  The returned PyObject * is a "borrowed reference", meaning that the
    //! underlying object's reference count has not been incremented on behalf
    //! of the caller.
    PyObject *ptr() const;

    //! \brief Produce a hash code for this object.  Note that this does not
    //! attempt to hash the underlying python object, it returns a hash code
    //! that's suitable for hash-table lookup of TfPyObjWrapper instances, and
    //! does not require taking the python lock.
    friend inline size_t hash_value(TfPyObjWrapper const &o) {
        return (size_t) o.ptr();
    }

    //! \brief Equality.  Returns true if \a other refers to the same python
    //! object.
    bool operator==(TfPyObjWrapper const &other) const;

    //! \brief Inequality.  Returns false if \a other refers to the same python
    //! object.
    bool operator!=(TfPyObjWrapper const &other) const;

private:

    // Befriend object_operators to allow it access to implicit conversion to
    // boost::python::object.
    friend class boost::python::api::object_operators<TfPyObjWrapper>;
    operator object const &() const {
        return Get();
    }

    // Store a shared_ptr to a python object.
    boost::shared_ptr<object> _objectPtr;
};

#endif // TF_PYOBJWRAPPER_H
