#include "Python.h"
#include <ctype.h>

#include "numpy/arrayobject.h"

#include <ctype.h>
#include "gpk_std.h"
#include "gpklib/image.h"
#include "gpklib/innerloop4.h"
#include "gpklib/ieee754.h"
#include "gpklib/assert.h"
#include "gpkio.h"
#include "gpkio/rw_misc.h"


#define D	if(0)

C int SMALLDIM = 10;	/* Ideally, chosen un consideration of INNERLOOP's overhead. */
C int SMALLSTRIDE = 16;	/* Ideally, chosen to match the processor's cache. */


static PyObject * gpkimg_read(PyObject*self, PyObject*args)
{
/* int d[2]; */
npy_intp d[2];
const char *name;
struct image *i;
PyArrayObject *a = 0;
struct multi_image ii;
int hdronly = 0;

if(!PyArg_ParseTuple(args, "s|i:gpkimg.read(string)", &name, &hdronly))
	return NULL;

Py_BEGIN_ALLOW_THREADS
ii = readfile(name, hdronly);

if(ii.n_image <= 0) {
	char b1[] = "Cannot open ";
	char *buf = (char *)malloc(strlen(name) + strlen(b1) + 1);
	strcpy(buf, b1);
	strcat(buf, name);
	Py_BLOCK_THREADS;
	PyErr_SetString(PyExc_IOError, buf);
	free(buf);
	return NULL;
	}

assert(ii.n_image == 1);
i = &ii.image[0];
D fprintf(stderr, "read in %dx%d\n", i->npts, i->nlines);
assert(i->npts>0 && i->nlines>0);
BOOL ok = image_normalize(i);
D fprintf(stderr, "read in %dx%d\n", i->npts, i->nlines);
D fprintf(stderr, "ok=%d\n", ok);
assert(ok);

d[1] = i->npts;
d[0] = i->nlines;

Py_END_ALLOW_THREADS;
D fprintf(stderr, "read in %ldx%ld\n", (long)(d[0]), (long)(d[1]));
/*
a = (PyArrayObject *)PyArray_FromDims(2, d, PyArray_FLOAT);
*/
if(!hdronly) {
	a = (PyArrayObject *)PyArray_SimpleNew(2, d, NPY_FLOAT);
	assert(a);
	assert(i->H);
}

assert(i->prop);
proplist pl = *(i->prop);
Py_BEGIN_ALLOW_THREADS
if(!hdronly)
	memcpy(a->data, i->H[0], sizeof(float)*i->nlines*i->npts);
normalize_proplist(&pl, i);
Py_END_ALLOW_THREADS;

PyObject *dict = PyDict_New();
assert(dict);

C property *p = pl.first;
for(; p; p=p->next) {
	if(!p->attr || !*p->attr || !p->val) {
		continue;
	}
	if(pl.get(p->attr) != p) {	/* Drop duplicates. */
		continue;
	}

	char *a0 = strdup(p->attr);
	char *at = a0;
	assert(a0);

	/* Take out special markings on attributes. */
	if(*at == '%') {
		char *b;
		at++;
		for(b=at; *b; b++)
			*b = toupper(*b);
	}
	if(*at != '\0'  && at[strlen(at)-1]=='*')
		at[strlen(at)-1] = '\0';

	PyObject *val = PyString_FromString(p->val);
	if(PyDict_SetItemString(dict, at, val) < 0)
		assert(0);
	free(a0);
	Py_DECREF(val);
}

multi_image_destr(&ii);

if(a == 0)
	return dict;
return Py_BuildValue("NN", dict, a);	/* Should this be "NN" ? */
}






