#define PY_SSIZE_T_CLEAN
#include <Python.h>
#include <DateTime.h>

#include "cmunipack.h"

PyObject *cmpack_error(int code)
{
	char *msg = cmpack_formaterror(code);
	PyErr_SetString(PyExc_ValueError, msg);
	cmpack_free(msg);
	return NULL;
}

PyObject *err_closed(void)
{
    PyErr_SetString(PyExc_ValueError, "I/O operation on closed file");
    return NULL;
} 

PyObject *return_datetime(CmpackDateTime *dt)
{
	return PyDateTime_FromDateAndTime(dt->date.year, dt->date.month, dt->date.day, 
		dt->time.hour, dt->time.minute, dt->time.second, 1000*dt->time.milisecond);
}

static PyObject *
encodejd(PyObject *self, PyObject *obj)
{
	CmpackDateTime aux;

	if (!PyDateTime_Check(obj)) {
		PyErr_SetString(PyExc_TypeError, "The function expects a datetime object.");
		return NULL;
	}

	aux.date.day = PyDateTime_GET_DAY(obj);
	aux.date.month = PyDateTime_GET_MONTH(obj);
	aux.date.year = PyDateTime_GET_YEAR(obj);
	aux.time.hour = PyDateTime_DATE_GET_HOUR(obj);
	aux.time.minute = PyDateTime_DATE_GET_MINUTE(obj);
	aux.time.second = PyDateTime_DATE_GET_SECOND(obj);
	aux.time.milisecond = PyDateTime_DATE_GET_MICROSECOND(obj)/1000;
	return PyFloat_FromDouble(cmpack_encodejd(&aux));
}

static PyObject *
decodejd(PyObject *self, PyObject *args)
{
	CmpackDateTime aux;
	double jd;

	if (!PyArg_ParseTuple(args, "d", &jd))
		return NULL;

	if (cmpack_decodejd(jd, &aux)!=0) {
		PyErr_SetString(PyExc_ValueError, "Given value is not a valid Julian date.");
		return NULL;
	}

	return return_datetime(&aux);
}

static PyObject *
strtora(PyObject *self, PyObject *args)
{
	const char *str;
	double ra;

	if (!PyArg_ParseTuple(args, "s", &str))
		return NULL;

	if (cmpack_strtora(str, &ra)!=0) {
		PyErr_SetString(PyExc_ValueError, "Given value is not a valid right ascension.");
		return NULL;
	}
	
	return PyFloat_FromDouble(ra);
}

static PyObject *
strtodec(PyObject *self, PyObject *args)
{
	const char *str;
	double dec;

	if (!PyArg_ParseTuple(args, "s", &str))
		return NULL;

	if (cmpack_strtodec(str, &dec)!=0) {
		PyErr_SetString(PyExc_ValueError, "Given value is not a valid declination.");
		return NULL;
	}
	
	return PyFloat_FromDouble(dec);
}

static PyObject *
strtolat(PyObject *self, PyObject *args)
{
	const char *str;
	double lat;

	if (!PyArg_ParseTuple(args, "s", &str))
		return NULL;

	if (cmpack_strtolat(str, &lat)!=0) {
		PyErr_SetString(PyExc_ValueError, "Given value is not a valid latitude.");
		return NULL;
	}
	
	return PyFloat_FromDouble(lat);
}

static PyObject *
strtolon(PyObject *self, PyObject *args)
{
	const char *str;
	double lon;

	if (!PyArg_ParseTuple(args, "s", &str))
		return NULL;

	if (cmpack_strtolon(str, &lon)!=0) {
		PyErr_SetString(PyExc_ValueError, "Given value is not a valid longitude.");
		return NULL;
	}
	
	return PyFloat_FromDouble(lon);
}

static PyObject *
ratostr(PyObject *self, PyObject *args)
{
	double ra;
	char buf[32];

	if (!PyArg_ParseTuple(args, "d", &ra))
		return NULL;

	if (cmpack_ratostr(ra, buf, 32)!=0) {
		PyErr_SetString(PyExc_ValueError, "Given value is not a valid right ascension.");
		return NULL;
	}
	
	return Py_BuildValue("s", buf);
}

static PyObject *
dectostr(PyObject *self, PyObject *args)
{
	double dec;
	char buf[32];

	if (!PyArg_ParseTuple(args, "d", &dec))
		return NULL;

	if (cmpack_dectostr(dec, buf, 32)!=0) {
		PyErr_SetString(PyExc_ValueError, "Given value is not a valid declination.");
		return NULL;
	}
	
	return Py_BuildValue("s", buf);
}

