#define PY_SSIZE_T_CLEAN
#include <Python.h>

#include "cmunipack.h"
#include "ccdfileclass.h"

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

static void update_params(CCDFile* self)
{
	cmpack_ccd_get_params(self->f, CMPACK_CM_FORMAT | CMPACK_CM_IMAGE | CMPACK_CM_DATETIME |
		CMPACK_CM_JD | CMPACK_CM_EXPOSURE | CMPACK_CM_CCDTEMP | CMPACK_CM_FILTER | 
		CMPACK_CM_OBJECT | CMPACK_CM_OBSERVER | CMPACK_CM_LOCATION | CMPACK_CM_SUBFRAMES, 
		&self->p);
	self->p_valid = 1;
}

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

static void
CCDFile_dealloc(CCDFile* self)
{
	if (self->f) {
		cmpack_ccd_destroy(self->f);
		self->f = NULL;
		self->p_valid = 0;
	}
    self->ob_base.ob_type->tp_free((PyObject*)self);
}

static int
CCDFile_init(CCDFile *self, PyObject *args)
{
    if (!PyArg_ParseTuple(args, ""))
        return -1;

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

static PyObject *
CCDFile_open(CCDFile *self, PyObject *args)
{
    char *name; 
	int res, mode;
	CmpackCcdFile *f;

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

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

	return CCDFile_FromHandle(f);
}

static PyObject *
CCDFile_test(CCDFile *self, PyObject *args)
{
	char *filename;
	PyObject *res;

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

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

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

static PyObject *
CCDFile_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 *
CCDFile_close(CCDFile *self, PyObject *args)
{
	if (self->f) {
		int res = cmpack_ccd_close(self->f);
		self->f = NULL;
		self->p_valid = 0;
		if (res!=0)
			return cmpack_error(res);
	}
	Py_RETURN_NONE;
}

static PyObject *
CCDFile_copy(CCDFile *self, PyObject *args, PyObject *kwds)
{
	int res, channel = CMPACK_CHANNEL_DEFAULT;
	CmpackCcdFile *newf; 

    static char *kwlist[] = {"channel", NULL};
    if (!PyArg_ParseTupleAndKeywords(args, kwds, "|i", kwlist, &channel))
        return NULL;

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

	newf = cmpack_ccd_new();
	if (!newf) {
		PyErr_SetString(PyExc_MemoryError, "Memory allocation error.");
		return NULL;
	}
	cmpack_ccd_set_channel(self->f, channel);
	res = cmpack_ccd_copy(self->f, newf, NULL);
	if (res!=0) {
		cmpack_ccd_destroy(newf);
		return cmpack_error(res);
	}
	return CCDFile_FromHandle(newf);
}

static PyObject *
CCDFile_save(CCDFile *self, PyObject *args, PyObject *kwds)
{
	int res;
    char *to; 
	CmpackCcdFile *f;
	int channel = CMPACK_CHANNEL_DEFAULT;

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

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

	cmpack_ccd_set_channel(self->f, channel);
	res = cmpack_ccd_open(&f, to, CMPACK_OPEN_CREATE, 0);
	if (res==0) {
		res = cmpack_ccd_copy(self->f, f, NULL);
		cmpack_ccd_destroy(f);
	}
	PyMem_Free(to);
	if (res!=0)
		return cmpack_error(res);

	Py_RETURN_NONE;
}

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

	return PyLong_FromLong(self->p.format_id);
}

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

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

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

	return Py_BuildValue("ii", self->p.image_width, self->p.image_height);
}

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

	return PyLong_FromLong(self->p.image_format);
}

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

	return return_datetime(&self->p.date_time);
}

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

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

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

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

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

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

