/* 
 *
 * ***** BEGIN GPL/BL DUAL LICENSE BLOCK *****
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version. The Blender
 * Foundation also sells licenses for use in proprietary software under
 * the Blender License.  See http://www.blender.org/BL/ for information
 * about this.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software Foundation,
 * Inc., 59 Temple Place - Suite 330, Boston, MA	02111-1307, USA.
 *
 * The Original Code is Copyright (C) 2001-2002 by NaN Holding BV.
 * All rights reserved.
 *
 * This is a new part of Blender.
 *
 * Contributor(s): Joseph Gilbert
 *
 * ***** END GPL/BL DUAL LICENSE BLOCK *****
*/

#include "NLA.h"
#include "Object.h"
#include <BKE_action.h>
#include <BKE_global.h>
#include <BKE_main.h>
#include "Types.h"

/*****************************************************************************/
/* Python API function prototypes for the NLA module.			 */
/*****************************************************************************/
static PyObject *M_NLA_NewAction (PyObject * self, PyObject * args);
static PyObject *M_NLA_CopyAction (PyObject * self, PyObject * args);
static PyObject *M_NLA_GetActions(PyObject* self);

/*****************************************************************************/
/* The following string definitions are used for documentation strings.	  	 */
/* In Python these will be written to the console when doing a		 */
/* Blender.Armature.NLA.__doc__					 */
/*****************************************************************************/
char M_NLA_doc[] = "The Blender NLA module -This module provides control over  Armature keyframing in Blender.";
char M_NLA_NewAction_doc[] = "(name) - Create new action for linking to an object.";
char M_NLA_CopyAction_doc[] = "(name) - Copy action and return copy.";
char M_NLA_GetActions_doc[] = "(name) - Returns a dictionary of actions.";

/*****************************************************************************/
/* Python method structure definition for Blender.Armature.NLA module:			 */
/*****************************************************************************/
struct PyMethodDef M_NLA_methods[] = {
  {"NewAction", (PyCFunction) M_NLA_NewAction, METH_VARARGS,
	  M_NLA_NewAction_doc},
  {"CopyAction", (PyCFunction) M_NLA_CopyAction, METH_VARARGS,
	  M_NLA_CopyAction_doc},
  {"GetActions", (PyCFunction) M_NLA_GetActions, METH_NOARGS,
	  M_NLA_GetActions_doc},
  {NULL, NULL, 0, NULL}
};
/*****************************************************************************/
/* Python BPy_Action methods declarations:																		 */
/*****************************************************************************/
static PyObject *Action_getName (BPy_Action * self);
static PyObject *Action_setName (BPy_Action * self, PyObject * args);
static PyObject *Action_setActive (BPy_Action * self, PyObject * args);
static PyObject *Action_getChannelIpo(BPy_Action * self, PyObject * args);
static PyObject *Action_removeChannel(BPy_Action * self, PyObject * args);
static PyObject *Action_getAllChannelIpos(BPy_Action*self);

/*****************************************************************************/
/* Python BPy_Action methods table:					 */
/*****************************************************************************/
static PyMethodDef BPy_Action_methods[] = {
  /* name, method, flags, doc */
  {"getName", (PyCFunction) Action_getName, METH_NOARGS,
   "() - return Action name"},
  {"setName", (PyCFunction) Action_setName, METH_VARARGS,
   "(str) - rename Action"},
  {"setActive", (PyCFunction) Action_setActive, METH_VARARGS,
   "(str) -set this action as the active action for an object"},
  {"getChannelIpo", (PyCFunction) Action_getChannelIpo, METH_VARARGS,
   "(str) -get the Ipo from a named action channel in this action"},
  {"removeChannel", (PyCFunction) Action_removeChannel, METH_VARARGS,
   "(str) -remove the channel from the action"},
  {"getAllChannelIpos", (PyCFunction)Action_getAllChannelIpos, METH_NOARGS,
	"() - Return a dict of (name:ipo)-keys containing each channel in the object's action"},
  {NULL, NULL, 0, NULL}
};

