#define PY_SSIZE_T_CLEAN
#include <Python.h>

#include "cmunipack.h"
#include "tableclass.h"
#include "lightcurveclass.h"
#include "trackcurveclass.h"
#include "airmasscurveclass.h"
#include "magdevcurveclass.h"
#include "apdevcurveclass.h"

PyObject *cmpack_error(int code);
PyObject *err_closed(void);

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

static void
Table_dealloc(Table* self)
{
	if (self->f) {
		cmpack_tab_destroy(self->f);
		self->f = NULL;
	}
    self->ob_base.ob_type->tp_free((PyObject*)self);
}

static int
Table_init(Table *self, PyObject *args)
{
	if (self->f) {
		cmpack_tab_destroy(self->f);
		self->f = NULL;
	}
    return 0;
}

static PyObject *
Table_row_count(Table *self, void *data)
{
	if (!self->f)
		return err_closed();

	return PyLong_FromLong(cmpack_tab_nrows(self->f));
}

static PyObject *
Table_col_count(Table *self, void *data)
{
	if (!self->f)
		return err_closed();

	return PyLong_FromLong(cmpack_tab_ncolumns(self->f));
}

static PyObject *
Table_columns(Table *self, void *data)
{
	int i, count;
	PyObject *res;
	CmpackTabColumn col;

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

	count = cmpack_tab_ncolumns(self->f);
	res = PyList_New(count);
	for (i=0; i<count; i++) {
		cmpack_tab_get_column(self->f, i, CMPACK_TM_NAME, &col);
		PyList_SET_ITEM(res, i, PyUnicode_FromString(col.name));
	}
	return res;
}