static PyObject *
CCDFile_filter(CCDFile *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 *
CCDFile_observer(CCDFile *self, void *data)
{
	if (!self->f)
		return err_closed();
	if (!self->p_valid)
		update_params(self);

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

static PyObject *
CCDFile_object_name(CCDFile *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 *
CCDFile_right_ascension(CCDFile *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 *
CCDFile_declination(CCDFile *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 *
CCDFile_object(CCDFile *self, void *data)
{
	PyObject *res = PyTuple_New(3);
	PyTuple_SET_ITEM(res, 0, CCDFile_object_name(self, data));
	PyTuple_SET_ITEM(res, 1, CCDFile_right_ascension(self, data));
	PyTuple_SET_ITEM(res, 2, CCDFile_declination(self, data));
	return res;
}

static PyObject *
CCDFile_location_name(CCDFile *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 *
CCDFile_longitude(CCDFile *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 *
CCDFile_latitude(CCDFile *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 *
CCDFile_location(CCDFile *self, void *data)
{
	PyObject *res = PyTuple_New(3);
	PyTuple_SET_ITEM(res, 0, CCDFile_location_name(self, data));
	PyTuple_SET_ITEM(res, 1, CCDFile_longitude(self, data));
	PyTuple_SET_ITEM(res, 2, CCDFile_latitude(self, data));
	return res;
}

static PyObject *
CCDFile_subframes(CCDFile *self, void *data)
{
	if (!self->f)
		return err_closed();
	if (!self->p_valid)
		update_params(self);
	return Py_BuildValue("ii", self->p.subframes_sum, self->p.subframes_avg);
}

static PyGetSetDef CCDFile_getset[] = {
	{"format_id", (getter)CCDFile_format_id, NULL, 
	"Format identifier"},
	{"format_name", (getter)CCDFile_format_name, NULL,
	"Format name"},
	{"image_size", (getter)CCDFile_image_size, NULL,
	"Image width and height in pixels"},
	{"image_format", (getter)CCDFile_image_format, NULL,
	"Image data format"},
	{"date_time", (getter)CCDFile_date_time, NULL,
	"Date and time of observation"},
	{"julian_date", (getter)CCDFile_julian_date, NULL,
	"Julian date of observation"},
	{"exposure", (getter)CCDFile_exposure, NULL,
	"Exposure duration in seconds"},
	{"ccd_temperature", (getter)CCDFile_ccdtemp, NULL,
	"CCD temperature in deg. C"},
	{"filter", (getter)CCDFile_filter, NULL,
	"Color filter designation"},
	{"observer", (getter)CCDFile_observer, NULL,
	"Observer's name"},
	{"object", (getter)CCDFile_object, NULL, 
	"Object designation and coordinates"},
	{"location", (getter)CCDFile_location, NULL,
	"Observatory designation and coordinates"},
	{"subframes",  (getter)CCDFile_subframes, NULL,
	"Number of subframes summed and averaged"},
	{NULL}
};

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

	{"close", (PyCFunction)CCDFile_close, METH_NOARGS,
	"Close file"},
	{"copy", (PyCFunction)CCDFile_copy, METH_KEYWORDS,
	"Make copy of the frame"},
	{"save", (PyCFunction)CCDFile_save, METH_KEYWORDS,
	"Save frame to a file"},

    {"__enter__", (PyCFunction)CCDFile_self, METH_NOARGS,  
	"Checks if the file is open."},
    {"__exit__",  (PyCFunction)CCDFile_exit, METH_VARARGS, 
	"Closes the file."}, 
    {NULL}  /* Sentinel */
};

PyTypeObject CCDFileType = {
    PyObject_HEAD_INIT(NULL)
    .tp_name = "cmpack.CCDFile",          /*tp_name*/
    .tp_basicsize = sizeof(CCDFile),           /*tp_basicsize*/
    .tp_dealloc = (destructor)CCDFile_dealloc,           /*tp_dealloc*/
    .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,        /*tp_flags*/
    .tp_doc = "CCD File (frame)",        /* tp_doc */
    .tp_methods = CCDFile_methods,           /* tp_methods */
    .tp_getset = CCDFile_getset,            /* tp_getset */
    .tp_init = (initproc)CCDFile_init,      /* tp_init */
    .tp_new = CCDFile_new                 /* tp_new */
};

PyObject *
CCDFile_FromHandle(CmpackCcdFile *f)
{
	CCDFile *pDoc = PyObject_New(CCDFile, &CCDFileType);
	if (pDoc) {
		pDoc->f = f;
		pDoc->p_valid = 0;
	}
	return (PyObject*)pDoc;
}

PyTypeObject *CCDFile_Type(void)
{
	return &CCDFileType;
}