/*****************************************************************************/
/* Python TypeAction callback function prototypes:				 */
/*****************************************************************************/
static void Action_dealloc (BPy_Action * bone);
static PyObject *Action_getAttr (BPy_Action * bone, char *name);
static int Action_setAttr (BPy_Action * bone, char *name, PyObject * v);
static PyObject *Action_repr (BPy_Action * bone);

/*****************************************************************************/
/* Python TypeAction structure definition:				 */
/*****************************************************************************/
PyTypeObject Action_Type = {
  PyObject_HEAD_INIT (NULL) 0,	  /* ob_size */
  "Blender Action",		                          /* tp_name */
  sizeof (BPy_Action),		                  /* tp_basicsize */
  0,				                                          /* tp_itemsize */
  /* methods */
  (destructor) Action_dealloc,	          /* tp_dealloc */
  0,				                                         /* tp_print */
  (getattrfunc) Action_getAttr,	         /* tp_getattr */
  (setattrfunc) Action_setAttr,	         /* tp_setattr */
  0,	                                                     /* tp_compare */
  (reprfunc) Action_repr,		             /* tp_repr */
  0,				                                         /* tp_as_number */
  0,				                                         /* tp_as_sequence */
  0,				                                         /* tp_as_mapping */
  0,				                                         /* tp_as_hash */
  0, 0, 0, 0, 0, 0,
  0,				                                         /* tp_doc */
  0, 0, 0, 0, 0, 0,
  BPy_Action_methods,		             /* tp_methods */
  0,				                                         /* tp_members */
};

//-------------------------------------------------------------------------------------------------------------------------------
static PyObject *
M_NLA_NewAction (PyObject * self, PyObject * args)
{
  char *name_str = "DefaultAction";
  BPy_Action *py_action = NULL;	/* for Action Data object wrapper in Python */
  bAction *bl_action = NULL;		/* for actual Action Data we create in Blender */

  if (!PyArg_ParseTuple (args, "|s", &name_str)){
    EXPP_ReturnPyObjError (PyExc_AttributeError,
				   "expected string or nothing");
	return NULL;
  }

  //Create new action globally
 bl_action = alloc_libblock(&G.main->action, ID_AC, name_str);
 bl_action->id.flag |= LIB_FAKEUSER;
 bl_action->id.us++;

  // now create the wrapper obj in Python
  if (bl_action)				
    py_action = (BPy_Action *) PyObject_NEW (BPy_Action, &Action_Type);
  else{
    EXPP_ReturnPyObjError (PyExc_RuntimeError,
				   "couldn't create Action Data in Blender");
	return NULL;
  }

  if (py_action == NULL){
    EXPP_ReturnPyObjError (PyExc_MemoryError,
				   "couldn't create Action Data object");
	return NULL;
  }

  py_action->action = bl_action;	// link Python action wrapper with Blender Action

  Py_INCREF(py_action);
  return (PyObject *) py_action;
}

static PyObject *
M_NLA_CopyAction(PyObject* self, PyObject * args)
{
	BPy_Action *py_action = NULL;
	bAction *copyAction = NULL;

	if (!PyArg_ParseTuple (args, "O!", &Action_Type, &py_action)){
		EXPP_ReturnPyObjError (PyExc_AttributeError,
				   "expected python action type");
		return NULL;
  }
	copyAction =  copy_action(py_action->action);
	return Action_CreatePyObject (copyAction);
}

static PyObject *
M_NLA_GetActions(PyObject* self)
{
	PyObject *dict=PyDict_New ();
	bAction *action = NULL;

	for(action = G.main->action.first; action; action = action->id.next){
        PyObject * py_action = Action_CreatePyObject (action);
		if (py_action) {
			// Insert dict entry using the bone name as key
			if (PyDict_SetItemString (dict, action->id.name + 2, py_action) !=0) {
				Py_DECREF (py_action);
				Py_DECREF ( dict );
				
				return EXPP_ReturnPyObjError (PyExc_RuntimeError,
						"NLA_GetActions: couldn't set dict item");
			}
			Py_DECREF (py_action);
		} else {
			Py_DECREF ( dict );
			return (EXPP_ReturnPyObjError (PyExc_RuntimeError,
				"NLA_GetActions: could not create Action object"));
		}
	}	
	return dict;
}

