// Bindings
#include "CPyCppyy.h"
#include "PyStrings.h"
#include "PropertyProxy.h"
#include "ObjectProxy.h"
#include "Utility.h"


namespace CPyCppyy {

   enum ETypeDetails {
      kNone           =    0,
      kIsStaticData   =    1,
      kIsEnumData     =    2,
      kIsConstData    =    4,
      kIsArrayType    =    8
   };

namespace {

//= CPyCppyy property proxy property behaviour ===============================
   PyObject* pp_get( PropertyProxy* pyprop, ObjectProxy* pyobj, PyObject* )
   {
   // normal getter access
      void* address = pyprop->GetAddress( pyobj );
      if ( ! address || (ptrdiff_t)address == -1 /* Cling error */ )
         return 0;

   // for fixed size arrays
      void* ptr = address;
      if ( pyprop->fProperty & kIsArrayType )
         ptr = &address;

   // not-initialized or public data accesses through class (e.g. by help())
      if ( ! ptr || (ptrdiff_t)ptr == -1 /* Cling error */ ) {
         Py_INCREF( pyprop );
         return (PyObject*)pyprop;
      }

      if ( pyprop->fConverter != 0 ) {
         PyObject* result = pyprop->fConverter->FromMemory( ptr );
         if ( ! result )
            return result;

         // ensure that the encapsulating class does not go away for the duration
         // of the data member's lifetime, if it is a bound type (it doesn't matter
         // for builtin types, b/c those are copied over into python types and thus
         // end up being "stand-alone")
         if ( pyobj && ObjectProxy_Check( result ) ) {
            if ( PyObject_SetAttr( result, PyStrings::gLifeLine, (PyObject*)pyobj ) == -1 )
               PyErr_Clear();     // ignored
         }
         return result;
      }

      PyErr_Format( PyExc_NotImplementedError,
         "no converter available for \"%s\"", pyprop->GetName().c_str() );
      return 0;
   }

////////////////////////////////////////////////////////////////////////////////
/// Set the value of the C++ datum held.

   int pp_set( PropertyProxy* pyprop, ObjectProxy* pyobj, PyObject* value )
   {
      const int errret = -1;

   // filter const objects to prevent changing their values
      if ( ( pyprop->fProperty & kIsConstData ) ) {
         PyErr_SetString( PyExc_TypeError, "assignment to const data not allowed" );
         return errret;
      }

      ptrdiff_t address = (ptrdiff_t)pyprop->GetAddress( pyobj );
      if ( ! address || address == -1 /* Cling error */ )
         return errret;

   // for fixed size arrays
      void* ptr = (void*)address;
      if ( pyprop->fProperty & kIsArrayType )
         ptr = &address;

   // actual conversion; return on success
      if ( pyprop->fConverter && pyprop->fConverter->ToMemory( value, ptr ) )
         return 0;

   // set a python error, if not already done
      if ( ! PyErr_Occurred() )
         PyErr_SetString( PyExc_RuntimeError, "property type mismatch or assignment not allowed" );

   // failure ...
      return errret;
   }

//= CPyCppyy property proxy construction/destruction =========================
   PropertyProxy* pp_new( PyTypeObject* pytype, PyObject*, PyObject* )
   {
   // Create and initialize a new property descriptor.
      PropertyProxy* pyprop = (PropertyProxy*)pytype->tp_alloc( pytype, 0 );

      pyprop->fOffset         = 0;
      pyprop->fProperty       = 0;
      pyprop->fConverter      = 0;
      pyprop->fEnclosingScope = 0;
      new ( &pyprop->fName ) std::string();

      return pyprop;
   }

////////////////////////////////////////////////////////////////////////////////
/// Deallocate memory held by this descriptor.