static PyObject * gpkimg_write(PyObject*self, PyObject*args)
{
 const char *name;
 PyObject *hdr, *array;
 struct image i;
 BOOL ok;

 D fprintf(stderr, "A\n");
 if(!PyArg_ParseTuple(args, "sOO:gpkimg.write(string,hdr,array)", &name, &hdr, &array))
	return NULL;

 if(! PyArray_Check(array)) {
	PyErr_SetString(PyExc_TypeError, "array must be Numeric");
	return NULL;
 }
 

 C int typenum = ((PyArrayObject*)array)->descr->type_num;
 PyArrayObject *a;
 if(typenum == PyArray_DOUBLE)
	a = (PyArrayObject *)PyArray_FromObject(array, PyArray_DOUBLE, 2, 2);
 else
	a = (PyArrayObject *)PyArray_FromObject(array, PyArray_FLOAT, 2, 2);
 if(a == NULL)
	return NULL;

 D fprintf(stderr, "C\n");
 if(PySequence_Check(hdr) < 0)	{
	Py_DECREF(a);
	PyErr_SetString(PyExc_TypeError, "arg 2 needs to be a sequence");
	return NULL;
 }
 /* hdr is a list of (a, v) tuples */

 Py_BEGIN_ALLOW_THREADS
 D fprintf(stderr, "D\n");
 image_constr_all(&i, quiet_nan(0), quiet_nan(0), quiet_nan(0),
		(C char *)0, quiet_nan(0), (C char *)0, (char *)0);
 i.zscale = 1.0;
 i.zshift = 0.0;
 D fprintf(stderr, "E:%ldx%ld\n", (long)(a->dimensions[0]),
 			(long)(a->dimensions[1])
			);
 ok = image_alloc_float(&i, a->dimensions[0], a->dimensions[1]);
 assert(ok);

 D fprintf(stderr, "image_check()=%d\n", image_check(&i));
 Py_END_ALLOW_THREADS

 D fprintf(stderr, "G\n");
 int j;
 C int ntuples = PySequence_Length(hdr);


 for(j=0; j<ntuples; j++) {
	PyObject *tup = PySequence_GetItem(hdr, j);
	if(tup == NULL) {
		Py_DECREF(a);
		image_destr(&i);
		return NULL;
	}
	if(!PyTuple_Check(tup) || PyTuple_Size(tup)!=2) {
		image_destr(&i);
		Py_DECREF(a);
		Py_DECREF(tup);
		PyErr_SetString(PyExc_TypeError, "Header must be a sequence of 2-tuples.");
		return NULL;
	}
	PyObject *attr = PyTuple_GetItem(tup, 0);
	/* PyTuple_GetItem returns a borrowed reference.  Do Not call Py_DECREF on it.*/
	if(attr == NULL) {
		image_destr(&i);
		Py_DECREF(a);
		Py_DECREF(tup);
		return NULL;
	}
	if(!PyString_Check(attr)) {
		image_destr(&i);
		Py_DECREF(a);
		Py_DECREF(tup);
		PyErr_SetString(PyExc_TypeError, "mapping key must be string");
		return NULL;
	}
	char *aa = PyString_AsString(attr);
		/* Aa points into the python obj. Don't free. */
	assert(aa);
	PyObject *val = PyTuple_GetItem(tup, 1);
	/* PyTuple_GetItem returns a borrowed reference.  Do Not call Py_DECREF on it.*/
	if(val == NULL) {
		image_destr(&i);
		Py_DECREF(a);
		Py_DECREF(tup);
		return NULL;
	}
	char *vv;
	if(PyString_Check(val))
		vv = PyString_AsString(val);	/* vv points into val. Don't DECREF early. */
	else	{
		image_destr(&i);
		PyErr_SetString(PyExc_TypeError, "All values must be strings");
		Py_DECREF(a);
		Py_DECREF(tup);
		return NULL;
	}


	if(strcmp(aa, "NAXIS1") == 0)
		;
	else if(strcmp(aa, "NAXIS2") == 0)
		;
	else if(strcmp(aa, "NAXIS") == 0)
		;
	else {
		D fprintf(stderr, "setting %s = %s\n", aa, vv);
		set_attr(&i, aa, vv);
	Py_DECREF(tup);
	}

 }
 D fprintf(stderr, "I\n");
 D fprintf(stderr, "image_check()=%d\n", image_check(&i));


 Py_BEGIN_ALLOW_THREADS
 D fprintf(stderr, "J\n");
 if(a->descr->type_num == PyArray_FLOAT) {
	float *H = i.H[0];
	char *ad = a->data;
	C npy_intp *s = a->strides;
	C int np = i.npts;
	if(s[1] == sizeof(float))
		memcpy(H, ad, sizeof(float)*i.nlines*np);
	else
		INNERLOOP(j, 0, i.nlines*np, H[j] = *(float *)(ad + (j/np)*s[0]+(j%np)*s[1]))
 }
 else if(a->descr->type_num == PyArray_DOUBLE) {
	float *H = i.H[0];
	char *ad = a->data;
	C npy_intp *s = a->strides;
	C int np = i.npts;
	INNERLOOP(j, 0, i.nlines*i.npts, H[j] = *(double *)(ad + (j/np)*s[0]+(j%np)*s[1]))
 }
 else
	assert(0);
 D fprintf(stderr, "image_check()=%d\n", image_check(&i));
 Py_BLOCK_THREADS;
 Py_DECREF(a);
 Py_UNBLOCK_THREADS;

 D fprintf(stderr, "K\n");
 D fprintf(stderr, "image_check()=%d\n", image_check(&i));

 ok = writefile(&i, name, (char *)0);
 D fprintf(stderr, "L\n");
 image_destr(&i);
 Py_END_ALLOW_THREADS
 if(!ok) {
	char b1[] = "Cannot write ";
	char *buf = (char *)PyMem_Malloc(strlen(name) + strlen(b1) + 1);
	strcpy(buf, b1);
	strcat(buf, name);
	PyErr_SetString(PyExc_IOError, buf);
	PyMem_Free(buf);
	return NULL;
	}

 D fprintf(stderr, "M\n");

 Py_INCREF(Py_None);
 return Py_None;
}