/*****************************************************************************/
/* Function:	NLA_Init						 */
/*****************************************************************************/
PyObject *
NLA_Init (void)
{
  PyObject *submodule;

  Action_Type.ob_type = &PyType_Type;

  submodule = Py_InitModule3 ("Blender.Armature.NLA",
			      M_NLA_methods, M_NLA_doc);

  return (submodule);
}

//-------------------------------------------------------------------------------------------------------------------------------
static PyObject *
Action_getName (BPy_Action * self)
{
  PyObject *attr = NULL;

  if (!self->action)
    (EXPP_ReturnPyObjError (PyExc_RuntimeError,
			    "couldn't get attribute from a NULL action"));

  attr = PyString_FromString (self->action->id.name+2);

  if (attr)
    return attr;

  return (EXPP_ReturnPyObjError (PyExc_RuntimeError,
				 "couldn't get Action.name attribute"));
}
//-------------------------------------------------------------------------------------------------------------------------------
static PyObject *
Action_setName (BPy_Action * self, PyObject * args)
{
  char *name;

  if (!self->action)
    (EXPP_ReturnPyObjError (PyExc_RuntimeError,
			    "couldn't get attribute from a NULL action"));

  if (!PyArg_ParseTuple (args, "s", &name))
    return (EXPP_ReturnPyObjError (PyExc_AttributeError,
				   "expected string argument"));

  //change name
  strcpy(self->action->id.name+2, name);

  Py_INCREF (Py_None);
  return Py_None;
}

static PyObject *
Action_setActive(BPy_Action * self, PyObject * args)
{
  BPy_Object *object;

  if (!self->action)
    (EXPP_ReturnPyObjError (PyExc_RuntimeError,
			    "couldn't get attribute from a NULL action"));

  if (!PyArg_ParseTuple (args, "O!", &Object_Type, &object))
    return (EXPP_ReturnPyObjError (PyExc_AttributeError,
				   "expected python object argument"));

  if(object->object->type != OB_ARMATURE) {
    return (EXPP_ReturnPyObjError (PyExc_AttributeError,
				   "object not of type armature"));
  }

  //set the active action to object
  object->object->action = self->action;

  Py_INCREF (Py_None);
  return Py_None;
}

static PyObject *
Action_getChannelIpo(BPy_Action * self, PyObject * args)
{
	char *chanName;
	bActionChannel *chan;

	if(!PyArg_ParseTuple(args, "s", &chanName)){
		EXPP_ReturnPyObjError(PyExc_AttributeError, "string expected");
		return NULL;
	}

	chan = get_named_actionchannel(self->action,chanName);
	if(chan == NULL){
		EXPP_ReturnPyObjError(PyExc_AttributeError, "no channel with that name...");
		return NULL;
	}

	//return IPO
    return Ipo_CreatePyObject (chan->ipo);
}

static PyObject *
Action_removeChannel(BPy_Action * self, PyObject * args)
{
	char *chanName;
	bActionChannel *chan;

	if(!PyArg_ParseTuple(args, "s", &chanName)){
		EXPP_ReturnPyObjError(PyExc_AttributeError, "string expected");
		return NULL;
	}

	chan = get_named_actionchannel(self->action,chanName);
	if(chan == NULL){
		EXPP_ReturnPyObjError(PyExc_AttributeError, "no channel with that name...");
		return NULL;
	}

	//release ipo
	if(chan->ipo)
		chan->ipo->id.us--;

	//remove channel
	BLI_freelinkN (&self->action->chanbase, chan);

	Py_INCREF (Py_None);
	return (Py_None);
}

