//-----------------------------------------------------------------------------
//
// Copyright (c) 1998 - 2007, The Regents of the University of California
// Produced at the Lawrence Livermore National Laboratory
// All rights reserved.
//
// This file is part of PyCXX. For details,see http://cxx.sourceforge.net/. The
// full copyright notice is contained in the file COPYRIGHT located at the root
// of the PyCXX distribution.
//
// Redistribution  and  use  in  source  and  binary  forms,  with  or  without
// modification, are permitted provided that the following conditions are met:
//
//  - Redistributions of  source code must  retain the above  copyright notice,
//    this list of conditions and the disclaimer below.
//  - Redistributions in binary form must reproduce the above copyright notice,
//    this  list of  conditions  and  the  disclaimer (as noted below)  in  the
//    documentation and/or materials provided with the distribution.
//  - Neither the name of the UC/LLNL nor  the names of its contributors may be
//    used to  endorse or  promote products derived from  this software without
//    specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT  HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR  IMPLIED WARRANTIES, INCLUDING,  BUT NOT  LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND  FITNESS FOR A PARTICULAR  PURPOSE
// ARE  DISCLAIMED.  IN  NO  EVENT  SHALL  THE  REGENTS  OF  THE  UNIVERSITY OF
// CALIFORNIA, THE U.S.  DEPARTMENT  OF  ENERGY OR CONTRIBUTORS BE  LIABLE  FOR
// ANY  DIRECT,  INDIRECT,  INCIDENTAL,  SPECIAL,  EXEMPLARY,  OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT  LIMITED TO, PROCUREMENT OF  SUBSTITUTE GOODS OR
// SERVICES; LOSS OF  USE, DATA, OR PROFITS; OR  BUSINESS INTERRUPTION) HOWEVER
// CAUSED  AND  ON  ANY  THEORY  OF  LIABILITY,  WHETHER  IN  CONTRACT,  STRICT
// LIABILITY, OR TORT  (INCLUDING NEGLIGENCE OR OTHERWISE)  ARISING IN ANY  WAY
// OUT OF THE  USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
// DAMAGE.
//
//-----------------------------------------------------------------------------

#ifndef __CXX_ExtensionClass__h
#define __CXX_ExtensionClass__h

#define PYCXX_NOARGS_METHOD_NAME( NAME ) _callNoArgsMethod__##NAME
#define PYCXX_VARARGS_METHOD_NAME( NAME ) _callVarArgsMethod__##NAME
#define PYCXX_KEYWORDS_METHOD_NAME( NAME ) _callKeywordsMethod__##NAME

#define PYCXX_NOARGS_METHOD_DECL( CLS, NAME ) \
    static PyObject *PYCXX_NOARGS_METHOD_NAME( NAME )( PyObject *_self, PyObject *, PyObject * ) \
    { \
        try \
        { \
            Py::PythonClassInstance *self_python = reinterpret_cast< Py::PythonClassInstance * >( _self ); \
            CLS *self = reinterpret_cast< CLS * >( self_python->m_pycxx_object ); \
            Py::Object r( (self->NAME)() ); \
            return Py::new_reference_to( r.ptr() ); \
        } \
        catch( Py::Exception & ) \
        { \
            return 0; \
        } \
    }
#define PYCXX_VARARGS_METHOD_DECL( CLS, NAME ) \
    static PyObject *PYCXX_VARARGS_METHOD_NAME( NAME )( PyObject *_self, PyObject *_a, PyObject * ) \
    { \
        try \
        { \
            Py::PythonClassInstance *self_python = reinterpret_cast< Py::PythonClassInstance * >( _self ); \
            CLS *self = reinterpret_cast< CLS * >( self_python->m_pycxx_object ); \
            Py::Tuple a( _a ); \
            Py::Object r( (self->NAME)( a ) ); \
            return Py::new_reference_to( r.ptr() ); \
        } \
        catch( Py::Exception & ) \
        { \
            return 0; \
        } \
    }
#define PYCXX_KEYWORDS_METHOD_DECL( CLS, NAME ) \
    static PyObject *PYCXX_KEYWORDS_METHOD_NAME( NAME )( PyObject *_self, PyObject *_a, PyObject *_k ) \
    { \
        try \
        { \
            Py::PythonClassInstance *self_python = reinterpret_cast< Py::PythonClassInstance * >( _self ); \
            CLS *self = reinterpret_cast< CLS * >( self_python->m_pycxx_object ); \
            Py::Tuple a( _a ); \
            Py::Dict k; \
            if( _k != NULL ) \
                k = _k; \
            Py::Object r( (self->NAME)( a, k ) ); \
            return Py::new_reference_to( r.ptr() ); \
        } \
        catch( Py::Exception & ) \
        { \
            return 0; \
        } \
    }