static PyObject *
lattostr(PyObject *self, PyObject *args)
{
	double lat;
	char buf[32];

	if (!PyArg_ParseTuple(args, "d", &lat))
		return NULL;

	if (cmpack_lattostr(lat, buf, 32)!=0) {
		PyErr_SetString(PyExc_ValueError, "Given value is not a valid latitude.");
		return NULL;
	}
	
	return Py_BuildValue("s", buf);
}

static PyObject *
lontostr(PyObject *self, PyObject *args)
{
	double lon;
	char buf[32];

	if (!PyArg_ParseTuple(args, "d", &lon))
		return NULL;

	if (cmpack_lontostr(lon, buf, 32)!=0) {
		PyErr_SetString(PyExc_ValueError, "Given value is not a valid longitude.");
		return NULL;
	}
	
	return Py_BuildValue("s", buf);
}

static PyObject *
robustmean(PyObject *self, PyObject *obj)
{
	int res;
	Py_ssize_t i, size;
	double *items, mean, stddev;

	if (!PyList_Check(obj)) {
		PyErr_SetString(PyExc_TypeError, "The function expects a list of floats.");
		return NULL;
	}

	size = PyList_Size(obj);
	if (size==0)
		Py_RETURN_NONE;

	items = malloc(size*sizeof(double));
	if (!items) {
		PyErr_SetString(PyExc_MemoryError, "Memory allocation error.");
		return NULL;
	}
	for (i=0; i<size; i++) {
		PyObject *item = PyList_GetItem(obj, i);
		if (!PyFloat_Check(item)) {
			PyErr_SetString(PyExc_TypeError, "The function expects a list of floats.");
			free(items);
			return NULL;
		}
		items[i] = PyFloat_AsDouble(item);
	}
	res = cmpack_robustmean((int)size, items, &mean, &stddev);
	if (res!=0) {
		cmpack_error(res);
		return NULL;
	}
	return Py_BuildValue("(dd)", mean, stddev);
}

static PyObject *
version(PyObject *self, PyObject *obj)
{
	return Py_BuildValue("(iiis)", cmpack_major_version(), cmpack_minor_version(),
		cmpack_revision_number(), cmpack_versionid());
}

static PyObject *
check_version(PyObject *self, PyObject *args)
{
	int major, minor, revision;

	if (!PyArg_ParseTuple(args, "iii", &major, &minor, &revision))
		return NULL;

	return PyBool_FromLong(cmpack_check_version(major, minor, revision)!=0);
}

static PyMethodDef cmpack_methods[] = {
	{"encodejd", encodejd, METH_O,
	"Convert date and time parts to Julian date"},
	{"decodejd", decodejd, METH_VARARGS, 
	"Convert Julian date to parts of date and time"},
	{"strtora", strtora, METH_VARARGS, 
	"Convert string to right ascension"},
	{"strtodec", strtodec, METH_VARARGS, 
	"Convert string to declination"},
	{"ratostr", ratostr, METH_VARARGS, 
	"Convert right ascension to string"},
	{"dectostr", dectostr, METH_VARARGS, 
	"Convert declination to string"},
	{"strtolat", strtolat, METH_VARARGS, 
	"Convert string to latitude"},
	{"strtolon", strtolon, METH_VARARGS, 
	"Convert string to longitude"},
	{"lattostr", lattostr, METH_VARARGS, 
	"Convert latitude to string"},
	{"lontostr", lontostr, METH_VARARGS, 
	"Convert longitude to string"},
	{"robustmean", robustmean, METH_O,
	"Computes robust mean and standard deviation"},
	{"version", version, METH_NOARGS,
	"Get C-Munipack library version"},
	{"check_version", check_version, METH_VARARGS,
	"Returns True if the C-Munipack library is of specified version or higher"},
	{ NULL }
};

static struct PyModuleDef cmpack_module = {
	PyModuleDef_HEAD_INIT,
	.m_name = "cmpack",   /* name of module */
	.m_doc = "C-Munipack 2.0", /* module documentation, may be NULL */
	.m_size = -1,       /* size of per-interpreter state of the module,
				 or -1 if the module keeps state in global variables. */
	.m_methods = cmpack_methods
};


