/* -*- mode: C -*-  */
/* 
   IGraph library.
   Copyright (C) 2006  Gabor Csardi <csardi@rmki.kfki.hu>
   MTA RMKI, Konkoly-Thege Miklos st. 29-33, Budapest 1121, Hungary
   
   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.
   
   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.,  51 Franklin Street, Fifth Floor, Boston, MA 
   02110-1301 USA

*/

/************************ Miscellaneous functions *************************/

#include "graphobject.h"
#include "convert.h"
#include "error.h"
#include "memory.h"

/**
 * \ingroup python_interface_conversion
 * \brief Converts a Python integer list to an igraph \c igraph_vector_t
 * The incoming \c igraph_vector_t should be uninitialized. Raises suitable
 * Python exceptions when needed.
 * 
 * \param list the Python list to be converted
 * \param v the \c igraph_vector_t containing the result
 * \param need_non_negative if true, checks whether all elements are non-negative
 * \param pairs if true, assumes that every list element is a pair of integers
 * \return 0 if everything was OK, 1 otherwise
 */
int igraphmodule_PyList_to_vector_t(PyObject *list, igraph_vector_t *v, igraph_bool_t need_non_negative, igraph_bool_t pairs)
{
   PyObject *item, *i1, *i2;
   int i, j, k, ok;
   long idx=0, idx2=0;

   if (!PyList_Check(list)) 
     {
	ok=1;
	
	if (pairs)
	  {
	     // a pair was given instead of a list
	     // Let's assume that the user meant a list consisting of this single pair
	     if (PyTuple_Check(list) && PyTuple_Size(list)==2)
	       {
		  i1=i2=NULL;
		  i1=PyTuple_GetItem(list, 0);
		  if (i1) i2=PyTuple_GetItem(list, 1);
		  if (i1 && i2) 
		    {
		       if (PyInt_Check(i1) && PyInt_Check(i2)) 
			 {
			    idx=PyInt_AsLong(i1); idx2=PyInt_AsLong(i2);
			    // domain checking
			    if (need_non_negative && (idx<0 || idx2<0)) ok=0;
			 }
		       else ok=0;
		    }
		  else
		    {
		       // should not ever get here
		       // Exception was set by PyTuple_GetItem, so return immediately
		       return 1;
		    }
		  
	       }
	     else 
	       {
		  PyErr_SetString(PyExc_TypeError, "List of pairs or a single pair of integer expected");
		  return 1;
	       }
	     
	     if (ok) 
	       {
		  igraph_vector_init(v, 2);
		  VECTOR(*v)[0]=(igraph_real_t)idx;
		  VECTOR(*v)[1]=(igraph_real_t)idx2;
	       }
	     else
	       {
		  if (need_non_negative)
		    PyErr_SetString(PyExc_TypeError, "List elements must be non-negative integers");
		  else
		    PyErr_SetString(PyExc_TypeError, "List elements must be integers");
		  return 1;
	       }
	  }
	else if (PyInt_Check(list)) 
	  {
	     // a single integer was given instead of a list
	     // Let's assume that the user meant a list consisting of this single item
	     igraph_vector_init(v, 1);
	     VECTOR(*v)[0]=(igraph_real_t)PyInt_AsLong(list);
	  }
	else 
	  {
	     PyErr_SetString(PyExc_TypeError, "List or single integer expected");
	     return 1;
	  }
     }
   else 
     {
	// we received a list, so loop through all elements and add them
	// to a vector.
	// Non-integer or negative elements raise an exception
	j=PyList_Size(list);
	if (pairs)
	  igraph_vector_init(v, 2*j);
	else
	  igraph_vector_init(v, j);
	for (i=0, k=0; i<j; i++)
	  {
	     item=PyList_GetItem(list, i);
	     if (item) 
	       {
		  ok=1;
		  
		  if (pairs) 
		    {
		       // item is a borrowed reference, so no need to hassle with reference counting
		       if (PyTuple_Check(item) && PyTuple_Size(item)==2)
			 {
			    i1=NULL; i2=NULL;
			    i1=PyTuple_GetItem(item, 0);
			    if (i1) i2=PyTuple_GetItem(item, 1);
			    if (i1 && i2) 
			      {
				 if (PyInt_Check(i1) && PyInt_Check(i2)) 
				   {
				      // domain checking
				      idx=PyInt_AsLong(i1);
				      idx2=PyInt_AsLong(i2);
				      if (need_non_negative && (idx<0 || idx2<0)) ok=0;
				   }
				 else ok=0;
			      }
			    else 
			      {
				 // this should not happen, but we return anyway.
				 // an IndexError exception was set by PyTuple_GetItem
				 // at this point
				 igraph_vector_destroy(v);
				 return 1;
			      }
			    
			 } else ok=0;
		    }
		  else 
		    {
		       // item is a borrowed reference, so no need to hassle with reference counting
		       if (PyInt_Check(item)) 
			 {
			    // domain checking
			    idx=PyInt_AsLong(item);
			    if (need_non_negative && idx<0) ok=0;
			 }
		       else ok=0;
		    }
	     
		  if (!ok)
		    {
		      if (pairs) {
			// item is not a non-negative integer pair, so throw an exception
			if (need_non_negative)
			  PyErr_SetString(PyExc_TypeError, "List elements must be non-negative integer pairs");
			else
			  PyErr_SetString(PyExc_TypeError, "List elements must be integer pairs");
			igraph_vector_destroy(v);
			return 1;
		      } else {
			// item is not a non-negative integer, so throw an exception
			if (need_non_negative)
			  PyErr_SetString(PyExc_TypeError, "List elements must be non-negative integers");
			else
			  PyErr_SetString(PyExc_TypeError, "List elements must be integers");
			igraph_vector_destroy(v);
			return 1;
		      }
		    }
		  
		 // add idx into index vector
		  VECTOR(*v)[k]=(igraph_real_t)idx;
		  k++;
		  if (pairs) 
		    {
		       // if we are working on pairs, add idx and idx2 as well
		       VECTOR(*v)[k]=(igraph_real_t)idx2;
		       k++;
		    }
	       }
	     else 
	       {
		  // this should not happen, but we return anyway.
		  // an IndexError exception was set by PyList_GetItem
		  // at this point
		  igraph_vector_destroy(v);
		  return 1;
	       }
	  }
     }
   
   return 0;
}

