#define NPY_NO_DEPRECATED_API NPY_1_7_API_VERSION
#define PY_SSIZE_T_CLEAN
#include <Python.h>
#include <numpy/arrayobject.h>

#include "ClusterReader.h"
#include "RawFileReader.h"

#include "arr_desc.h"
#include "cluster_reader.h"
#include "data_types.h"

/* static PyObject *clusterize(PyObject *Py_UNUSED(self), PyObject *args) {

    // // Create an uninitialized numpy array
    // PyArray_Descr *dtypeIn = cluster_dt();
    // PyArray_Descr *dtypeOut = cluster_analysis_dt();

    PyObject *cl_obj;
    Py_ssize_t csize = 0;
    if (!PyArg_ParseTuple(args, "nO", &csize,&cl_obj))
        return NULL;

    // Create a numpy array from the passed object, if possible numpy will
    // use the underlying buffer, otherwise it will create a copy, for example
    // if data type is different or we pass in a list. The
    // NPY_ARRAY_C_CONTIGUOUS flag ensures that we have contiguous memory.
    // function steals a reference to the data type so no need to deallocate
    /\* PyObject *cl_array = PyArray_FromArray( *\/
    /\*     (PyArrayObject *)cl_obj, cluster_dt(), NPY_ARRAY_C_CONTIGUOUS); *\/
    /\* if (cl_array == NULL) { *\/
    /\*     PyErr_SetString(PyExc_TypeError, *\/
    /\*                     "Could not convert first argument to numpy array.");
*\/
    /\*     return NULL; *\/
    /\* } *\/

    const int ndim = PyArray_NDIM((PyArrayObject *)cl_array);
    npy_intp *dims = PyArray_SHAPE((PyArrayObject *)cl_array);
    Py_ssize_t size = dims[0];

    Cluster *clusters = (Cluster *)(PyArray_DATA((PyArrayObject *)(cl_array)));
    PyObject *cl_analysis =
        PyArray_SimpleNewFromDescr(ndim, dims, cluster_analysis_dt());
    PyArray_FILLWBYTE((PyArrayObject *)cl_analysis, 0); // zero initialization

    // // Get a pointer to the array memory
    ClusterAnalysis *buf = PyArray_DATA((PyArrayObject *)cl_analysis);

    int nc = analyze_clusters(size, clusters, buf,csize);
    if (nc != size) {
        PyErr_SetString(PyExc_TypeError, "Parsed wrong size array!");
    }
    Py_DECREF(cl_array);
    return cl_analysis;
}

*/

// clusterize method
// static PyObject *ClusterFileReader_clusterize(ClusterFileReader *self,
// PyObject *args) {
static PyObject *clusterize(PyObject *Py_UNUSED(self), PyObject *args) {
    const int ndim = 1;

    Py_ssize_t size = 0;
    PyObject *data_obj;
    if (!PyArg_ParseTuple(args, "nO", &size, &data_obj)) {
        PyErr_SetString(PyExc_TypeError, "Could not parse args.");
        return NULL;
    }

    //

    // Create two numpy arrays from the passed objects, if possible numpy will
    // use the underlying buffer, otherwise it will create a copy, for example
    // if data type is different or we pass in a list. The
    // NPY_ARRAY_C_CONTIGUOUS flag ensures that we have contiguous memory.
    PyObject *data_array =
        PyArray_FROM_OTF(data_obj, NPY_INT32, NPY_ARRAY_C_CONTIGUOUS);
    int nx = 0, ny = 0;
    int32_t *data = NULL;

    // If parsing of a or b fails we throw an exception in Python
    if (data_array) {

        int ndim_data = PyArray_NDIM((PyArrayObject *)(data_array));
        npy_intp *data_shape = PyArray_SHAPE((PyArrayObject *)(data_array));

        // For the C++ function call we need pointers (or another C++ type/data
        // structure)

        data = (int32_t *)(PyArray_DATA((PyArrayObject *)(data_array)));

        /* for (int i=0; i< ndim_noise; i++) { */
        /*   printf("Dimension %d size %d pointer \n",i,noise_shape[i],
         * noise_map); */

        /* } */

        if (ndim_data == 2) {

            nx = data_shape[0];
            ny = data_shape[1];
            if (ny != 9) {
                PyErr_SetString(PyExc_TypeError, "Wrong data type.");
                //	printf("Data found size %d %d %d\n",nx,ny,ndim);
            }

        } else {
            PyErr_SetString(PyExc_TypeError, "Wrong data type.");
        }
    }

    // Create an uninitialized numpy array
    // npy_intp dims[] = {nx};
    // printf("%d %d\n",ndim,nx);
    npy_intp dims[] = {nx};
    PyObject *ca =
        PyArray_SimpleNewFromDescr(ndim, dims, cluster_analysis_dt());

    // printf("1\n");

    // Fill with zeros
    PyArray_FILLWBYTE((PyArrayObject *)ca, 0);

    // printf("2\n");
    //  Get a pointer to the array memory
    void *buf = PyArray_DATA((PyArrayObject *)ca);

    // Call the standalone C code to read clusters from file
    // Here goes the looping, removing frame numbers etc.

    // printf("3\n");
    int nc = analyze_clusters(nx, data, buf, size);

    // printf("aa %d %d\n",n_read, nx);
    /* if (nc != nx) { */
    /*     // resize the array to match the number of read photons */
    /*     // this will reallocate memory */

    /*     // create a new_shape struct on the stack */
    /*     PyArray_Dims new_shape; */

    /*     // reuse dims for the shape */
    /*     //dims[0] = n_read; */
    /*     new_shape.ptr = n_read; */
    /*     new_shape.len = 1; */

    /*     // resize the array to match the number of clusters read */
    /*     PyArray_Resize((PyArrayObject *)ca, &new_shape, 1, NPY_ANYORDER); */
    /* } */
    if (nc != nx) {
        printf("%d %d\n", nx, nc);
        PyErr_SetString(PyExc_TypeError, "Parsed wrong size array!");
    }
    Py_DECREF(data_array);
    return ca;
}

static PyObject *get_cluster_dt(PyObject *Py_UNUSED(self), PyObject *args) {
    if (!PyArg_ParseTuple(args, ""))
        return NULL;
    return (PyObject *)cluster_dt();
}

static PyObject *get_frame_header_dt(PyObject *Py_UNUSED(self),
                                     PyObject *args) {
    if (!PyArg_ParseTuple(args, ""))
        return NULL;
    return (PyObject *)frame_header_dt();
}

// Module docstring, shown as a part of help(creader)
static char module_docstring[] = "C functions to read cluster files";

// Module methods
static PyMethodDef creader_methods[] = {
    {"clusterize", clusterize, METH_VARARGS, "Do some stuff"},
    {"cluster_dt", get_cluster_dt, METH_VARARGS, "Do some stuff"},
    {"frame_header_dt", get_frame_header_dt, METH_VARARGS, "Do some stuff"},
    {NULL, NULL, 0, NULL} /* Sentinel */
};

// Module defenition
static struct PyModuleDef creader_def = {
    PyModuleDef_HEAD_INIT,
    "_creader",
    module_docstring,
    -1,
    creader_methods, // m_methods
    NULL,            // m_slots
    NULL,            // m_traverse
    NULL,            // m_clear
    NULL             // m_free
};

// Initialize module and add classes
PyMODINIT_FUNC PyInit__creader(void) {

    PyObject *m = PyModule_Create(&creader_def);
    if (m == NULL)
        return NULL;
    import_array();

    m = init_ClusterFileReader(m);
    m = init_RawFileReader(m);
    return m;
}
