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

#include "numpy/arrayobject.h"

#include <gpk_std.h>
#include <gpklib/ieee754.h>
#include <gpklib/innerloop4.h>
#include <gpklib/avg.h>
#include <gpklib/numerics.h>

/* Copyright 2005-2007 Greg Kochanski.
Released under the LGPL, version 2 or later.
*/


#define D if(0)



extern "C"
PyObject *
gpkavg_avg(PyObject *self, PyObject *args)
{
	PyObject *v;
        PyObject *wt = Py_None;
        double clip = 0.15;
	PyArrayObject *va, *wa;
	struct a_w out;
 
	if (!PyArg_ParseTuple(args, "O|Od", &v, &wt, &clip))
		return NULL;
	if(! (clip >= 0) || !(clip<0.5)) {
		PyErr_SetString(PyExc_ValueError, "Not (0 <= clip < 0.5).");
		return NULL;
		}

	D fprintf(stderr, "Arg parsed\n");
	va = (PyArrayObject *)
		PyArray_FromObject(v, PyArray_DOUBLE, 1, 1);
	if(va == NULL)
		return NULL;


	D fprintf(stderr, "va -> array\n");
	if(wt != Py_None)
		wa = (PyArrayObject *)
			PyArray_FromObject(wt, PyArray_DOUBLE, 1, 1);
	else
		wa = NULL;
	D fprintf(stderr, "wa -> ???\n");

	int n = va->dimensions[0];
	if(wa && n != wa->dimensions[0])	{
		Py_DECREF(wa);
		Py_DECREF(va);
		PyErr_SetString(PyExc_ValueError, "Size of value and weight arrays must match.");
		return NULL;
	}
	D fprintf(stderr, "checked\n");
 

	struct v_wt *p = new v_wt [n];
	C int vs = va->strides[0];
	C char *vp = va->data;
	D fprintf(stderr, "prepped\n");
	assert(p);
	if(wa)	{
		C int ws = wa->strides[0];
		int nneg = 0;
		int ineg = -1;
		double vneg = 0.0;
		double wsum = 0.0;
		C char *wp0 = wa->data;
		int i;
		for(i=0; i<n; i++) {
			C double *wp = (double *)(wp0+i*ws);
			p[i].v = *(double *)(vp+i*vs);
			if( !((*wp) >= 0.0) ) {
				nneg += 1;
				if(ineg < 0) {
					ineg = i;
					vneg = *wp;
				}
				p[i].wt = 0.0;
			}
			else
		       		p[i].wt = *wp;
				wsum += *wp;
		}
		Py_DECREF(wa);
		if(nneg != 0) {
			char buf[128];
			Py_DECREF(va);
			sprintf(buf, "%d negative or NaN weight(s): %6g at %d",
					nneg, vneg, ineg);
			PyErr_SetString(PyExc_ValueError, buf);
			return NULL;
			}
		if(! (wsum > 0.0)) {
			char buf[128];
			Py_DECREF(va);
			sprintf(buf, "Nonpositive weight sum: %g", wsum);
			PyErr_SetString(PyExc_ValueError, buf);
			return NULL;
			}
	}
	else {
		C int vs = va->strides[0];
		C char *vp = va->data;
		int i;
		for(i=0; i<n; i++) {
			p[i].v = *(double *)(vp+i*vs);
		       	p[i].wt = 1.0;
		}
	}
	D fprintf(stderr, "copied\n");
#if 0
	{
		int k;
		for(k=0; k<n; k++) {
			fprintf(stderr, "p[%d]=(%g,%g)\n", k, p[k].v, p[k].wt);
		}
	}
#endif
	Py_DECREF(va);
	Py_BEGIN_ALLOW_THREADS
	D fprintf(stderr, "multit {\n");
	out = avg_robust(p, n, clip);
	D fprintf(stderr, "avg_robust\n");
	delete [] p;
	D fprintf(stderr, "delete\n");
	Py_END_ALLOW_THREADS
	if(!finite(out.avg)) {
		PyErr_SetString(PyExc_ValueError, "No Center.");
		return NULL;
	}
	if(!finite(out.width))
		return Py_None;

	D fprintf(stderr, "multit }\n");
	D fprintf(stderr, "out=(%g,%g,%g)\n", out.avg, out.width, out.wt);
	return Py_BuildValue("(dd)", out.avg, out.width);
}


PyObject *
gpkavg_monotonic_guts(PyObject *self, PyObject *args,
			double * (*monotonizer)(const double*, int, int))
{
	double *a0;
	PyObject *v;
	PyArrayObject *a, *va;
        int sign = 1;
	npy_intp dims[1];
 
	if (!PyArg_ParseTuple(args, "O|i", &v, &sign))
		return NULL;
	if(sign == 0) {
		PyErr_SetString(PyExc_ValueError, "Sign = +1 or -1.");
		return NULL;
		}

	D fprintf(stderr, "Arg parsed\n");
	va = (PyArrayObject *)
		PyArray_ContiguousFromObject(v, PyArray_DOUBLE, 1, 1);
	if(va == NULL)
		return NULL;

	int n = va->dimensions[0];
	Py_BEGIN_ALLOW_THREADS
	a0 = (*monotonizer)((const double *)(va->data), n, sign);
	Py_END_ALLOW_THREADS
	Py_DECREF(va);
	dims[0] = n;
	/* a = (PyArrayObject *)PyArray_FromDims(1, dims, PyArray_DOUBLE); */
	a = (PyArrayObject *)PyArray_SimpleNew(1, dims, NPY_DOUBLE);
	Py_BEGIN_ALLOW_THREADS
	double *ad = (double *)(a->data);

	int j;
	for(j=0; j<n; j++)
		ad[j] = a0[j];
	/* INNERLOOP(j, 0, n, ad[j] = a0[j]); */
	free(a0);
	Py_END_ALLOW_THREADS

	return (PyObject *)a;
}




extern "C"
PyObject *
gpkavg_monotonic(PyObject *self, PyObject *args)
{
	return gpkavg_monotonic_guts(self, args, monotonic);
}



extern "C"
PyObject *
gpkavg_monomedian(PyObject *self, PyObject *args)
{
	return gpkavg_monotonic_guts(self, args, monotonic_median);
}



static char DocString[] = "This is a fast, C module implementing weighted trimmed averages,\n"
"and two flavors of best-fit monotonic functions.";


static PyMethodDef GpkavgMethods[] = {
	{"avg", gpkavg_avg, METH_VARARGS},
	{"monotonic", gpkavg_monotonic, METH_VARARGS},
	{"monomedian", gpkavg_monomedian, METH_VARARGS},
	{NULL, NULL}
};


extern "C" void initgpkavg()
{
	PyObject *m = Py_InitModule("gpkavg", GpkavgMethods);
	if(!m)
		return;
	PyModule_AddObject(m, "__doc__", PyString_FromString(DocString) );
	import_array();
}

