#define PY_SSIZE_T_CLEAN
#include <Python.h>

#include "cmunipack.h"
#include "phtfileclass.h"

PyObject *cmpack_error(int code);
PyObject *err_closed(void);
PyObject *return_datetime(CmpackDateTime *dt);

static void update_params(PhtFile* self)
{
	cmpack_pht_get_info(self->f, CMPACK_PI_FRAME_PARAMS	| CMPACK_PI_ORIGIN_CRDATE |
		CMPACK_PI_PHOT_PARAMS | CMPACK_PI_MATCH_PARAMS | CMPACK_PI_OBJECT | 
		CMPACK_PI_LOCATION, &self->p);
	cmpack_decodejd(self->p.jd, &self->date_time);
	self->p_valid = 1;
}

static PyObject *
PhtFile_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
{
    PhtFile *self = (PhtFile*)type->tp_alloc(type, 0);
    if (self != NULL) 
		self->f = NULL;
    return (PyObject *)self;
}

static void
PhtFile_dealloc(PhtFile* self)
{
	if (self->f) {
		cmpack_pht_destroy(self->f);
		self->f = NULL;
		self->p_valid = 0;
	}
    self->ob_base.ob_type->tp_free((PyObject*)self);
}

static int
PhtFile_init(PhtFile *self, PyObject *args, PyObject *kwds)
{
    if (!PyArg_ParseTuple(args, ""))
        return -1;

	if (!self->f) {
		self->f = cmpack_pht_init();
		if (!self->f) {
			PyErr_SetString(PyExc_MemoryError, "Memory allocation error");
			return -1;
		}
	}
	self->p_valid = 0;
    return 0;
}

static PyObject *
PhtFile_open(PhtFile *self, PyObject *args)
{
    char *name = NULL; 
	int res, mode = CMPACK_OPEN_READONLY;
	CmpackPhtFile *f;

    if (!PyArg_ParseTuple(args, "esi", Py_FileSystemDefaultEncoding, &name, &mode))
        return NULL;

	res = cmpack_pht_open(&f, name, mode, 0);
	PyMem_Free(name);
	if (res!=0)
		return cmpack_error(res);

	return PhtFile_FromHandle(f);
}

static PyObject *
PhtFile_test(PhtFile *self, PyObject *args)
{
	char *filename;
	PyObject *res;

	if (!PyArg_ParseTuple(args, "es", Py_FileSystemDefaultEncoding, &filename))
		return NULL;

	res = PyBool_FromLong(cmpack_pht_test(filename)!=0);
	PyMem_Free(filename);
	return res;
}

static PyObject *
PhtFile_self(PhtFile *self)
{
    if (!self->f)
        return err_closed();
    Py_INCREF(self);
    return (PyObject *)self;
} 

static PyObject *
PhtFile_exit(PyObject *f, PyObject *args)
{
    PyObject *ret = PyObject_CallMethod(f, "close", NULL);
    if (!ret)
        return NULL;

    Py_DECREF(ret);
    /* We cannot return the result of close since a true
     * value will be interpreted as "yes, swallow the
     * exception if one was raised inside the with block". */
    Py_RETURN_NONE;
} 

static PyObject *
PhtFile_close(PhtFile *self, PyObject *args)
{
	if (self->f) {
		int res = cmpack_pht_close(self->f);
		self->f = NULL;
		self->p_valid = 0;
		if (res!=0)
			return cmpack_error(res);
	}
	Py_RETURN_NONE;
}

static PyObject *
PhtFile_copy(PhtFile *self, PyObject *args, PyObject *kwds)
{
	int res;
    CmpackPhtFile *newf; 

	if (!self->f)
		return err_closed();

	newf = cmpack_pht_init();
	if (!newf) {
		PyErr_SetString(PyExc_MemoryError, "Memory allocation error.");
		return NULL;
	}
	res = cmpack_pht_copy(newf, self->f);
	if (res!=0) {
		cmpack_pht_destroy(newf);
		return cmpack_error(res);
	}
	return PhtFile_FromHandle(newf);
}