// need to support METH_STATIC and METH_CLASS

#define PYCXX_ADD_NOARGS_METHOD( PYNAME, NAME, docs ) \
    add_method( #PYNAME, (PyCFunction)PYCXX_NOARGS_METHOD_NAME( NAME ), METH_NOARGS, docs )
#define PYCXX_ADD_VARARGS_METHOD( PYNAME, NAME, docs ) \
    add_method( #PYNAME, (PyCFunction)PYCXX_VARARGS_METHOD_NAME( NAME ), METH_VARARGS, docs )
#define PYCXX_ADD_KEYWORDS_METHOD( PYNAME, NAME, docs ) \
    add_method( #PYNAME, (PyCFunction)PYCXX_KEYWORDS_METHOD_NAME( NAME ), METH_VARARGS | METH_KEYWORDS, docs )

namespace Py
{
    extern PythonExtensionBase *getPythonExtensionBase( PyObject *self );
    struct PythonClassInstance
    {
        PyObject_HEAD
        PythonExtensionBase *m_pycxx_object;
    };


    class ExtensionClassMethodsTable
    {
    public:
        ExtensionClassMethodsTable()
        : m_methods_table( new PyMethodDef[ METHOD_TABLE_SIZE_INCREMENT ] )
        , m_methods_used( 0 )
        , m_methods_size( METHOD_TABLE_SIZE_INCREMENT )
        {
        }

        ~ExtensionClassMethodsTable()
        {
            delete m_methods_table;
        }

        // check that all methods added are unique
        void check_unique_method_name( const char *_name )
        {
            std::string name( _name );
            for( int i=0; i<m_methods_used; i++ )
            {
                if( name == m_methods_table[i].ml_name )
                {
                    throw AttributeError( name );
                }
            }
        }
        PyMethodDef *add_method( const char *name, PyCFunction function, int flags, const char *doc )
        {
            check_unique_method_name( name );

            // see if there is enough space for one more method
            if( m_methods_used == (m_methods_size-1) )
            {
                PyMethodDef *old_mt = m_methods_table;
                m_methods_size += METHOD_TABLE_SIZE_INCREMENT;
                PyMethodDef *new_mt = new PyMethodDef[ m_methods_size ];
                for( int i=0; i<m_methods_used; i++ )
                {
                    new_mt[ i ] = old_mt[ i ];
                }
                delete[] old_mt;
                m_methods_table = new_mt;
            }

            // add method into the table
            PyMethodDef *p = &m_methods_table[ m_methods_used ];
            p->ml_name = name;
            p->ml_meth = function;
            p->ml_flags = flags;
            p->ml_doc = doc;

            m_methods_used++;
            p++;

            // add the sentinel marking the table end
            p->ml_name = NULL;
            p->ml_meth = NULL;
            p->ml_flags = 0;
            p->ml_doc = NULL;

            return m_methods_table;
        }

    private:
        enum {METHOD_TABLE_SIZE_INCREMENT = 1};
        PyMethodDef *m_methods_table;
        int m_methods_used;
        int m_methods_size;
    };

    template<TEMPLATE_TYPENAME T> class PythonClass
    : public PythonExtensionBase
    {
    protected:
        explicit PythonClass( PythonClassInstance *self, Tuple &args, Dict &kwds )
        : PythonExtensionBase()
        , m_class_instance( self )
        {
        }

        virtual ~PythonClass()
        {} 

        static ExtensionClassMethodsTable &methodTable()
        {
            static ExtensionClassMethodsTable *method_table;
            if( method_table == NULL )
                method_table = new ExtensionClassMethodsTable;
            return *method_table;
        }

        static void add_method( const char *name, PyCFunction function, int flags, const char *doc=NULL )
        {
            behaviors().set_methods( methodTable().add_method( name, function, flags, doc ) );
        }

        static PythonType &behaviors()
        {
            static PythonType *p;
            if( p == NULL ) 
            {
#if defined( _CPPRTTI ) || defined( __GNUG__ )
                const char *default_name = (typeid( T )).name();
#else
                const char *default_name = "unknown";
#endif
                p = new PythonType( sizeof( T ), 0, default_name );
                p->set_tp_new( extension_object_new );
                p->set_tp_init( extension_object_init );
                p->set_tp_dealloc( extension_object_deallocator );

                // we are a class
                p->supportClass();

                // always support get and set attr
                p->supportGetattro();
                p->supportSetattro();
            }

            return *p;
        }

        static PyObject *extension_object_new( PyTypeObject *subtype, PyObject *args, PyObject *kwds )
        {
#ifdef PYCXX_DEBUG
            std::cout << "extension_object_new()" << std::endl;
#endif
            PythonClassInstance *o = reinterpret_cast<PythonClassInstance *>( subtype->tp_alloc( subtype, 0 ) );
            if( o == NULL )
                return NULL;

            o->m_pycxx_object = NULL;

            PyObject *self = reinterpret_cast<PyObject *>( o );
#ifdef PYCXX_DEBUG
            std::cout << "extension_object_new() => self=0x" << std::hex << reinterpret_cast< unsigned int >( self ) << std::dec << std::endl;
#endif
            return self;
        }

        static int extension_object_init( PyObject *_self, PyObject *args_, PyObject *kwds_ )
        {
            try
            {
                Py::Tuple args( args_ );
                Py::Dict kwds;
                if( kwds_ != NULL )
                    kwds = kwds_;

                PythonClassInstance *self = reinterpret_cast<PythonClassInstance *>( _self );
#ifdef PYCXX_DEBUG
                std::cout << "extension_object_init( self=0x" << std::hex << reinterpret_cast< unsigned int >( self ) << std::dec << " )" << std::endl;
                std::cout << "    self->m_pycxx_object=0x" << std::hex << reinterpret_cast< unsigned int >( self->m_pycxx_object ) << std::dec << std::endl;
#endif

                if( self->m_pycxx_object == NULL )
                {
                    self->m_pycxx_object = new T( self, args, kwds );
#ifdef PYCXX_DEBUG
                    std::cout << "    self->m_pycxx_object=0x" << std::hex << reinterpret_cast< unsigned int >( self->m_pycxx_object ) << std::dec << std::endl;
#endif
                }
                else
                {
#ifdef PYCXX_DEBUG
                    std::cout << "    reinit - self->m_pycxx_object=0x" << std::hex << reinterpret_cast< unsigned int >( self->m_pycxx_object ) << std::dec << std::endl;
#endif
                    self->m_pycxx_object->reinit( args, kwds );
                }
            }
            catch( Exception & )
            {
                return -1;
            }
            return 0;
        }

        static void extension_object_deallocator( PyObject *_self )
        {
            PythonClassInstance *self = reinterpret_cast< PythonClassInstance * >( _self );
#ifdef PYCXX_DEBUG
            std::cout << "extension_object_deallocator( self=0x" << std::hex << reinterpret_cast< unsigned int >( self ) << std::dec << " )" << std::endl;
            std::cout << "    self->m_pycxx_object=0x" << std::hex << reinterpret_cast< unsigned int >( self->m_pycxx_object ) << std::dec << std::endl;
#endif
            delete self->m_pycxx_object;
            _self->ob_type->tp_free( _self );
        }

    public:
        static PyTypeObject *type_object()
        {
            return behaviors().type_object();
        }

        static Object type()
        {
            return Object( reinterpret_cast<PyObject *>( behaviors().type_object() ) );
        }

        static bool check( PyObject *p )
        {
            // is p like me?
            return p->ob_type == type_object();
        }

        static bool check( const Object &ob )
        {
            return check( ob.ptr() );
        }

        virtual PyObject *selfPtr()
        {
            return reinterpret_cast<PyObject *>( m_class_instance );
        }

        virtual Object self()
        {
            return Object( reinterpret_cast<PyObject *>( m_class_instance ) );
        }

    protected:
    private:
        PythonClassInstance *m_class_instance;

    private:
        //
        // prevent the compiler generating these unwanted functions
        //
        explicit PythonClass( const PythonClass<T> &other );
        void operator=( const PythonClass<T> &rhs );
    };

    //
    // ExtensionObject<T> is an Object that will accept only T's.
    //
    template<TEMPLATE_TYPENAME T>
    class PythonClassObject: public Object
    {
    public:

        explicit PythonClassObject( PyObject *pyob )
        : Object( pyob )
        {
            validate();
        }

        PythonClassObject( const PythonClassObject<T> &other )
        : Object( *other )
        {
            validate();
        }

        PythonClassObject( const Object &other )
        : Object( *other )
        {
            validate();
        }

        PythonClassObject &operator=( const Object &rhs )
        {
            *this = *rhs;
            return *this;
        }

        PythonClassObject &operator=( PyObject *rhsp )
        {
            if( ptr() != rhsp )
                set( rhsp );
            return *this;
        }

        virtual bool accepts( PyObject *pyob ) const
        {
            return( pyob && T::check( pyob ) );
        }

        //
        //    Obtain a pointer to the PythonExtension object
        //
        T *getCxxObject( void )
        {
            return dynamic_cast< T * >( getPythonExtensionBase( ptr() ) );
        }
    };
} // Namespace Py

// End of __CXX_ExtensionClass__h
#endif