/**
 * \ingroup python_interface_conversion
 * \brief Converts an igraph \c igraph_vector_t to a Python integer list
 * 
 * \param v the \c igraph_vector_t containing the vector to be converted
 * \return the Python integer list as a \c PyObject*, or \c NULL if an error occurred
 */
PyObject* igraphmodule_vector_t_to_PyList(igraph_vector_t *v,
    igraphmodule_conv_t type) {
  PyObject *list, *item;
  int n, i;
   
  n=igraph_vector_size(v);
  if (n<0) return igraphmodule_handle_igraph_error();

  list=PyList_New(n);
  for (i=0; i<n; i++) {
    if (type == IGRAPHMODULE_TYPE_INT) {
      item=PyInt_FromLong((long)VECTOR(*v)[i]);
    } else if (type == IGRAPHMODULE_TYPE_FLOAT) {
      item=PyFloat_FromDouble((double)VECTOR(*v)[i]);
    } else {
      item=Py_None;
      Py_INCREF(item);
    }
    if (!item) {
      Py_DECREF(list);
      return NULL;
    }
    PyList_SET_ITEM(list, i, item);
  }

  return list;
}

/**
 * \ingroup python_interface_conversion
 * \brief Converts an igraph \c igraph_vector_t to a Python integer tuple
 * 
 * \param v the \c igraph_vector_t containing the vector to be converted
 * \return the Python integer tuple as a \c PyObject*, or \c NULL if an error occurred
 */
PyObject* igraphmodule_vector_t_to_PyTuple(igraph_vector_t *v) {
  PyObject* tuple;
  int n, i;
  
  n=igraph_vector_size(v);
  if (n<0) return igraphmodule_handle_igraph_error();
  
  tuple=PyTuple_New(n);
  for (i=0; i<n; i++) {
    PyObject *item=PyInt_FromLong((long)VECTOR(*v)[i]);
    if (!item) {
      Py_DECREF(tuple);
      return NULL;
    }
    PyTuple_SET_ITEM(tuple, i, item);
  }

  return tuple;
}

/**
 * \ingroup python_interface_conversion
 * \brief Converts an igraph \c igraph_vector_t to a Python list of integer pairs
 * 
 * \param v the \c igraph_vector_t containing the vector to be converted
 * \return the Python integer pair list as a \c PyObject*, or \c NULL if an error occurred
 */
