/*
 * ***** BEGIN GPL LICENSE BLOCK *****
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software  Foundation,
 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 *
 * Copyright (c) 2007 The Zdeno Ash Miklas
 *
 * This source file is part of VideoTexture library
 *
 * Contributor(s):
 *
 * ***** END GPL LICENSE BLOCK *****
 */

/** \file gameengine/VideoTexture/ImageBuff.cpp
 *  \ingroup bgevideotex
 */

// implementation

#include "PyObjectPlus.h"
#include <structmember.h>

#include "ImageBuff.h"
#include "Exception.h"
#include "ImageBase.h"
#include "FilterSource.h"

// use ImBuf API for image manipulation
extern "C" {
#include "IMB_imbuf_types.h"
#include "IMB_imbuf.h"
#include "bgl.h"
};

// default filter
FilterRGB24 defFilter;

// forward declaration;
extern PyTypeObject ImageBuffType;

static int ImageBuff_init(PyObject *pySelf, PyObject *args, PyObject *kwds)
{
	short width = -1;
	short height = -1;
	unsigned char color = 0;
	PyObject *py_scale = Py_False;
	ImageBuff *image;

	PyImage *self = reinterpret_cast<PyImage*>(pySelf);
	// create source object
	if (self->m_image != NULL) 
		delete self->m_image;
	image = new ImageBuff();
	self->m_image = image;

	if (PyArg_ParseTuple(args, "hh|bO!:ImageBuff", &width, &height, &color, &PyBool_Type, &py_scale)) 
	{
		// initialize image buffer
		image->setScale(py_scale == Py_True);
		image->clear(width, height, color);
	}
	else
	{
		// check if at least one argument was passed
		if (width != -1 || height != -1)
			// yes and they didn't match => it's an error
			return -1;
		// empty argument list is okay
		PyErr_Clear();
	}
	// initialization succeded
	return 0;

}

ImageBuff::~ImageBuff (void)
{
	if (m_imbuf)
		IMB_freeImBuf(m_imbuf);
}


// load image from buffer
void ImageBuff::load(unsigned char *img, short width, short height)
{
	// loading a new buffer implies to reset the imbuf if any, because the size may change
	if (m_imbuf)
	{
		IMB_freeImBuf(m_imbuf);
		m_imbuf = NULL;
	}
	// initialize image buffer
	init(width, height);
	// original size
	short orgSize[2] = {width, height};
	// is filter available
	if (m_pyfilter != NULL)
		// use it to process image
		convImage(*(m_pyfilter->m_filter), img, orgSize);
	else
		// otherwise use default filter
		convImage(defFilter, img, orgSize);
	// image is available
	m_avail = true;
}

void ImageBuff::clear(short width, short height, unsigned char color)
{
	unsigned char *p;
	int size;

	// loading a new buffer implies to reset the imbuf if any, because the size may change
	if (m_imbuf)
	{
		IMB_freeImBuf(m_imbuf);
		m_imbuf = NULL;
	}
	// initialize image buffer
	init(width, height);
	// the width/height may be different due to scaling
	size = (m_size[0] * m_size[1]);
	// initialize memory with color for all channels
	memset(m_image, color, size*4);
	// and change the alpha channel
	p = &((unsigned char*)m_image)[3];
	for (; size>0; size--)
	{
		*p = 0xFF;
		p += 4;
	}
	// image is available
	m_avail = true;
}

// img must point to a array of RGBA data of size width*height
void ImageBuff::plot(unsigned char *img, short width, short height, short x, short y, short mode)
{
	struct ImBuf *tmpbuf;

	if (m_size[0] == 0 || m_size[1] == 0 || width <= 0 || height <= 0)
		return;

	if (!m_imbuf) {
		// allocate most basic imbuf, we will assign the rect buffer on the fly
		m_imbuf = IMB_allocImBuf(m_size[0], m_size[1], 0, 0);
	}

	tmpbuf = IMB_allocImBuf(width, height, 0, 0);

	// assign temporarily our buffer to the ImBuf buffer, we use the same format
	tmpbuf->rect = (unsigned int*)img;
	m_imbuf->rect = m_image;
	IMB_rectblend(m_imbuf, m_imbuf, tmpbuf, NULL, NULL, NULL, 0, x, y, x, y, 0, 0, width, height, (IMB_BlendMode)mode, false);
	// remove so that MB_freeImBuf will free our buffer
	m_imbuf->rect = NULL;
	tmpbuf->rect = NULL;
	IMB_freeImBuf(tmpbuf);
}