static PyObject *
PhtFile_save(PhtFile *self, PyObject *args, PyObject *kwds)
{
	int res;
    char *to; 
	CmpackPhtFile *f;

    static char *kwlist[] = {"to", NULL};
    if (!PyArg_ParseTupleAndKeywords(args, kwds, "es", kwlist, 
		Py_FileSystemDefaultEncoding, &to))
        return NULL;

	if (!self->f) {
		PyMem_Free(to);
		return err_closed();
	}

	res = cmpack_pht_open(&f, to, CMPACK_OPEN_CREATE, 0);
	if (res==0)
		res = cmpack_pht_copy(f, self->f);
	cmpack_pht_destroy(f);
	PyMem_Free(to);
	if (res!=0)
		return cmpack_error(res);
	Py_RETURN_NONE;
}

static PyObject *
PhtFile_image_size(PhtFile *self, void *data)
{
	if (!self->f)
		return err_closed();
	if (!self->p_valid)
		update_params(self);

	return Py_BuildValue("ii", self->p.width, self->p.height);
}

static PyObject *
PhtFile_date_time(PhtFile *self, void *data)
{
	if (!self->f)
		return err_closed();
	if (!self->p_valid)
		update_params(self);
	return return_datetime(&self->date_time);
}

static PyObject *
PhtFile_julian_date(PhtFile *self, void *data)
{
	if (!self->f)
		return err_closed();
	if (!self->p_valid)
		update_params(self);

	return PyFloat_FromDouble(self->p.jd);
}

static PyObject *
PhtFile_offset(PhtFile *self, void *data)
{
	if (!self->f)
		return err_closed();
	if (!self->p_valid)
		update_params(self);

	return Py_BuildValue("dd", self->p.offset[0], self->p.offset[1]);
}

static PyObject *
PhtFile_exposure(PhtFile *self, void *data)
{
	if (!self->f)
		return err_closed();
	if (!self->p_valid)
		update_params(self);

	return PyFloat_FromDouble(self->p.exptime);
}

static PyObject *
PhtFile_ccdtemp(PhtFile *self, void *data)
{
	if (!self->f)
		return err_closed();
	if (!self->p_valid)
		update_params(self);

	return PyFloat_FromDouble(self->p.ccdtemp);
}

static PyObject *
PhtFile_filter(PhtFile *self, void *data)
{
	if (!self->f)
		return err_closed();
	if (!self->p_valid)
		update_params(self);

	if (self->p.filter)
		return PyUnicode_FromString(self->p.filter);
	Py_RETURN_NONE;
}

static PyObject *
PhtFile_object_name(PhtFile *self, void *data)
{
	if (!self->f)
		return err_closed();
	if (!self->p_valid)
		update_params(self);

	if (self->p.object.designation) 
		return PyUnicode_FromString(self->p.object.designation);
	Py_RETURN_NONE;
}

static PyObject *
PhtFile_right_ascension(PhtFile *self, void *data)
{
	if (!self->f)
		return err_closed();
	if (!self->p_valid)
		update_params(self);

	if (self->p.object.ra_valid)
		return PyFloat_FromDouble(self->p.object.ra);
	Py_RETURN_NONE;
}

static PyObject *
PhtFile_declination(PhtFile *self, void *data)
{
	if (!self->f)
		return err_closed();
	if (!self->p_valid)
		update_params(self);

	if (self->p.object.dec_valid)
		return PyFloat_FromDouble(self->p.object.dec);
	Py_RETURN_NONE;
}

static PyObject *
PhtFile_object(PhtFile *self, void *data)
{
	PyObject *res = PyTuple_New(3);
	PyTuple_SET_ITEM(res, 0, PhtFile_object_name(self, data));
	PyTuple_SET_ITEM(res, 1, PhtFile_right_ascension(self, data));
	PyTuple_SET_ITEM(res, 2, PhtFile_declination(self, data));
	return res;
}

static PyObject *
PhtFile_location_name(PhtFile *self, void *data)
{
	if (!self->f)
		return err_closed();
	if (!self->p_valid)
		update_params(self);

	if (self->p.location.designation) 
		return PyUnicode_FromString(self->p.location.designation);
	Py_RETURN_NONE;
}

static PyObject *
PhtFile_longitude(PhtFile *self, void *data)
{
	if (!self->f)
		return err_closed();
	if (!self->p_valid)
		update_params(self);

	if (self->p.location.lon_valid)
		return PyFloat_FromDouble(self->p.location.lon);
	Py_RETURN_NONE;
}