PyObject* igraphmodule_vector_t_to_PyList_pairs(igraph_vector_t *v) {
   PyObject *list, *pair;
   long n, i, j;
   
   n=igraph_vector_size(v);
   if (n<0) return igraphmodule_handle_igraph_error();
   if (n%2) return igraphmodule_handle_igraph_error();
   
   /* create a new Python list */
   n>>=1;
   list=PyList_New(n);
   
   /* populate the list with data */
   for (i=0, j=0; i<n; i++, j+=2) {
     pair=Py_BuildValue("(ll)", (long)VECTOR(*v)[j], (long)VECTOR(*v)[j+1]);
     if (pair==NULL || PyList_SetItem(list, i, pair)) {
       /* error occurred while populating the list, return immediately */
       Py_DECREF(pair);
       Py_DECREF(list);
       return NULL;
     }
   }

   return list;
}

/**
 * \ingroup python_interface_conversion
 * \brief Converts two igraph \c igraph_vector_t vectors to a Python list of integer pairs
 * 
 * \param v1 the \c igraph_vector_t containing the 1st vector to be converted
 * \param v2 the \c igraph_vector_t containing the 2nd vector to be converted
 * \return the Python integer pair list as a \c PyObject*, or \c NULL if an error occurred
 */
PyObject* igraphmodule_vector_t_pair_to_PyList(igraph_vector_t *v1,
					       igraph_vector_t *v2) {
   PyObject *list, *pair;
   long n, i;
   
   n=igraph_vector_size(v1);
   if (n<0) return igraphmodule_handle_igraph_error();
   if (igraph_vector_size(v2) != n) return igraphmodule_handle_igraph_error();

   /* create a new Python list */
   list=PyList_New(n);
   
   /* populate the list with data */
   for (i=0; i<n; i++) {
     pair=Py_BuildValue("(ll)", (long)VECTOR(*v1)[i], (long)VECTOR(*v2)[i]);
     if (pair==NULL || PyList_SetItem(list, i, pair)) {
       /* error occurred while populating the list, return immediately */
       Py_DECREF(pair);
       Py_DECREF(list);
       return NULL;
     }
   }

   return list;
}

/**
 * \ingroup python_interface_conversion
 * \brief Converts an igraph \c igraph_matrix_t to a Python list of lists
 * 
 * \param m the \c igraph_matrix_t containing the matrix to be converted
 * \param type the type of conversion. If equals to IGRAPHMODULE_TYPE_INT,
 *        returns an integer matrix, else returns a float matrix.
 * \return the Python list of lists as a \c PyObject*, or \c NULL if an error occurred
 */
PyObject* igraphmodule_matrix_t_to_PyList(igraph_matrix_t *m,
						 igraphmodule_conv_t type) {
   PyObject *list, *row, *item;
   int nr, nc, i, j;
   
   
   nr=igraph_matrix_nrow(m); nc=igraph_matrix_ncol(m);
   if (nr<0 || nc<0) return igraphmodule_handle_igraph_error();

   // create a new Python list
   list=PyList_New(nr);
   // populate the list with data
   for (i=0; i<nr; i++) 
     {
	row=PyList_New(nc);
	for (j=0; j<nc; j++) 
	  {
	     if (type==IGRAPHMODULE_TYPE_INT)
	       item=PyInt_FromLong(MATRIX(*m,i,j));
	     else
	       item=PyFloat_FromDouble(MATRIX(*m,i,j));
	       
	     if (PyList_SetItem(row, j, item))
	       {
		  // error occurred while populating the list, return immediately
		  Py_DECREF(row);
		  Py_DECREF(list);
		  return NULL;
	       }
	  }
	if (PyList_SetItem(list, i, row)) 
	  {
	     Py_DECREF(row);
	     Py_DECREF(list);
	     return NULL;
	  }
     }
   // return the list
   return list;
}

/**
 * \ingroup python_interface_conversion
 * \brief Converts a Python list of lists to an \c igraph_matrix_t
 * 
 * \param o the Python object representing the list of lists
 * \param m the address of an uninitialized \c igraph_matrix_t
 * \return 0 if everything was OK, 1 otherwise
 */