#if 0

static PyObject *
gpkimg_isNaN(PyObject *self, PyObject *args)
{
	PyObject *input;
	PyArrayObject *array, *result;
	int i;
	int isfloat = 0;
	int dim[2], ris, rjs, ais, ajs;
 
	if (!PyArg_ParseTuple(args, "O", &input))
		return NULL;
	array = (PyArrayObject *)
		PyArray_FromObject(input, PyArray_FLOAT, 2, 2);
	if (array == NULL) {
		array = (PyArrayObject *)
			PyArray_FromObject(input, PyArray_DOUBLE, 2, 2);
		if(array == NULL)
			return NULL;
		isfloat = 0;
	}
	else
		isfloat = 1;
 
/*
	result = (PyArrayObject *)PyArray_FromDims(array->nd, array->dimensions, PyArray_SHORT);
*/
	result = (PyArrayObject *)PyArray_SimpleNew(array->nd, array->dimensions, NPY_SHORT);
	if (result == NULL)	 {
		Py_DECREF(array);
		return NULL;
	}

	/* Arrange to put the longer dimension in the inner loop,
	 * for greater efficiency. */
	const int order = (array->dimensions[1] > array->dimensions[0])
				+ (array->dimensions[0]<SMALLDIM)
				- (array->dimensions[1]<SMALLDIM)
				+ (array->strides[0]<SMALLSTRIDE)
				+ (result->strides[0]<SMALLSTRIDE)
				- (array->strides[1]<SMALLSTRIDE)
				- (result->strides[1]<SMALLSTRIDE);
	if(order >= 0) {
		dim[0] = array->dimensions[0];
		dim[1] = array->dimensions[1];
		ris = result->strides[0];
		rjs = result->strides[1];
		ais = array->strides[0];
		ajs = array->strides[1];
	}
	else {
		dim[1] = array->dimensions[0];
		dim[0] = array->dimensions[1];
		rjs = result->strides[0];
		ris = result->strides[1];
		ajs = array->strides[0];
		ais = array->strides[1];
	}

	OPENMP(omp parallel for schedule(dynamic, 64))
	for (i = 0; i < dim[0]; i++) {
		char *ri = result->data + i * ris;
		char *ai = array->data + i * ais;
		if(isfloat) {
			INNERLOOP(j, 0, dim[1], *(short*)(ri+rjs*j) = (short)isnanf(*(float*)(ai+ajs*j)));
		}
		else	{
			INNERLOOP(j, 0, dim[1], *(short*)(ri+rjs*j) = (short)isnan(*(double*)(ai+ajs*j)));
		}
	}
	Py_DECREF(array);
	return PyArray_Return(result);
}



#endif



static PyMethodDef GpkimgMethods[] = {
#if 0
	{"isNaN", gpkimg_isNaN, METH_VARARGS, "Returns True if given Not-a-number"},
#endif
	{"read", gpkimg_read, METH_VARARGS, "Read in a data file"},
	{"write", gpkimg_write, METH_VARARGS, "Write out a data file."},
	{NULL, NULL}
};

static char *doc = "A class for reading 2-dimensional arrays of data.";

extern "C" void initgpkimg()
{
	PyObject *m = Py_InitModule3("gpkimg", GpkimgMethods, doc);
	if(!m)
		return;
	import_array();
}