static PyObject *Action_getAllChannelIpos (BPy_Action *self)
{
	PyObject *dict=PyDict_New ();
	bActionChannel *chan = NULL;

	for(chan = self->action->chanbase.first; chan; chan = chan->next){
        PyObject * ipo_attr = Ipo_CreatePyObject (chan->ipo);
		if (ipo_attr) {
			// Insert dict entry using the bone name as key
			if (PyDict_SetItemString (dict, chan->name, ipo_attr) !=0) {
				Py_DECREF ( ipo_attr );
				Py_DECREF ( dict );
				
				return EXPP_ReturnPyObjError (PyExc_RuntimeError,
						"Action_getAllChannelIpos: couldn't set dict item");
			}
			Py_DECREF (ipo_attr);
		} else {
			Py_DECREF ( dict );
			return (EXPP_ReturnPyObjError (PyExc_RuntimeError,
				"Action_getAllChannelIpos: could not create Ipo object"));
		}
	}	
	return dict;
}

//-------------------------------------------------------------------------------------------------------------------------------
static void
Action_dealloc (BPy_Action * self)
{
    PyObject_DEL (self);
}
//-------------------------------------------------------------------------------------------------------------------------------
static PyObject *
Action_getAttr (BPy_Action * self, char *name)
{
  PyObject *attr = Py_None;

  if (strcmp (name, "name") == 0)
    attr = Action_getName (self);
  else if (strcmp (name, "__members__") == 0)  {
      attr = Py_BuildValue ("[s]",
			    "name");
    }

  if (!attr)
    return (EXPP_ReturnPyObjError (PyExc_MemoryError,
				   "couldn't create PyObject"));

  if (attr != Py_None)
    return attr;		/* member attribute found, return it */

  /* not an attribute, search the methods table */
  return Py_FindMethod (BPy_Action_methods, (PyObject *) self, name);
}

//-------------------------------------------------------------------------------------------------------------------------------
static int
Action_setAttr (BPy_Action * self, char *name, PyObject * value)
{
  PyObject *valtuple;
  PyObject *error = NULL;

  valtuple = Py_BuildValue ("(O)", value);	/* the set* functions expect a tuple */

  if (!valtuple)
    return EXPP_ReturnIntError (PyExc_MemoryError,
				"ActionSetAttr: couldn't create tuple");

  if (strcmp (name, "name") == 0)
    error = Action_setName (self, valtuple);
  else
    {				/* Error */
      Py_DECREF (valtuple);

      /* ... member with the given name was found */
      return (EXPP_ReturnIntError (PyExc_KeyError, "attribute not found"));
    }

  Py_DECREF (valtuple);

  if (error != Py_None)
    return -1;

  Py_DECREF (Py_None);		/* was incref'ed by the called Action_set* function */
  return 0;			/* normal exit */
}
//-------------------------------------------------------------------------------------------------------------------------------
static PyObject *
Action_repr (BPy_Action * self)
{
  if (self->action)
    return PyString_FromFormat ("[Action \"%s\"]", self->action->id.name + 2);
  else
    return PyString_FromString ("NULL");
}
//-------------------------------------------------------------------------------------------------------------------------------
PyObject *
Action_CreatePyObject (struct bAction * act)
{
  BPy_Action *blen_action;

  blen_action = (BPy_Action *) PyObject_NEW (BPy_Action, &Action_Type);

  if (blen_action == NULL)
    {
      return (NULL);
    }
  blen_action->action	= act;
  return ((PyObject *) blen_action);
}
//-------------------------------------------------------------------------------------------------------------------------------
int
Action_CheckPyObject (PyObject * py_obj)
{
  return (py_obj->ob_type == &Action_Type);
}
//-------------------------------------------------------------------------------------------------------------------------------
struct bAction *
Action_FromPyObject (PyObject * py_obj)
{
  BPy_Action *blen_obj;

  blen_obj = (BPy_Action *) py_obj;
  return (blen_obj->action);
}