   void pp_dealloc( PropertyProxy* pyprop )
   {
      using namespace std;
      delete pyprop->fConverter;
      pyprop->fName.~string();

      Py_TYPE(pyprop)->tp_free( (PyObject*)pyprop );
   }


} // unnamed namespace


//= CPyCppyy property proxy type =============================================
PyTypeObject PropertyProxy_Type = {
   PyVarObject_HEAD_INIT( &PyType_Type, 0 )
   (char*)"cppyy.PropertyProxy",                  // tp_name
   sizeof(PropertyProxy),     // tp_basicsize
   0,                         // tp_itemsize
   (destructor)pp_dealloc,    // tp_dealloc
   0,                         // tp_print
   0,                         // tp_getattr
   0,                         // tp_setattr
   0,                         // tp_compare
   0,                         // tp_repr
   0,                         // tp_as_number
   0,                         // tp_as_sequence
   0,                         // tp_as_mapping
   0,                         // tp_hash
   0,                         // tp_call
   0,                         // tp_str
   0,                         // tp_getattro
   0,                         // tp_setattro
   0,                         // tp_as_buffer
   Py_TPFLAGS_DEFAULT,        // tp_flags
   (char*)"cppyy property proxy (internal)",      // tp_doc
   0,                         // tp_traverse
   0,                         // tp_clear
   0,                         // tp_richcompare
   0,                         // tp_weaklistoffset
   0,                         // tp_iter
   0,                         // tp_iternext
   0,                         // tp_methods
   0,                         // tp_members
   0,                         // tp_getset
   0,                         // tp_base
   0,                         // tp_dict
   (descrgetfunc)pp_get,      // tp_descr_get
   (descrsetfunc)pp_set,      // tp_descr_set
   0,                         // tp_dictoffset
   0,                         // tp_init
   0,                         // tp_alloc
   (newfunc)pp_new,           // tp_new
   0,                         // tp_free
   0,                         // tp_is_gc
   0,                         // tp_bases
   0,                         // tp_mro
   0,                         // tp_cache
   0,                         // tp_subclasses
   0                          // tp_weaklist
#if PY_VERSION_HEX >= 0x02030000
   , 0                        // tp_del
#endif
#if PY_VERSION_HEX >= 0x02060000
   , 0                        // tp_version_tag
#endif
#if PY_VERSION_HEX >= 0x03040000
   , 0                        // tp_finalize
#endif
};

} // namespace CPyCppyy


//- public members -----------------------------------------------------------
void CPyCppyy::PropertyProxy::Set( Cppyy::TCppScope_t scope, Cppyy::TCppIndex_t idata )
{
   fEnclosingScope = scope;
   fName           = Cppyy::GetDatamemberName( scope, idata );
   fOffset         = Cppyy::GetDatamemberOffset( scope, idata );
   fProperty       = Cppyy::IsStaticData( scope, idata ) ? kIsStaticData : 0;

   Int_t size = Cppyy::GetDimensionSize( scope, idata, 0 );
   if ( 0 < size )
      fProperty |= kIsArrayType;

   std::string fullType = Cppyy::GetDatamemberType( scope, idata );
   if ( Cppyy::IsEnumData( scope, idata ) ) {
      fullType = "UInt_t";
      fProperty |= kIsEnumData;
   }

   if ( Cppyy::IsConstData( scope, idata ) )
      fProperty |= kIsConstData;

   fConverter = CreateConverter( fullType, size );
}

////////////////////////////////////////////////////////////////////////////////

void CPyCppyy::PropertyProxy::Set( Cppyy::TCppScope_t scope, const std::string& name, void* address )
{
   fEnclosingScope = scope;
   fName           = name;
   fOffset         = (ptrdiff_t)address;
   fProperty       = (kIsStaticData | kIsConstData | kIsEnumData /* true, but may chance */ );
   fConverter      = CreateConverter( "UInt_t", -1 );
}

////////////////////////////////////////////////////////////////////////////////
/// class attributes, global properties

void* CPyCppyy::PropertyProxy::GetAddress( ObjectProxy* pyobj ) {
   if ( fProperty & kIsStaticData )
      return (void*)fOffset;

// special case: non-static lookup through class
   if ( ! pyobj ) {
      PyErr_SetString( PyExc_ReferenceError, "attribute access requires an instance" );
      return 0;
   }

// instance attributes; requires valid object for full address
   if ( ! ObjectProxy_Check( pyobj ) ) {
      PyErr_Format( PyExc_TypeError,
         "object instance required for access to property \"%s\"", GetName().c_str() );
      return 0;
   }

   void* obj = pyobj->GetObject();
   if ( ! obj ) {
      PyErr_SetString( PyExc_ReferenceError, "attempt to access a null-pointer" );
      return 0;
   }

// the proxy's internal offset is calculated from the enclosing class
   ptrdiff_t offset = 0;
   if ( pyobj->ObjectIsA() != fEnclosingScope)
      offset = Cppyy::GetBaseOffset( pyobj->ObjectIsA(), fEnclosingScope, obj, 1 /* up-cast */ );

   return (void*)((ptrdiff_t)obj + offset + fOffset);
}