void ImageBuff::plot(ImageBuff *img, short x, short y, short mode)
{
	if (m_size[0] == 0 || m_size[1] == 0 || img->m_size[0] == 0 || img->m_size[1] == 0)
		return;

	if (!m_imbuf) {
		// allocate most basic imbuf, we will assign the rect buffer on the fly
		m_imbuf = IMB_allocImBuf(m_size[0], m_size[1], 0, 0);
	}
	if (!img->m_imbuf) {
		// allocate most basic imbuf, we will assign the rect buffer on the fly
		img->m_imbuf = IMB_allocImBuf(img->m_size[0], img->m_size[1], 0, 0);
	}
	// assign temporarily our buffer to the ImBuf buffer, we use the same format
	img->m_imbuf->rect = img->m_image;
	m_imbuf->rect = m_image;
	IMB_rectblend(m_imbuf, m_imbuf, img->m_imbuf, NULL, NULL, NULL, 0, x, y, x, y, 0, 0, img->m_imbuf->x, img->m_imbuf->y, (IMB_BlendMode)mode, false);
	// remove so that MB_freeImBuf will free our buffer
	m_imbuf->rect = NULL;
	img->m_imbuf->rect = NULL;
}


// cast Image pointer to ImageBuff
inline ImageBuff *getImageBuff(PyImage *self)
{ return static_cast<ImageBuff *>(self->m_image); }


// python methods

static bool testPyBuffer(Py_buffer *buffer, int width, int height, unsigned int pixsize)
{
	if (buffer->itemsize != 1)
	{
		PyErr_SetString(PyExc_ValueError, "Buffer must be an array of bytes");
		return false;
	} 
	if (buffer->len != width*height*pixsize)
	{
		PyErr_SetString(PyExc_ValueError, "Buffer hasn't the correct size");
		return false;
	} 
	// multi dimension are ok as long as there is no hole in the memory
	Py_ssize_t size = buffer->itemsize;
	for (int i=buffer->ndim-1; i>=0 ; i--)
	{
		if (buffer->suboffsets != NULL && buffer->suboffsets[i] >= 0)
		{
			PyErr_SetString(PyExc_ValueError, "Buffer must be of one block");
			return false;
		}
		if (buffer->strides != NULL && buffer->strides[i] != size)
		{
			PyErr_SetString(PyExc_ValueError, "Buffer must be of one block");
			return false;
		}
		if (i > 0)
			size *= buffer->shape[i];
	}
	return true;
}

static bool testBGLBuffer(Buffer *buffer, int width, int height, unsigned int pixsize)
{
	unsigned int size = BGL_typeSize(buffer->type);
	for (int i=0; i<buffer->ndimensions; i++)
	{
		size *= buffer->dimensions[i];
	}
	if (size != width*height*pixsize)
	{
		PyErr_SetString(PyExc_ValueError, "Buffer hasn't the correct size");
		return false;
	} 
	return true;
}


// load image
static PyObject *load(PyImage *self, PyObject *args)
{
	// parameters: string image buffer, its size, width, height
	Py_buffer buffer;
	Buffer *bglBuffer;
	short width;
	short height;
	unsigned int pixSize;

	// calc proper buffer size
	// use pixel size from filter
	if (self->m_image->getFilter() != NULL)
		pixSize = self->m_image->getFilter()->m_filter->firstPixelSize();
	else
		pixSize = defFilter.firstPixelSize();

	// parse parameters
	if (!PyArg_ParseTuple(args, "s*hh:load", &buffer, &width, &height))
	{
		PyErr_Clear();
		// check if it is BGL buffer
		if (!PyArg_ParseTuple(args, "O!hh:load", &BGL_bufferType, &bglBuffer, &width, &height))
		{
			// report error
			return NULL;
		}
		else
		{
			if (testBGLBuffer(bglBuffer, width, height, pixSize))
			{
				try
				{
					// if correct, load image
					getImageBuff(self)->load((unsigned char*)bglBuffer->buf.asvoid, width, height);
				}
				catch (Exception & exp)
				{
					exp.report();
				}
			}
		}
	}
	else
	{
		// check if buffer size is correct
		if (testPyBuffer(&buffer, width, height, pixSize))
		{
			try 
			{
				// if correct, load image
				getImageBuff(self)->load((unsigned char*)buffer.buf, width, height);
			}
			catch (Exception & exp)
			{
				exp.report();
			}
		}
		PyBuffer_Release(&buffer);
	}
	if (PyErr_Occurred())
		return NULL;
	Py_RETURN_NONE;
}