int igraphmodule_PyList_to_matrix_t(PyObject* o, igraph_matrix_t *m) {
  int nr, nc, n, i, j;
  PyObject *row, *item;
  
  /* calculate the matrix dimensions */
  if (!PyList_Check(o)) return 1;
  nr = PyList_Size(o);
  nc = 0;
  for (i=0; i<nr; i++) {
    row=PyList_GetItem(o, i);
    if (!PyList_Check(row)) return 1;
    n=PyList_Size(row);
    if (n>nc) nc=n;
  }
  
  igraph_matrix_init(m, nr, nc);
  for (i=0; i<nr; i++) {
    row=PyList_GetItem(o, i);
    n=PyList_Size(row);
    for (j=0; j<n; j++) {
      item=PyList_GetItem(row, j);
      if (PyInt_Check(item)) {
	MATRIX(*m, i, j) = (igraph_real_t)PyInt_AsLong(item);
      } else if (PyLong_Check(item)) {
	MATRIX(*m, i, j) = (igraph_real_t)PyLong_AsLong(item);
      } else if (PyFloat_Check(item)) {
	MATRIX(*m, i, j) = (igraph_real_t)PyFloat_AsDouble(item);
      } else {
	/* warning? */
      }
    }
  }

  return 0;
}

/**
 * \ingroup python_interface_conversion
 * \brief Converts an \c igraph_strvector_t to a Python string list
 * 
 * \param v the \c igraph_strvector_t containing the vector to be converted
 * \return the Python string list as a \c PyObject*, or \c NULL if an error occurred
 */
PyObject* igraphmodule_strvector_t_to_PyList(igraph_strvector_t *v) {
  PyObject* list;
  int n, i;
  char* ptr;
  
  n=igraph_strvector_size(v);
  if (n<0) return igraphmodule_handle_igraph_error();
  
  // create a new Python list
  list=PyList_New(n);
  /* populate the list with data */
  for (i=0; i<n; i++) {
    igraph_strvector_get(v, i, &ptr);
    if (PyList_SetItem(list, i, PyString_FromString(ptr))) {
      /* error occurred while populating the list, return immediately */
      Py_DECREF(list);
      return NULL;
    }
  }
  
   /* return the list */
   return list;
}

/**
 * \ingroup python_interface_conversion
 * \brief Converts a Python string list to an \c igraph_strvector_t
 * 
 * \param v the Python list as a \c PyObject*
 * \param result an \c igraph_strvector_t containing the result
 * The incoming \c igraph_strvector_t should be uninitialized. Raises suitable
 * Python exceptions when needed.
 * \return 0 if everything was OK, 1 otherwise
 */
int igraphmodule_PyList_to_strvector_t(PyObject* v, igraph_strvector_t *result) {
  int n, i;
  static char* empty = "";
  
  if (!PyList_Check(v)) {
    PyErr_SetString(PyExc_TypeError, "expected list");
    return 1;
  }
  
  n=PyList_Size(v);

  /* initialize the string vector */
  if (igraph_strvector_init(result, n)) return 1;
  
  /* populate the vector with data */
  for (i=0; i<n; i++) {
    PyObject *item = PyList_GetItem(v, i);
    char* ptr;
    if (PyString_Check(item))
      ptr=PyString_AS_STRING(item);
    else
      ptr=empty;
    if (igraph_strvector_set(result, i, ptr)) {
      igraph_strvector_destroy(result);
      return 1;
    }
  }

  return 0;
}

/**
 * \ingroup python_interface_conversion
 * \brief Appends the contents of a Python iterator returning graphs to
 * an \c igraph_vectorptr_t
 *
 * The incoming \c igraph_vector_ptr_t should be INITIALIZED.
 * Raises suitable Python exceptions when needed.
 * 
 * \param it the Python iterator
 * \param v the \c igraph_vector_ptr_t which will contain the result
 * \return 0 if everything was OK, 1 otherwise
 */
int igraphmodule_append_PyIter_to_vector_ptr_t(PyObject *it, igraph_vector_ptr_t *v) {
  PyObject *t;
  
  while ((t=PyIter_Next(it))) {
    if (!PyObject_TypeCheck(t, &igraphmodule_GraphType)) {
      PyErr_SetString(PyExc_TypeError, "iterable argument must contain graphs");
      Py_DECREF(t);
      return 1;
    }
    igraph_vector_ptr_push_back(v, &((igraphmodule_GraphObject*)t)->g);
    Py_DECREF(t);
  }  
  
  return 0;
}

/**
 * \ingroup python_interface_conversion
 * \brief Tries to interpret a Python object as a vertex selector
 * 
 * \param o the Python object
 * \param vs the \c igraph_vs_t which will contain the result
 * \param return_single will be 1 if the selector selected only a single vertex,
 * 0 otherwise
 * \return 0 if everything was OK, 1 otherwise
 */
