/* Created By: Virgil Dupras
 * Created On: 2010-01-31
 * Copyright 2014 Hardcoded Software (http://www.hardcoded.net)
 *
 * This software is licensed under the "BSD" License as described in the "LICENSE" file, 
 * which should be included with this package. The terms are also available at 
 * http://www.hardcoded.net/licenses/bsd_license
**/

#define PY_SSIZE_T_CLEAN
#include "Python.h"

/* It seems like MS VC defines min/max already */
#ifndef _MSC_VER
static int
max(int a, int b)
{
    return b > a ? b : a;
}

static int
min(int a, int b)
{
    return b < a ? b : a;
}
#endif

static PyObject*
getblock(PyObject *image, int width, int height)
{
    int pixel_count, red, green, blue, bytes_per_line;
    PyObject *pred, *pgreen, *pblue;
    PyObject *result;
    
    red = green = blue = 0;
    pixel_count = width * height;
    if (pixel_count) {
        PyObject *sipptr, *bits_capsule, *pi;
        char *s;
        int i;
        
        pi = PyObject_CallMethod(image, "bytesPerLine", NULL);
        bytes_per_line = PyLong_AsLong(pi);
        Py_DECREF(pi);
        
        sipptr = PyObject_CallMethod(image, "bits", NULL);
        bits_capsule = PyObject_CallMethod(sipptr, "ascapsule", NULL);
        Py_DECREF(sipptr);
        s = (char *)PyCapsule_GetPointer(bits_capsule, NULL);
        Py_DECREF(bits_capsule);
        /* Qt aligns all its lines on 32bit, which means that if the number of bytes per
         * line for image is not divisible by 4, there's going to be crap inserted in "s"
         * We have to take this into account when calculating offsets
        **/
        for (i=0; i<height; i++) {
            int j;
            for (j=0; j<width; j++) {
                int offset;
                unsigned char r, g, b;
                
                offset = i * bytes_per_line + j * 3;
                r = s[offset];
                g = s[offset + 1];
                b = s[offset + 2];
                red += r;
                green += g;
                blue += b;
            }
        }
        
        red /= pixel_count;
        green /= pixel_count;
        blue /= pixel_count;
    }
    
    pred = PyLong_FromLong(red);
    pgreen = PyLong_FromLong(green);
    pblue = PyLong_FromLong(blue);
    result = PyTuple_Pack(3, pred, pgreen, pblue);
    Py_DECREF(pred);
    Py_DECREF(pgreen);
    Py_DECREF(pblue);
    
    return result;
}

/* block_getblocks(QImage image, int block_count_per_side) -> [(int r, int g, int b), ...]
 *
 * Compute blocks out of `image`. Note the use of min/max when compes the time of computing widths
 * and heights and positions. This is to cover the case where the width or height of the image is
 * smaller than `block_count_per_side`. In these cases, blocks will be, of course, 1 pixel big. But
 * also, because all compared block lists are required to be of the same size, any block that has
 * no pixel to be assigned to will simply be assigned the last pixel. This is why we have
 * min(..., height-block_height-1) and stuff like that.
**/
static PyObject*
block_getblocks(PyObject *self, PyObject *args)
{
    int block_count_per_side, width, height, block_width, block_height, ih;
    PyObject *image;
    PyObject *pi;
    PyObject *result;
    
    if (!PyArg_ParseTuple(args, "Oi", &image, &block_count_per_side)) {
        return NULL;
    }
    
    pi = PyObject_CallMethod(image, "width", NULL);
    width = PyLong_AsLong(pi);
    Py_DECREF(pi);
    pi = PyObject_CallMethod(image, "height", NULL);
    height = PyLong_AsLong(pi);
    Py_DECREF(pi);
    
    if (!(width && height)) {
        return PyList_New(0);
    }
    
    block_width = max(width / block_count_per_side, 1);
    block_height = max(height / block_count_per_side, 1);
    
    result = PyList_New(block_count_per_side * block_count_per_side);
    if (result == NULL) {
        return NULL;
    }
    
    for (ih=0; ih<block_count_per_side; ih++) {
        int top, iw;
        top = min(ih*block_height, height-block_height-1);
        for (iw=0; iw<block_count_per_side; iw++) {
            int left;
            PyObject *pcrop;
            PyObject *pblock;
            
            left = min(iw*block_width, width-block_width-1);
            pcrop = PyObject_CallMethod(image, "copy", "iiii", left, top, block_width, block_height);
            if (pcrop == NULL) {
                Py_DECREF(result);
                return NULL;
            }
            pblock = getblock(pcrop, block_width, block_height);
            Py_DECREF(pcrop);
            if (pblock == NULL) {
                Py_DECREF(result);
                return NULL;
            }
            PyList_SET_ITEM(result, ih*block_count_per_side+iw, pblock);
        }
    }
    
    return result;
}

static PyMethodDef BlockMethods[] = {
    {"getblocks",  block_getblocks, METH_VARARGS, ""},
    {NULL, NULL, 0, NULL} /* Sentinel */
};

static struct PyModuleDef BlockDef = {
    PyModuleDef_HEAD_INIT,
    "_block_qt",
    NULL,
    -1,
    BlockMethods,
    NULL,
    NULL,
    NULL,
    NULL
};

PyObject *
PyInit__block_qt(void)
{
    PyObject *m = PyModule_Create(&BlockDef);
    if (m == NULL) {
        return NULL;
    }
    return m;
}