static PyObject *
PhtFile_latitude(PhtFile *self, void *data)
{
	if (!self->f)
		return err_closed();
	if (!self->p_valid)
		update_params(self);

	if (self->p.location.lat_valid)
		return PyFloat_FromDouble(self->p.location.lat);
	Py_RETURN_NONE;
}

static PyObject *
PhtFile_location(PhtFile *self, void *data)
{
	PyObject *res = PyTuple_New(3);
	PyTuple_SET_ITEM(res, 0, PhtFile_location_name(self, data));
	PyTuple_SET_ITEM(res, 1, PhtFile_longitude(self, data));
	PyTuple_SET_ITEM(res, 2, PhtFile_latitude(self, data));
	return res;
}

static PyObject *
PhtFile_apertures(PhtFile *self, void *data)
{
	int i, count;
	PyObject *res;
	CmpackPhtAperture aper;

	if (!self->f)
		return err_closed();

	count = cmpack_pht_aperture_count(self->f);
	res = PyList_New(count);
	for (i=0; i<count; i++) {
		cmpack_pht_get_aperture(self->f, i, CMPACK_PA_ID, &aper);
		PyList_SET_ITEM(res, i, PyLong_FromLong(aper.id));
	}
	return res;
}

static PyObject *
PhtFile_stars(PhtFile *self, void *data)
{
	int i, count;
	PyObject *res;
	CmpackPhtObject obj;

	if (!self->f)
		return err_closed();

	count = cmpack_pht_object_count(self->f);
	res = PyList_New(count);
	for (i=0; i<count; i++) {
		cmpack_pht_get_object(self->f, i, CMPACK_PO_ID, &obj);
		PyList_SET_ITEM(res, i, PyLong_FromLong(obj.id));
	}
	return res;
}

static PyObject *
PhtFile_ref_stars(PhtFile *self, void *data)
{
	int i, j, count, items;
	PyObject *res;
	CmpackPhtObject obj;

	if (!self->f)
		return err_closed();

	count = cmpack_pht_object_count(self->f);
	for (i=items=0; i<count; i++) {
		cmpack_pht_get_object(self->f, i, CMPACK_PO_REF_ID, &obj);
		if (obj.ref_id>=0)
			items++;
	}
	res = PyList_New(items);
	for (i=j=0; i<count; i++) {
		cmpack_pht_get_object(self->f, i, CMPACK_PO_REF_ID, &obj);
		if (obj.ref_id>=0)
			PyList_SET_ITEM(res, j++, PyLong_FromLong(obj.ref_id));
	}
	return res;
}

static PyObject *
PhtFile_ref_table(PhtFile *self, void *data)
{
	int i, count;
	PyObject *res;
	CmpackPhtObject obj;

	if (!self->f)
		return err_closed();

	res = PyDict_New();
	count = cmpack_pht_object_count(self->f);
	for (i=0; i<count; i++) {
		cmpack_pht_get_object(self->f, i, CMPACK_PO_ID | CMPACK_PO_REF_ID, &obj);
		if (obj.ref_id>=0)
			PyDict_SetItem(res, PyLong_FromLong(obj.ref_id), PyLong_FromLong(obj.id));
	}
	return res;
}

static PyObject *
PhtFile_get_aperture(PhtFile *self, PyObject *args)
{
	int index, id;
	CmpackPhtAperture aper;

	if (!PyArg_ParseTuple(args, "i", &id))
		return NULL;

	if (!self->f)
		return err_closed();

	index = cmpack_pht_find_aperture(self->f, id);
	if (index<0)
		Py_RETURN_NONE;

	cmpack_pht_get_aperture(self->f, index, CMPACK_PA_RADIUS, &aper);
	return PyFloat_FromDouble(aper.radius);
}

static PyObject *
PhtFile_get_aperture_dict(PhtFile *self, PyObject *args)
{
	int index, id;
	CmpackPhtAperture aper;
	PyObject *res;

	if (!PyArg_ParseTuple(args, "i", &id))
		return NULL;

	if (!self->f)
		return err_closed();

	index = cmpack_pht_find_aperture(self->f, id);
	if (index<0)
		Py_RETURN_NONE;

	cmpack_pht_get_aperture(self->f, index, CMPACK_PA_ID | CMPACK_PA_RADIUS, &aper);

	res = PyDict_New();
	PyDict_SetItemString(res, "x", PyLong_FromLong(aper.id));
	PyDict_SetItemString(res, "radius", PyFloat_FromDouble(aper.radius));
	return res;
}