static PyObject *plot(PyImage *self, PyObject *args)
{
	PyImage * other;
	Buffer* bglBuffer;
	Py_buffer buffer;
	//unsigned char * buff;
	//unsigned int buffSize;
	short width;
	short height;
	short x, y;
	short mode = IMB_BLEND_COPY;

	if (PyArg_ParseTuple(args, "s*hhhh|h:plot", &buffer, &width, &height, &x, &y, &mode))
	{
		// correct decoding, verify that buffer size is correct
		// we need a continuous memory buffer
		if (testPyBuffer(&buffer, width, height, 4))
		{
			getImageBuff(self)->plot((unsigned char*)buffer.buf, width, height, x, y, mode);
		}
		PyBuffer_Release(&buffer);
		if (PyErr_Occurred())
			return NULL;
		Py_RETURN_NONE;
	}
	PyErr_Clear();
	// try the other format
	if (PyArg_ParseTuple(args, "O!hh|h:plot", &ImageBuffType, &other, &x, &y, &mode))
	{
		getImageBuff(self)->plot(getImageBuff(other), x, y, mode);
		Py_RETURN_NONE;
	}
	PyErr_Clear();
	// try the last format (BGL buffer)
	if (!PyArg_ParseTuple(args, "O!hhhh|h:plot", &BGL_bufferType, &bglBuffer, &width, &height, &x, &y, &mode))
	{
		PyErr_SetString(PyExc_TypeError, "Expecting ImageBuff or Py buffer or BGL buffer as first argument; width, height next; postion x, y and mode as last arguments");
		return NULL;
	}
	if (testBGLBuffer(bglBuffer, width, height, 4))
	{
		getImageBuff(self)->plot((unsigned char*)bglBuffer->buf.asvoid, width, height, x, y, mode);
	}
	if (PyErr_Occurred())
		return NULL;
	Py_RETURN_NONE;
}

// methods structure
static PyMethodDef imageBuffMethods[] = {
	{"load", (PyCFunction)load, METH_VARARGS, "Load image from buffer"},
	{"plot", (PyCFunction)plot, METH_VARARGS, "update image buffer"},
	{NULL}
};
// attributes structure
static PyGetSetDef imageBuffGetSets[] = {
	// attributes from ImageBase class
	{(char*)"valid", (getter)Image_valid, NULL, (char*)"bool to tell if an image is available", NULL},
	{(char*)"image", (getter)Image_getImage, NULL, (char*)"image data", NULL},
	{(char*)"size", (getter)Image_getSize, NULL, (char*)"image size", NULL},
	{(char*)"scale", (getter)Image_getScale, (setter)Image_setScale, (char*)"fast scale of image (near neighbor)", NULL},
	{(char*)"flip", (getter)Image_getFlip, (setter)Image_setFlip, (char*)"flip image vertically", NULL},
	{(char*)"filter", (getter)Image_getFilter, (setter)Image_setFilter, (char*)"pixel filter", NULL},
	{NULL}
};


// define python type
PyTypeObject ImageBuffType = {
	PyVarObject_HEAD_INIT(NULL, 0)
	"VideoTexture.ImageBuff",   /*tp_name*/
	sizeof(PyImage),          /*tp_basicsize*/
	0,                         /*tp_itemsize*/
	(destructor)Image_dealloc, /*tp_dealloc*/
	0,                         /*tp_print*/
	0,                         /*tp_getattr*/
	0,                         /*tp_setattr*/
	0,                         /*tp_compare*/
	0,                         /*tp_repr*/
	0,                         /*tp_as_number*/
	0,                         /*tp_as_sequence*/
	0,                         /*tp_as_mapping*/
	0,                         /*tp_hash */
	0,                         /*tp_call*/
	0,                         /*tp_str*/
	0,                         /*tp_getattro*/
	0,                         /*tp_setattro*/
	&imageBufferProcs,         /*tp_as_buffer*/
	Py_TPFLAGS_DEFAULT,        /*tp_flags*/
	"Image source from image buffer",       /* tp_doc */
	0,		               /* tp_traverse */
	0,		               /* tp_clear */
	0,		               /* tp_richcompare */
	0,		               /* tp_weaklistoffset */
	0,		               /* tp_iter */
	0,		               /* tp_iternext */
	imageBuffMethods,    /* tp_methods */
	0,                   /* tp_members */
	imageBuffGetSets,          /* tp_getset */
	0,                         /* tp_base */
	0,                         /* tp_dict */
	0,                         /* tp_descr_get */
	0,                         /* tp_descr_set */
	0,                         /* tp_dictoffset */
	(initproc)ImageBuff_init,     /* tp_init */
	0,                         /* tp_alloc */
	Image_allocNew,           /* tp_new */
};