static PyObject *
Table_test(Table *self, PyObject *args)
{
	char *filename;
	PyObject *res;

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

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

static PyObject *
Table_load(Table *self, PyObject *args)
{
	int res;
	CmpackTable *f;
    char *filename; 

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

	res = cmpack_tab_load(&f, filename, 0);
	PyMem_Free(filename);
	if (res!=0)
		return cmpack_error(res);

	switch (cmpack_tab_get_type(f))
	{
	case CMPACK_TABLE_LCURVE_DIFF:
		return LightCurve_FromHandle(f);
	case CMPACK_TABLE_TRACKLIST:
		return TrackCurve_FromHandle(f);
	case CMPACK_TABLE_AIRMASS:
		return AirMassCurve_FromHandle(f);
	case CMPACK_TABLE_MAGDEV:
		return MagDevCurve_FromHandle(f);
	case CMPACK_TABLE_APERTURES:
		return ApDevCurve_FromHandle(f);
	default:
		return Table_FromHandle(f);
	}
}

static PyObject *
Table_save(Table *self, PyObject *args, PyObject *kwds)
{
	int res, flags;
	PyObject *save_header = Py_True;
    char *to; 

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

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

	flags = (save_header==Py_True ? CMPACK_SAVE_NO_HEADER : 0);
	res = cmpack_tab_save(self->f, to, flags, NULL, 0);
	PyMem_Free(to);
	if (res!=0)
		return cmpack_error(res);
	Py_RETURN_TRUE;
}

static PyObject *
Table_rewind(Table *self, PyObject *args)
{
	if (!self->f)
		return err_closed();

	if (cmpack_tab_rewind(self->f)==0)
		Py_RETURN_TRUE;
	Py_RETURN_FALSE;
}

static PyObject *
Table_next(Table *self, PyObject *args)
{
	if (!self->f)
		return err_closed();

	if (cmpack_tab_next(self->f)==0)
		Py_RETURN_TRUE;
	Py_RETURN_FALSE;
}

static PyObject *
Table_delete(Table *self, PyObject *args)
{
	if (!self->f)
		return err_closed();

	cmpack_tab_delete(self->f);
	Py_RETURN_NONE;
}

static PyObject *
Table_get_col(Table *self, PyObject *args)
{
	const char *name;
	int j;
	PyObject *res, *limit;
	CmpackTabColumn col;

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

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

	j = cmpack_tab_find_column(self->f, name);
	if (j<0)
		Py_RETURN_NONE;

	res = PyDict_New();
	if (res) {
		cmpack_tab_get_column(self->f, j, CMPACK_TM_NAME | CMPACK_TM_LIMITS | CMPACK_TM_NULVAL | CMPACK_TM_TYPE_PREC, &col);
		PyDict_SetItemString(res, "name", PyUnicode_FromString(col.name));
		PyDict_SetItemString(res, "type", PyLong_FromLong(col.dtype));
		switch (col.dtype)
		{
		case CMPACK_TYPE_INT:
			PyDict_SetItemString(res, "prec", PyLong_FromLong(col.prec));
			limit = PyTuple_New(2);
			if (limit) {
				PyTuple_SetItem(limit, 0, PyLong_FromLong((long)col.limit_min));
				PyTuple_SetItem(limit, 1, PyLong_FromLong((long)col.limit_max));
				PyDict_SetItemString(res, "limit", limit);
			}
			PyDict_SetItemString(res, "nulvalue", PyLong_FromLong((long)col.nul_value));
			break;
		case CMPACK_TYPE_DBL:
			PyDict_SetItemString(res, "prec", PyLong_FromLong(col.prec));
			limit = PyTuple_New(2);
			if (limit) {
				PyTuple_SetItem(limit, 0, PyFloat_FromDouble(col.limit_min));
				PyTuple_SetItem(limit, 1, PyFloat_FromDouble(col.limit_max));
				PyDict_SetItemString(res, "limit", limit);
			}
			PyDict_SetItemString(res, "nulvalue", PyFloat_FromDouble(col.nul_value));
			break;
		default:
			break;
		}
	}
	return res;
}

static PyObject *
get_value(CmpackTable *table, int j, CmpackType dtype)
{
	int iVal;
	double dVal;
	char *sVal;

	if (j>=0) {
		switch (dtype)
		{
		case CMPACK_TYPE_INT:
			if (cmpack_tab_gtdi(table, j, &iVal)==0)
				return PyLong_FromLong(iVal);
			break;
		case CMPACK_TYPE_DBL:
			if (cmpack_tab_gtdd(table, j, &dVal)==0)
				return PyFloat_FromDouble(dVal);
			break;
		case CMPACK_TYPE_STR:
			if (cmpack_tab_gtds(table, j, &sVal)==0) {
				PyObject *res = PyUnicode_FromString(sVal);
				cmpack_free(sVal);
				return res;
			}
			break;
		default:
			break;
		}
	}
	Py_RETURN_NONE;
}

static PyObject *
Table_get_val(Table *self, PyObject *args)
{
	const char *name;
	int j;
	CmpackTabColumn col;

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

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

	if (cmpack_tab_eof(self->f))
		Py_RETURN_NONE;

	j = cmpack_tab_find_column(self->f, name);
	if (j<0)
		Py_RETURN_NONE;

	if (cmpack_tab_get_column(self->f, j, CMPACK_TM_TYPE_PREC, &col)==0)
		return get_value(self->f, j, col.dtype);
	Py_RETURN_NONE;
}

static PyObject *
Table_get_all(Table *self, PyObject *args)
{
	PyObject *list, *res;
	int i, j, nrow, *items;
	CmpackType *types;
	CmpackTabColumn col;

	if (!PyArg_ParseTuple(args, "O!", &PyList_Type, &list))
		return NULL;

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

	Py_ssize_t ncol = PyList_Size(list);
	items = (int*)cmpack_malloc(ncol*sizeof(int));
	types = (CmpackType*)cmpack_calloc(ncol, sizeof(CmpackType));
	for (i=0; i<ncol; i++) {
		const char *str = PyUnicode_AsUTF8(PyList_GetItem(list, i));
		if (!str) {
			cmpack_free(items);
			PyErr_SetString(PyExc_TypeError, "List items must be strings");
			return NULL;
		}
		items[i] = cmpack_tab_find_column(self->f, str);
		if (items[i]>=0) {
			cmpack_tab_get_column(self->f, i, CMPACK_TM_TYPE_PREC, &col);
			types[i] = col.dtype;
		}
	}

	nrow = cmpack_tab_nrows(self->f);
	res = PyList_New(nrow);
	cmpack_tab_rewind(self->f);
	j = 0;
	while (!cmpack_tab_eof(self->f) && j<nrow) {
		PyObject *row = PyTuple_New(ncol);
		for (i=0; i<ncol; i++) 
			PyTuple_SET_ITEM(row, i, get_value(self->f, items[i], types[i]));
		PyList_SET_ITEM(res, j++, row);
		cmpack_tab_next(self->f);
	}
	return res;
}

static PyGetSetDef Table_getset[] = {
	{"row_count", (getter)Table_row_count, NULL,
	"Number of rows"},
	{"col_count", (getter)Table_col_count, NULL,
	"Number of columns"},
	{"columns", (getter)Table_columns, NULL,
	"List of column names"},

	{NULL}
};

static PyMethodDef Table_methods[] = {
	{"test", (PyCFunction)Table_test, METH_VARARGS | METH_STATIC,
	"Check if the file is a table"},
	{"load", (PyCFunction)Table_load, METH_VARARGS | METH_STATIC,
	"Load table from a file"},

	{"save", (PyCFunction)Table_save, METH_KEYWORDS,
	"Save table to a file"},

	{"get_col", (PyCFunction)Table_get_col, METH_VARARGS,
	"Get column attributes"},
	{"get_val", (PyCFunction)Table_get_val, METH_VARARGS,
	"Get value from active row"},
	{"get_all", (PyCFunction)Table_get_all, METH_VARARGS,
	"Get all values from specified columns"},

	{"rewind", (PyCFunction)Table_rewind, METH_NOARGS,
	"Activate the first frame in the set"},
	{"next", (PyCFunction)Table_next, METH_NOARGS,
	"Activate the frame after the active"},
	{"delete", (PyCFunction)Table_delete, METH_NOARGS,
	"Delete the active frame"},

    {NULL}  /* Sentinel */
};

PyTypeObject TableType = {
    PyObject_HEAD_INIT(NULL)
	.tp_name = "cmpack.Table",        
	.tp_doc = "Table",
	.tp_basicsize = sizeof(Table),
    .tp_dealloc = (destructor)Table_dealloc,     
    .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,       
    .tp_methods = Table_methods,        
    .tp_getset = Table_getset,          
    .tp_init = (initproc)Table_init,     
    .tp_new = Table_new                 
};

PyObject *
Table_FromHandle(CmpackTable *f)
{
	Table *pDoc = PyObject_New(Table, &TableType);
	if (pDoc) {
		pDoc->f = f;
	}
	return (PyObject*)pDoc;
}

PyTypeObject *Table_Type(void)
{
	return &TableType;
}