static PyObject *
PhtFile_get_star(PhtFile *self, PyObject *args)
{
	int index, id;
	CmpackPhtObject obj;
	PyObject *byref = Py_False;

	if (!PyArg_ParseTuple(args, "i|O!", &id, &PyBool_Type, &byref))
		return NULL;

	if (!self->f)
		return err_closed();

	index = cmpack_pht_find_object(self->f, id, byref==Py_True);
	if (index<0)
		Py_RETURN_NONE;

	cmpack_pht_get_object(self->f, index, CMPACK_PO_CENTER | CMPACK_PO_REF_ID, &obj);
	if (obj.ref_id>=0)
		return Py_BuildValue("ddi", obj.x, obj.y, obj.ref_id);
	else
		return Py_BuildValue("ddO", obj.x, obj.y, Py_None);
}

static PyObject *
PhtFile_get_star_dict(PhtFile *self, PyObject *args)
{
	int index, id;
	CmpackPhtObject obj;
	PyObject *byref = Py_False, *res;

	if (!PyArg_ParseTuple(args, "i|O!", &id, &PyBool_Type, &byref))
		return NULL;

	if (!self->f)
		return err_closed();

	index = cmpack_pht_find_object(self->f, id, byref==Py_True);
	if (index<0)
		Py_RETURN_NONE;

	cmpack_pht_get_object(self->f, index, CMPACK_PO_CENTER | CMPACK_PO_REF_ID |
		CMPACK_PO_SKY | CMPACK_PO_FWHM, &obj);
	
	res = PyDict_New();
	PyDict_SetItemString(res, "x", PyFloat_FromDouble(obj.x));
	PyDict_SetItemString(res, "y", PyFloat_FromDouble(obj.y));
	PyDict_SetItemString(res, "sky_mean", PyFloat_FromDouble(obj.skymed));
	PyDict_SetItemString(res, "sky_stdev", PyFloat_FromDouble(obj.skysig));
	PyDict_SetItemString(res, "fwhm", PyFloat_FromDouble(obj.fwhm));
	if (obj.ref_id>=0)
		PyDict_SetItemString(res, "ref_id", PyLong_FromLong(obj.ref_id));
	return res;
}

static PyObject *
PhtFile_get_mag(PhtFile *self, PyObject *args)
{
	int i, j, object, aperture;
	PyObject *byref = Py_False;
	CmpackPhtData data;

	if (!PyArg_ParseTuple(args, "ii|O!", &object, &aperture, &PyBool_Type, &byref))
		return NULL;

	if (!self->f)
		return err_closed();

	i = cmpack_pht_find_object(self->f, object, byref==Py_True);
	if (i<0)
		Py_RETURN_NONE;
	j = cmpack_pht_find_aperture(self->f, aperture);
	if (j<0)
		Py_RETURN_NONE;

	cmpack_pht_get_data(self->f, i, j, &data);
	if (!data.mag_valid)
		return Py_BuildValue("OO", Py_None, Py_None);
	else
		return Py_BuildValue("dd", data.magnitude, data.mag_error);
}

static PyObject *
PhtFile_get_all(PhtFile *self, PyObject *args)
{
	int i, j, aperture, count;
	PyObject *byref = Py_False, *res, *val;
	CmpackPhtObject obj;
	CmpackPhtData data;

	if (!PyArg_ParseTuple(args, "i|O!", &aperture, &PyBool_Type, &byref))
		return NULL;

	if (!self->f)
		return err_closed();

	j = cmpack_pht_find_aperture(self->f, aperture);
	if (j<0)
		Py_RETURN_NONE;

	res = PyDict_New();
	if (res) {
		count = cmpack_pht_object_count(self->f);
		for (i=0; i<count; i++) {
			if (cmpack_pht_get_object(self->f, i, CMPACK_PO_ID | CMPACK_PO_REF_ID, &obj)==0) {
				if (!byref || obj.ref_id>=0) {
					cmpack_pht_get_data(self->f, i, j, &data);
					if (!data.mag_valid)
						val = Py_BuildValue("OO", Py_None, Py_None);
					else
						val = Py_BuildValue("dd", data.magnitude, data.mag_error);
					PyDict_SetItem(res,PyLong_FromLong(byref ? obj.ref_id : obj.id), val);
				}
			}
		}
	}
	return res;
}