#define ADD_CONST(name, value) \
	PyModule_AddIntConstant(m, #name, (value))

#define ADD_CLASS(c) { extern PyTypeObject c ## Type; \
	c ## Type.tp_new = PyType_GenericNew; \
    if (PyType_Ready(&c ## Type) < 0) return NULL; \
    Py_INCREF(&c ## Type); \
	PyModule_AddObject(m, #c, (PyObject*)&c ## Type); \
}

PyMODINIT_FUNC
initcmpack(void)
{
	PyObject* m;

	cmpack_init();

	m = PyModule_Create(&cmpack_module);
    if (m == NULL)
        return NULL; 

	ADD_CONST(READONLY, CMPACK_OPEN_READONLY);
	ADD_CONST(READWRITE, CMPACK_OPEN_READWRITE);
	ADD_CONST(CREATE, CMPACK_OPEN_CREATE);
	ADD_CONST(FORMAT_UNKNOWN, CMPACK_FORMAT_UNKNOWN);
	ADD_CONST(FORMAT_FITS, CMPACK_FORMAT_FITS);
	ADD_CONST(FORMAT_SBIG, CMPACK_FORMAT_SBIG);
	ADD_CONST(FORMAT_OES, CMPACK_FORMAT_OES);
	ADD_CONST(FORMAT_CRW, CMPACK_FORMAT_CRW);
	ADD_CONST(FORMAT_NEF, CMPACK_FORMAT_NEF);
	ADD_CONST(FORMAT_MRW, CMPACK_FORMAT_MRW);
	ADD_CONST(FORMAT_CR3, CMPACK_FORMAT_CR3);
	ADD_CONST(CHANNEL_DEFAULT, CMPACK_CHANNEL_DEFAULT);
	ADD_CONST(CHANNEL_SUM, CMPACK_CHANNEL_SUM);
	ADD_CONST(CHANNEL_RED, CMPACK_CHANNEL_RED);
	ADD_CONST(CHANNEL_GREEN, CMPACK_CHANNEL_GREEN);
	ADD_CONST(CHANNEL_BLUE, CMPACK_CHANNEL_BLUE);
	ADD_CONST(BITPIX_UNKNOWN, CMPACK_BITPIX_UNKNOWN);
	ADD_CONST(BITPIX_INT16, CMPACK_BITPIX_SSHORT);
	ADD_CONST(BITPIX_UINT16, CMPACK_BITPIX_USHORT);
	ADD_CONST(BITPIX_INT32, CMPACK_BITPIX_SLONG);
	ADD_CONST(BITPIX_UINT32, CMPACK_BITPIX_ULONG);
	ADD_CONST(BITPIX_FLOAT32, CMPACK_BITPIX_FLOAT);
	ADD_CONST(BITPIX_FLOAT64, CMPACK_BITPIX_DOUBLE);
	ADD_CONST(MATCH_STANDARD, CMPACK_MATCH_STANDARD);
	ADD_CONST(MATCH_AUTO, CMPACK_MATCH_AUTO);
	ADD_CONST(MATCH_SPARSE_FIELDS, CMPACK_MATCH_SPARSE_FIELDS);
	ADD_CONST(TABLE_UNSPECIFIED, CMPACK_TABLE_UNSPECIFIED);
	ADD_CONST(TABLE_LIGHT_CURVE, CMPACK_TABLE_LCURVE_DIFF);
	ADD_CONST(TABLE_MUNIFIND, CMPACK_TABLE_MAGDEV);
	ADD_CONST(TABLE_TRACK_LIST, CMPACK_TABLE_TRACKLIST);
	ADD_CONST(TABLE_APERTURE, CMPACK_TABLE_APERTURES);
	ADD_CONST(TABLE_AIR_MASS, CMPACK_TABLE_AIRMASS);
	ADD_CONST(LCURVE_DIFFERENTIAL, 0);
	ADD_CONST(LCURVE_INSTRUMENTAL, 1);
	ADD_CONST(JD_GEOCENTRIC, 0);
	ADD_CONST(JD_HELIOCENTRIC, 1);
	ADD_CONST(JD_BOTH, 2);
	ADD_CONST(VARIABLE_STAR, CMPACK_SELECT_VAR);
	ADD_CONST(COMPARISON_STAR, CMPACK_SELECT_COMP);
	ADD_CONST(CHECK_STAR, CMPACK_SELECT_CHECK);

	ADD_CLASS(AirMass);
	ADD_CLASS(AirMassCurve);
	ADD_CLASS(ApDevCurve);
	ADD_CLASS(Bias);
	ADD_CLASS(CatFile);
	ADD_CLASS(CCDFile);
	ADD_CLASS(Dark);
	ADD_CLASS(Flat);
	ADD_CLASS(FrameSet);
	ADD_CLASS(HelCorr);
	ADD_CLASS(Kombine);
	ADD_CLASS(LightCurve);
	ADD_CLASS(MagDevCurve);
	ADD_CLASS(MasterBias);
	ADD_CLASS(MasterDark);
	ADD_CLASS(MasterFlat);
	ADD_CLASS(Match);
	ADD_CLASS(Photometry);
	ADD_CLASS(PhtFile);
	ADD_CLASS(Table);
	ADD_CLASS(TrackCurve);
	
	PyDateTime_IMPORT;

	return m;
}