int igraphmodule_PyObject_to_vs_t(PyObject *o, igraph_vs_t *vs,
				  igraph_bool_t *return_single) {
  *return_single=0;
  if (o==NULL || o == Py_None) {
    /* Returns a vertex sequence for all vertices */
    igraph_vs_all(vs);
  } else if (PyInt_Check(o)) {
    /* Returns a vertex sequence for a single vertex ID */
    igraph_vs_1(vs, PyInt_AsLong(o));
    *return_single=1;
  } else if (PyLong_Check(o)) {
    /* Returns a vertex sequence for a single vertex ID */
    igraph_vs_1(vs, PyLong_AsLong(o));
    *return_single=1;
  } else {
    /* Returns a vertex sequence with the IDs returned by the iterator */
    PyObject *iterator = PyObject_GetIter(o);
    PyObject *item;
    igraph_vector_t vector;

    if (iterator == NULL) {
      PyErr_SetString(PyExc_TypeError, "integer, long, iterable or None expected");
      return 1;
    }

    IGRAPH_CHECK(igraph_vector_init(&vector, 0));
    IGRAPH_FINALLY(igraph_vector_destroy, &vector);
    IGRAPH_CHECK(igraph_vector_reserve(&vector, 20));

    while ((item = PyIter_Next(iterator))) {
      long val=-1;
      if (PyInt_Check(item)) val=PyInt_AsLong(item);
      else if (PyLong_Check(item)) val=PyLong_AsLong(item);
      Py_DECREF(item);

      if (val >= 0) igraph_vector_push_back(&vector, val);
      else {
	PyErr_SetString(PyExc_TypeError, "integer or long expected");
      }

      if (PyErr_Occurred()) break;
    }
    Py_DECREF(iterator);

    if (PyErr_Occurred()) {
      igraph_vector_destroy(&vector);
      IGRAPH_FINALLY_CLEAN(1);
      return 1;
    } else {
      igraph_vs_vector_copy(vs, &vector);
      igraph_vector_destroy(&vector);
      IGRAPH_FINALLY_CLEAN(1);
    }
  }
  return 0;
}


/**
 * \ingroup python_interface_conversion
 * \brief Tries to interpret a Python object as a numeric attribute value list
 * 
 * \param o the Python object
 * \param v the \c igraph_vector_t which will contain the result
 * \param g a \c igraphmodule_GraphObject object or \c NULL - used when the
 * provided Python object is not a list and we're trying to interpret it as
 * an attribute name.
 * \param type the attribute type (graph = 0, vertex = 1, edge = 2) to be used
 * \param def default value if the attribute name supplied is \c None
 * if \c o is not a list.
 * \return 0 if everything was OK, 1 otherwise
 *
 * If the Python object is not a list, tries to interpret it as an attribute
 * name.
 */
int igraphmodule_PyObject_to_attribute_values(PyObject *o,
					      igraph_vector_t *v,
					      igraphmodule_GraphObject* g,
					      int type, igraph_real_t def) {
  PyObject* list = o;
  long i, n;

  if (o==NULL) return 1;
  
  if (o == Py_None) {
    if (type == ATTRHASH_IDX_VERTEX) n=igraph_vcount(&g->g);
    else if (type == ATTRHASH_IDX_EDGE) n=igraph_ecount(&g->g);
    else n=1;

    if (igraph_vector_init(v, n)) return 1;
    for (i=0; i<n; i++) VECTOR(*v)[i] = def;
    return 0;
  }

  if (!PyList_Check(o)) {
    list = PyDict_GetItem(((PyObject**)g->g.attr)[type], o);
    if (!list) {
      if (!PyErr_Occurred())
	PyErr_SetString(PyExc_KeyError, "Attribute does not exist");
      return 1;
    }
  }

  n=PyList_Size(list);
  if (igraph_vector_init(v, n)) return 1;

  for (i=0; i<n; i++) {
    PyObject *item = PyList_GetItem(list, i);
    if (!item) {
      igraph_vector_destroy(v);
      return 1;
    }

    if (PyInt_Check(item))
      VECTOR(*v)[i] = PyInt_AsLong(item);
    else if (PyLong_Check(item))
      VECTOR(*v)[i] = PyLong_AsLong(item);
    else if (PyFloat_Check(item))
      VECTOR(*v)[i] = PyFloat_AsDouble(item);
    else
      VECTOR(*v)[i] = def;
  }

  return 0;
}


syntax highlighted by Code2HTML, v. 0.9.1