static PyGetSetDef PhtFile_getset[] = {
	{"image_size", (getter)PhtFile_image_size, NULL,
	"Image width and height in pixels"},
	{"date_time", (getter)PhtFile_date_time, NULL,
	"Date and time of observation"},
	{"julian_date", (getter)PhtFile_julian_date, NULL,
	"Julian date of observation"},
	{"exposure", (getter)PhtFile_exposure, NULL,
	"Exposure duration in seconds"},
	{"ccdtemp", (getter)PhtFile_ccdtemp, NULL,
	"CCD temperature in C"},
	{"filter", (getter)PhtFile_filter, NULL,
	"Color filter designation"},
	{"object", (getter)PhtFile_object, NULL, 
	"Object designation and coordinates"},
	{"location", (getter)PhtFile_location, NULL,
	"Observatory designation and coordinates"},
	{"offset", (getter)PhtFile_offset, NULL,
	"Frame offset in x and y coordinates with respect to the reference frame"},
	{"apertures", (getter)PhtFile_apertures, NULL,
	"List of aperture identifiers"},
	{"stars", (getter)PhtFile_stars, NULL,
	"List of star identifiers"},
	{"ref_stars", (getter)PhtFile_ref_stars, NULL,
	"List of reference identifiers"},
	{"ref_table", (getter)PhtFile_ref_table, NULL,
	"Get crossreference table (ref_id => object_id)"},
	{NULL}
};

static PyMethodDef PhtFile_methods[] = {
	{"test", (PyCFunction)PhtFile_test, METH_VARARGS | METH_STATIC,
	"Check if the file is a Pht frame"},
	{"open", (PyCFunction)PhtFile_open, METH_VARARGS | METH_STATIC,
	"Open a CCD frame stored in a file"},

	{"close", (PyCFunction)PhtFile_close, METH_NOARGS,
	"Close file"},
	{"copy", (PyCFunction)PhtFile_copy, METH_NOARGS,
	"Make copy of the frame"},
	{"save", (PyCFunction)PhtFile_save, METH_KEYWORDS,
	"Save frame to a file"},

    {"__enter__", (PyCFunction)PhtFile_self, METH_NOARGS,  
	"Checks if the file is open."},
    {"__exit__",  (PyCFunction)PhtFile_exit, METH_VARARGS, 
	"Closes the file."}, 

	{"get_aperture", (PyCFunction)PhtFile_get_aperture, METH_VARARGS,
	"Get aperture radius in pixels"},
	{"get_aperture_dict", (PyCFunction)PhtFile_get_aperture_dict, METH_VARARGS,
	"Get aperture information as a dictionary"},
	{"get_star", (PyCFunction)PhtFile_get_star, METH_VARARGS,
	"Get star information as tuple (x, y, ref_id)"},
	{"get_star_dict", (PyCFunction)PhtFile_get_star_dict, METH_VARARGS,
	"Get star information as a dictionary"},
	{"get_mag", (PyCFunction)PhtFile_get_mag, METH_VARARGS,
	"Get magnitude for given star and aperture"},
	{"get_all", (PyCFunction)PhtFile_get_all, METH_VARARGS,
	"Get magnitude for all stars as a dictionary"},

    {NULL}  /* Sentinel */
};

PyTypeObject PhtFileType = {
    PyObject_HEAD_INIT(NULL)
    .tp_name = "cmpack.PhtFile",          /*tp_name*/
    .tp_basicsize = sizeof(PhtFile),           /*tp_basicsize*/
    .tp_dealloc = (destructor)PhtFile_dealloc,           /*tp_dealloc*/
    .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,        /*tp_flags*/
    .tp_doc = "Pht File (frame)",        /* tp_doc */
    .tp_methods = PhtFile_methods,           /* tp_methods */
    .tp_getset = PhtFile_getset,            /* tp_getset */
    .tp_init = (initproc)PhtFile_init,      /* tp_init */
    .tp_new = PhtFile_new                 /* tp_new */
};

PyObject *
PhtFile_FromHandle(CmpackPhtFile *f)
{
	PhtFile *pDoc = PyObject_New(PhtFile, &PhtFileType);
	if (pDoc) {
		pDoc->f = f;
		pDoc->p_valid = 0;
	}
	return (PyObject*)pDoc;
}


PyTypeObject *PhtFile_Type(void)
{
	return &PhtFileType;
}
