#include <eter/__py_tinker_common.h>

namespace py_tinker {

extern "C"
{

extern PyObject*	PyCppClass_NewInstance(PyObject* type, PyObject* args);
extern void		PyCppClass_DeleteInstance(PyObject* type, void* cpp_inst);

extern PyObject* PyCppMethod_BuildBoundCppMethod(PyObject* method, PyObject* self);

extern PyTypeObject PyCppInstance_Type;

struct PyCppInstance_SObject
{
	PyObject_HEAD
	PyObject* in_class;
	PyObject* in_dict;
	PyObject* in_cpp_inst;
};

bool PyCppInstance_Check(PyObject* inst)
{
	return PyObject_TypeCheck(inst, &PyCppInstance_Type);
}

PyObject* PyCppInstance_FromCppInstance(PyObject* cls, PyObject* cpp_inst)
{
	PyCppInstance_SObject* inst = (PyCppInstance_SObject*)PyType_GenericAlloc(&PyCppInstance_Type, 0);

	Py_INCREF(cls);
	inst->in_class		= cls;
	inst->in_dict		= PyDict_New();
	inst->in_cpp_inst	= cpp_inst;

	return (PyObject*)inst;	
}

PyObject* PyCppInstance_New(PyObject* cls, PyObject* args)
{
	PyCppInstance_SObject* inst = (PyCppInstance_SObject*)PyType_GenericAlloc(&PyCppInstance_Type, 0);

	Py_INCREF(cls);
	inst->in_class		= cls;
	inst->in_dict		= PyDict_New();
	inst->in_cpp_inst	= PyCppClass_NewInstance(cls, args);

	if (!inst->in_cpp_inst)
		return NULL;

	return (PyObject*)inst;	
}

void* PyCppInstance_GetCppInstPtr(PyObject* self)
{
	assert(NULL != self);

	PyCppInstance_SObject* inst = (PyCppInstance_SObject*)self;
	if (!PyCObject_Check(inst->in_cpp_inst))
	{
		//assert(!"-_-");
		return NULL;
	}
	
	return PyCObject_AsVoidPtr(inst->in_cpp_inst);
}

static PyObject *
PyCppInstance_Repr(PyCppInstance_SObject* inst)
{
	static PyObject* namestr = PyString_InternFromString("__name__");

	PyObject* cl_name = inst->in_class->ob_type->tp_getattro(inst->in_class, namestr);
	if (!cl_name)
		return NULL;

	char repr[256];
	snprintf(repr, sizeof(repr)-1, "<%s %s at 0x%p>", PyString_AsString(cl_name), inst->ob_type->tp_name, inst);
	return PyString_FromString(repr);
}

static PyObject *
PyCppInstance_Call(PyObject* self, PyObject* args, PyObject* kw)
{
	return NULL;//((PyCppInstance_SObject*)self)->Call(args);
}

static int
PyCppInstance_SetAttr(register PyCppInstance_SObject* inst, PyObject* key, PyObject* value)
{
	PyObject* v = inst->in_class->ob_type->tp_getattro(inst->in_class, key);
	if (!v)
		return -1;

	int ret = 0;
	
	descrsetfunc descr_set = PY_TINKER_TP_DESCR_SET(v->ob_type);
	if (descr_set)
		ret = descr_set(v, (PyObject*)inst, value);	
	
	Py_DECREF(v);
	return 0;
}

static PyObject *
PyCppInstance_GetAttr(register PyCppInstance_SObject* inst, PyObject* key)
{
	const char* skey = PyString_AsString(key);
	if (skey[0] == '_' && skey[1] == '_') 
	{
		if (strcmp(skey, "__dict__") == 0) // dir(inst.__dict__) 
		{
			if (PyEval_GetRestricted()) 
			{
				PyErr_SetString(PyExc_RuntimeError, "py_tinker.cpp_instance not accessible in restricted mode");
				return NULL;
			}
			Py_INCREF(inst->in_dict);
			return inst->in_dict;
		}
		else if (strcmp(skey, "__class__") == 0) // dir(inst) class 멤버가 출력을 위해 참조됨
		{
			Py_INCREF(inst->in_class);
			return inst->in_class;
		}
	}


	PyObject* v = inst->in_class->ob_type->tp_getattro(inst->in_class, key);
	if (!v)
	{	
		PyObject* inst_name = PyObject_Str((PyObject*)inst);
		PyObject* key_name	= PyObject_Str(key);
		if (!PyString_Check(inst_name))		
		{
			Py_DECREF(key_name);
			Py_DECREF(inst_name);
			return NULL;
		}
		if (!PyString_Check(key_name))		
		{
			Py_DECREF(key_name);
			Py_DECREF(inst_name);
			return NULL;
		}

		char msg[128];				
		snprintf(msg, sizeof(msg)-1, "%s instance no attribute '%s'",
				PyString_AsString(inst_name), PyString_AsString(key_name));
		
		PyErr_SetString(PyExc_AttributeError, msg);
		Py_DECREF(key_name);
		Py_DECREF(inst_name);
		return NULL;
	}
	
	descrgetfunc descr_get = PY_TINKER_TP_DESCR_GET(v->ob_type);
	if (descr_get)
	{
		PyObject* r = descr_get(v, (PyObject*)inst, (PyObject*)inst->in_class);
		Py_DECREF(v);
		return r;
	}

	Py_INCREF(v);
	return v;
}

static void
PyCppInstance_Dealloc(PyCppInstance_SObject* self)
{
	PyCppInstance_SObject* inst = (PyCppInstance_SObject*)self;	
	Py_DECREF(inst->in_cpp_inst);
	Py_DECREF(inst->in_class);
	Py_DECREF(inst->in_dict);
	inst->ob_type->tp_free(inst);	
}

PyTypeObject PyCppInstance_Type = {
    PyObject_HEAD_INIT(&PyType_Type)
    0,
    "py_tinker.cpp.instance",
    sizeof(PyCppInstance_Type),
    0,
    (destructor)PyCppInstance_Dealloc,       /* tp_dealloc */
    0,                                  /* tp_print */
    0,                                  /* tp_getattr */
    0,                                  /* tp_setattr */
    0,                                  /* tp_compare */
    (reprfunc)PyCppInstance_Repr,  /* tp_repr */
    0,                                  /* tp_as_number */
    0,                                  /* tp_as_sequence */
    0,                                  /* tp_as_mapping */
    0,                                  /* tp_hash */
    PyCppInstance_Call,                              /* tp_call */
    0,                                  /* tp_str */
    (getattrofunc)PyCppInstance_GetAttr,  /* tp_getattro */
    (setattrofunc)PyCppInstance_SetAttr,  /* tp_setattro */
    0,                                  /* tp_as_buffer */
    Py_TPFLAGS_DEFAULT /* | Py_TPFLAGS_HAVE_GC */,/* tp_flags */
    0,                                  /* tp_doc */
    0, // (traverseproc)func_traverse,          /* tp_traverse */
    0,                                  /* tp_clear */
    0,                                  /* tp_richcompare */
    0, //offsetof(PyFunctionObject, func_weakreflist), /* tp_weaklistoffset */
    0,                                  /* tp_iter */
    0,                                  /* tp_iternext */
    0,                                  /* tp_methods */
    0, // func_memberlist,              /* tp_members */
    0,                /* tp_getset */
    0,                                  /* tp_base */
    0,                                  /* tp_dict */
    0,                 /* tp_descr_get */
    0,                                  /* tp_descr_set */
    0, //offsetof(PyFunctionObject, func_dict),      /* tp_dictoffset */
    0,                                      /* tp_init */
    0,									/* tp_alloc */
    0,							/* tp_new */
    PyObject_Del,									/* tp_free */                                       
};


} // end of extern "C"


} // py_tinker
