/*
   +----------------------------------------------------------------------+
   | PHP Version 5 / Imagick	                                          |
   +----------------------------------------------------------------------+
   | Copyright (c) 2006-2013 Mikko Koppanen, Scott MacVicar               |
   | ImageMagick (c) ImageMagick Studio LLC                               |
   +----------------------------------------------------------------------+
   | This source file is subject to version 3.01 of the PHP license,      |
   | that is bundled with this package in the file LICENSE, and is        |
   | available through the world-wide-web at the following url:           |
   | http://www.php.net/license/3_01.txt                                  |
   | If you did not receive a copy of the PHP license and are unable to   |
   | obtain it through the world-wide-web, please send a note to          |
   | license@php.net so we can mail you a copy immediately.               |
   +----------------------------------------------------------------------+
   | Author: Mikko Kopppanen <mkoppanen@php.net>                          |
   |         Scott MacVicar <scottmac@php.net>                            |
   +----------------------------------------------------------------------+
*/

#include "php_imagick.h"
#include "php_imagick_defs.h"
#include "php_imagick_macros.h"
#include "php_imagick_helpers.h"

#if MagickLibVersion <= 0x628
static
long s_count_pixeliterator_rows(php_imagickpixeliterator_object *internpix)
{
	long rows = 0, tmp;
	PixelWand **row;
	PixelResetIterator(internpix->pixel_iterator);

	while ((row = PixelGetNextIteratorRow(internpix->pixel_iterator, &tmp)) != NULL)
		rows++;

	return rows;
}
#endif

void php_imagick_pixel_iterator_new (PixelIterator *pixel_it, zval *return_value TSRMLS_DC)
{
	php_imagickpixeliterator_object *internpix;

	object_init_ex(return_value, php_imagickpixeliterator_sc_entry);
	internpix = Z_IMAGICKPIXELITERATOR_P(return_value);

	internpix->pixel_iterator = pixel_it;
	internpix->initialized = 1;

#if MagickLibVersion <= 0x628
	internpix->rows = s_count_pixeliterator_rows (internpix);
#endif
}

/* {{{ proto ImagickPixelIterator ImagickPixelIterator::__construct(Imagick source)
   The ImagickPixelIterator constructor
*/
PHP_METHOD(ImagickPixelIterator, __construct)
{
	zval *magick_object;
	php_imagickpixeliterator_object *internpix;
	php_imagick_object *intern;

	/* Parse parameters given to function */
	if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "O|llll", &magick_object, php_imagick_sc_entry) == FAILURE) {
		RETURN_THROWS();
	}

	internpix = Z_IMAGICKPIXELITERATOR_P(getThis());
	intern = Z_IMAGICK_P(magick_object);

	if (!intern->magick_wand) {
		php_imagick_throw_exception (IMAGICKPIXELITERATOR_CLASS, "Invalid Imagick object passed" TSRMLS_CC);
		RETURN_THROWS();
	}

	IMAGICK_NOT_EMPTY(intern);

	internpix->pixel_iterator = NewPixelIterator(intern->magick_wand);

	if (!internpix->pixel_iterator) {
		php_imagick_throw_exception (IMAGICKPIXELITERATOR_CLASS, "Can not allocate ImagickPixelIterator" TSRMLS_CC);
		RETURN_THROWS();
	}

#if MagickLibVersion <= 0x628
	internpix->rows = s_count_pixeliterator_rows (internpix);
#endif

	internpix->initialized = 1;
	RETURN_TRUE;
}
/* }}} */

/* {{{ proto void ImagickPixelIterator::rewind()
	Resets the pixel iterator.  Use it in conjunction with PixelGetNextIteratorRow() to iterate over all the pixels in a pixel container.
*/
PHP_METHOD(ImagickPixelIterator, rewind)
{
	php_imagickpixeliterator_object *internpix;

	if (zend_parse_parameters_none() == FAILURE) {
		RETURN_THROWS();
	}
	(void)return_value; // fix -Werror=unused-parameter

	internpix = Z_IMAGICKPIXELITERATOR_P(getThis());

	if (!internpix->initialized) {
		php_imagick_throw_exception (IMAGICKPIXELITERATOR_CLASS, "ImagickPixelIterator is not initialized correctly" TSRMLS_CC);
		RETURN_THROWS();
	}

	PixelResetIterator(internpix->pixel_iterator);
#if MagickLibVersion <= 0x628
	internpix->iterator_position = 0;
#endif
}
/* }}} */

/* {{{ proto bool ImagickPixelIterator::resetIterator()
	Resets the pixel iterator.  Use it in conjunction with PixelGetNextIteratorRow() to iterate over all the pixels in a pixel container.
*/
PHP_METHOD(ImagickPixelIterator, resetIterator)
{
	php_imagickpixeliterator_object *internpix;

	if (zend_parse_parameters_none() == FAILURE) {
		RETURN_THROWS();
	}

	internpix = Z_IMAGICKPIXELITERATOR_P(getThis());

	if (!internpix->initialized) {
		php_imagick_throw_exception (IMAGICKPIXELITERATOR_CLASS, "ImagickPixelIterator is not initialized correctly" TSRMLS_CC);
		RETURN_THROWS();
	}

	PixelResetIterator(internpix->pixel_iterator);
#if MagickLibVersion <= 0x628
	internpix->iterator_position = 0;
#endif

	RETURN_TRUE;
}
/* }}} */

/* {{{ proto bool ImagickPixelIterator::syncIterator()
	Syncs the pixel iterator.
*/
PHP_METHOD(ImagickPixelIterator, syncIterator)
{
	php_imagickpixeliterator_object *internpix;

	if (zend_parse_parameters_none() == FAILURE) {
		RETURN_THROWS();
	}

	internpix = Z_IMAGICKPIXELITERATOR_P(getThis());

	if (!internpix->initialized) {
		php_imagick_throw_exception (IMAGICKPIXELITERATOR_CLASS, "ImagickPixelIterator is not initialized correctly" TSRMLS_CC);
		RETURN_THROWS();
	}

	PixelSyncIterator(internpix->pixel_iterator);
	RETURN_TRUE;
}
/* }}} */

/* {{{ proto bool ImagickPixelIterator::setIteratorFirstRow()
	Sets the pixel iterator to the first pixel row.
*/
PHP_METHOD(ImagickPixelIterator, setIteratorFirstRow)
{
	php_imagickpixeliterator_object *internpix;

	if (zend_parse_parameters_none() == FAILURE) {
		RETURN_THROWS();
	}

	internpix = Z_IMAGICKPIXELITERATOR_P(getThis());

	if (!internpix->initialized) {
		php_imagick_throw_exception (IMAGICKPIXELITERATOR_CLASS, "ImagickPixelIterator is not initialized correctly" TSRMLS_CC);
		RETURN_THROWS();
	}

	PixelSetFirstIteratorRow(internpix->pixel_iterator);
#if MagickLibVersion <= 0x628
	internpix->iterator_position = 0;
#endif
	RETURN_TRUE;
}
/* }}} */

/* {{{ proto bool ImagickPixelIterator::setIteratorLastRow()
	Sets the pixel iterator to the last pixel row.
*/
PHP_METHOD(ImagickPixelIterator, setIteratorLastRow)
{
	php_imagickpixeliterator_object *internpix;

	if (zend_parse_parameters_none() == FAILURE) {
		RETURN_THROWS();
	}

	internpix = Z_IMAGICKPIXELITERATOR_P(getThis());

	if (!internpix->initialized) {
		php_imagick_throw_exception (IMAGICKPIXELITERATOR_CLASS, "ImagickPixelIterator is not initialized correctly" TSRMLS_CC);
		RETURN_THROWS();
	}

	PixelSetLastIteratorRow(internpix->pixel_iterator);
#if MagickLibVersion <= 0x628
	internpix->iterator_position = (internpix->rows - 1);
#endif
	RETURN_TRUE;
}
/* }}} */

/* {{{ proto ImagickPixelIterator ImagickPixelIterator::getPixelIterator(Imagick source)
	Returns a new pixel iterator, static method. This is equivalent to Imagick::getPixelIterator
	which probably ought to be used in preference to this method.
*/
PHP_METHOD(ImagickPixelIterator, getPixelIterator)
{
	PixelIterator *pixel_it;
	zval *magick_object;
	php_imagick_object *intern;

	/* Parse parameters given to function */
	if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "O", &magick_object, php_imagick_sc_entry) == FAILURE) {
		RETURN_THROWS();
	}

	intern = Z_IMAGICK_P(magick_object);

	if (!intern->magick_wand) {
		php_imagick_throw_exception (IMAGICKPIXELITERATOR_CLASS, "Invalid Imagick object passed" TSRMLS_CC);
		RETURN_THROWS();
	}

	IMAGICK_NOT_EMPTY(intern);

	pixel_it = NewPixelIterator (intern->magick_wand);
	if (!pixel_it) {
		php_imagick_throw_exception (IMAGICKPIXELITERATOR_CLASS, "Can not allocate ImagickPixelIterator" TSRMLS_CC);
		RETURN_THROWS();
	}

	php_imagick_pixel_iterator_new (pixel_it, return_value TSRMLS_CC);
}
/* }}} */

/* {{{ proto ImagickPixelIterator ImagickPixelIterator::newPixelIterator(Imagick source)
	Returns a new pixel iterator.
*/
PHP_METHOD(ImagickPixelIterator, newPixelIterator)
{
	PixelIterator *pixel_it;
	zval *magick_object;
	php_imagickpixeliterator_object *internpix;
	php_imagick_object *intern;

	/* Parse parameters given to function */
	if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "O", &magick_object, php_imagick_sc_entry) == FAILURE) {
		RETURN_THROWS();
	}

	IMAGICK_METHOD_DEPRECATED_USE_INSTEAD("ImagickPixelIterator", "newPixelIterator", "ImagickPixelIterator", "getPixelIterator");

	internpix = Z_IMAGICKPIXELITERATOR_P(getThis() );
	intern = Z_IMAGICK_P(magick_object);

	if (!intern->magick_wand) {
		php_imagick_throw_exception (IMAGICKPIXELITERATOR_CLASS, "Invalid Imagick object passed" TSRMLS_CC);
		RETURN_THROWS();
	}

	IMAGICK_NOT_EMPTY(intern);

	pixel_it = NewPixelIterator(intern->magick_wand);

	if (!pixel_it) {
		php_imagick_throw_exception (IMAGICKPIXELITERATOR_CLASS, "Can not allocate ImagickPixelIterator" TSRMLS_CC);
		RETURN_THROWS();
	}

	if (internpix->initialized && internpix->pixel_iterator)
		DestroyPixelIterator (internpix->pixel_iterator);

	internpix->pixel_iterator = pixel_it;
#if MagickLibVersion <= 0x628
	internpix->rows = s_count_pixeliterator_rows (internpix);
#endif

	internpix->initialized = 1;
	RETURN_TRUE;
}
/* }}} */

/* {{{ proto ImagickPixelIterator ImagickPixelIterator::getPixelRegionIterator(Imagick source, int x, int y, int columns, int rows)
	Returns a new pixel region iterator, static method. This is equivalent to the non-static
	Imagick::getPixelRegionIterator which ought to be used in preference to this method.
*/
PHP_METHOD(ImagickPixelIterator, getPixelRegionIterator)
{
	PixelIterator *pixel_it;
	zval *magick_object;
	php_imagick_object *intern;
	long x, y, columns, rows;

	/* Parse parameters given to function */
	if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "Ollll", &magick_object, php_imagick_sc_entry, &x, &y, &columns, &rows) == FAILURE) {
		RETURN_THROWS();
	}

	intern = Z_IMAGICK_P(magick_object);

	if (!intern->magick_wand) {
		php_imagick_throw_exception (IMAGICKPIXELITERATOR_CLASS, "Invalid Imagick object passed" TSRMLS_CC);
		RETURN_THROWS();
	}

	IMAGICK_NOT_EMPTY(intern);

	pixel_it = NewPixelRegionIterator(intern->magick_wand, x, y, columns, rows);
	if (!pixel_it) {
		php_imagick_throw_exception (IMAGICKPIXELITERATOR_CLASS, "Can not allocate ImagickPixelIterator" TSRMLS_CC);
		RETURN_THROWS();
	}

	php_imagick_pixel_iterator_new (pixel_it, return_value TSRMLS_CC);
}
/* }}} */

/* {{{ proto bool ImagickPixelIterator::newPixelRegionIterator(Imagick source, int x, int y, int columns, int rows)
	Returns a new pixel iterator.
*/
PHP_METHOD(ImagickPixelIterator, newPixelRegionIterator)
{
	PixelIterator *pixel_it;
	zval *magick_object;
	php_imagickpixeliterator_object *internpix;
	php_imagick_object *intern;
	long x, y, columns, rows;

	/* Parse parameters given to function */
	if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "Ollll", &magick_object, php_imagick_sc_entry, &x, &y, &columns, &rows) == FAILURE) {
		RETURN_THROWS();
	}

	IMAGICK_METHOD_DEPRECATED_USE_INSTEAD("ImagickPixelIterator", "newPixelRegionIterator", "ImagickPixelIterator", "getPixelRegionIterator");

	internpix = Z_IMAGICKPIXELITERATOR_P(getThis());
	intern = Z_IMAGICK_P(magick_object);

	if (!intern->magick_wand) {
		php_imagick_throw_exception (IMAGICKPIXELITERATOR_CLASS, "Invalid Imagick object passed" TSRMLS_CC);
		RETURN_THROWS();
	}

	IMAGICK_NOT_EMPTY(intern);

	pixel_it = NewPixelRegionIterator(intern->magick_wand, x, y, columns, rows);

	if (!pixel_it) {
		php_imagick_throw_exception (IMAGICKPIXELITERATOR_CLASS, "Can not allocate ImagickPixelIterator" TSRMLS_CC);
		RETURN_THROWS();
	}

	if (internpix->initialized && internpix->pixel_iterator)
		DestroyPixelIterator (internpix->pixel_iterator);

	internpix->pixel_iterator = pixel_it;

#if MagickLibVersion <= 0x628
	internpix->rows = s_count_pixeliterator_rows (internpix);
#endif
	internpix->initialized = 1;
	RETURN_TRUE;
}
/* }}} */

/* {{{ proto int ImagickPixelIterator::getIteratorRow()
	Returns the crurent pixel iterator row.
*/
PHP_METHOD(ImagickPixelIterator, getIteratorRow)
{
	php_imagickpixeliterator_object *internpix;

	if (zend_parse_parameters_none() == FAILURE) {
		RETURN_THROWS();
	}
	internpix = Z_IMAGICKPIXELITERATOR_P(getThis());

	if (!internpix->initialized) {
		php_imagick_throw_exception (IMAGICKPIXELITERATOR_CLASS, "ImagickPixelIterator is not initialized correctly" TSRMLS_CC);
		RETURN_THROWS();
	}

#if MagickLibVersion <= 0x628
	ZVAL_LONG(return_value, internpix->iterator_position);
#else
	ZVAL_LONG(return_value, (long) PixelGetIteratorRow(internpix->pixel_iterator));
#endif
}
/* }}} */

/* {{{ proto bool ImagickPixelIterator::setIteratorRow(int row)
	Set the pixel iterator row.
*/
PHP_METHOD(ImagickPixelIterator, setIteratorRow)
{
	php_imagickpixeliterator_object *internpix;
	MagickBooleanType status;
	im_long row;

	/* Parse parameters given to function */
	if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "l", &row) == FAILURE) {
		RETURN_THROWS();
	}

	internpix = Z_IMAGICKPIXELITERATOR_P(getThis());

	if (!internpix->initialized) {
		php_imagick_throw_exception (IMAGICKPIXELITERATOR_CLASS, "ImagickPixelIterator is not initialized correctly" TSRMLS_CC);
		RETURN_THROWS();
	}

	status = PixelSetIteratorRow(internpix->pixel_iterator, row);

	if (status == MagickFalse) {
		php_imagick_convert_imagickpixeliterator_exception(internpix->pixel_iterator, "Unable to set iterator row" TSRMLS_CC);
		RETURN_THROWS();
	}
#if MagickLibVersion <= 0x628
	internpix->iterator_position = row;
#endif
	RETURN_TRUE;
}
/* }}} */

static
void s_pixelwands_to_zval (PixelWand **wand_array, unsigned long num_wands, zval *return_value TSRMLS_DC)
{
	php_imagickpixel_object *internp;
#if PHP_VERSION_ID >= 70000
	zval obj;
#else
	zval *obj;
#endif

	unsigned long i;

	array_init(return_value);

	for (i = 0; i < num_wands; i++) {
#if PHP_VERSION_ID >= 70000
		object_init_ex(&obj, php_imagickpixel_sc_entry);
		internp = Z_IMAGICKPIXEL_P(&obj);
#else 
		MAKE_STD_ZVAL(obj);
		object_init_ex(obj, php_imagickpixel_sc_entry);
		internp = Z_IMAGICKPIXEL_P(obj);
#endif

		internp->initialized_via_iterator = 1;

		php_imagick_replace_pixelwand(internp, wand_array[i]);
#if PHP_VERSION_ID >= 70000
		add_next_index_zval(return_value, &obj);
#else
		add_next_index_zval(return_value, obj);
#endif
	}

}

/* {{{ proto array ImagickPixelIterator::getPreviousIteratorRow()
	Returns the previous row as an array of pixel wands from the pixel iterator.
*/
PHP_METHOD(ImagickPixelIterator, getPreviousIteratorRow)
{
	php_imagickpixeliterator_object *internpix;
	PixelWand **wand_array;
	size_t num_wands;

	if (zend_parse_parameters_none() == FAILURE) {
		RETURN_THROWS();
	}

	internpix = Z_IMAGICKPIXELITERATOR_P(getThis());

	if (!internpix->initialized) {
		php_imagick_throw_exception (IMAGICKPIXELITERATOR_CLASS, "ImagickPixelIterator is not initialized correctly" TSRMLS_CC);
		RETURN_THROWS();
	}

	wand_array = PixelGetPreviousIteratorRow(internpix->pixel_iterator, &num_wands);

#if MagickLibVersion <= 0x628
	if (internpix->iterator_position > 0) {
		internpix->iterator_position--;
	}
#endif

	if (!wand_array) {
		RETURN_NULL();
	}

	s_pixelwands_to_zval (wand_array, num_wands, return_value TSRMLS_CC);
}
/* }}} */


/* {{{ proto array ImagickPixelIterator::getCurrentIteratorRow()
	Returns the current row as an array of pixel wands from the pixel iterator.
*/
PHP_METHOD(ImagickPixelIterator, getCurrentIteratorRow)
{
	php_imagickpixeliterator_object *internpix;
	PixelWand **wand_array;
	size_t num_wands;

	if (zend_parse_parameters_none() == FAILURE) {
		RETURN_THROWS();
	}

	internpix = Z_IMAGICKPIXELITERATOR_P(getThis());

	if (!internpix->initialized) {
		php_imagick_throw_exception (IMAGICKPIXELITERATOR_CLASS, "ImagickPixelIterator is not initialized correctly" TSRMLS_CC);
		RETURN_THROWS();
	}

#if MagickLibVersion <= 0x628
	if (internpix->iterator_position >= internpix->rows) {
		RETURN_NULL();
	} else if (internpix->iterator_position != 0) {
		long tmp;
		(void)PixelGetPreviousIteratorRow(internpix->pixel_iterator, &tmp);
	}
	wand_array = PixelGetNextIteratorRow(internpix->pixel_iterator, &num_wands);
#else
	wand_array = PixelGetCurrentIteratorRow(internpix->pixel_iterator, &num_wands);
#endif

	if (!wand_array) {
		RETURN_NULL();
	}

	s_pixelwands_to_zval (wand_array, num_wands, return_value TSRMLS_CC);
}
/* }}} */

/* {{{ proto void ImagickPixelIterator::next()
	Returns the next row as an array of pixel wands from the pixel iterator.
*/
PHP_METHOD(ImagickPixelIterator, next)
{
	php_imagickpixeliterator_object *internpix;
	size_t num_wands;

	if (zend_parse_parameters_none() == FAILURE) {
		RETURN_THROWS();
	}
	(void)return_value; // fix -Werror=unused-parameter

	internpix = Z_IMAGICKPIXELITERATOR_P(getThis());

	if (!internpix->initialized) {
		php_imagick_throw_exception (IMAGICKPIXELITERATOR_CLASS, "ImagickPixelIterator is not initialized correctly" TSRMLS_CC);
		RETURN_THROWS();
	}

	PixelGetNextIteratorRow(internpix->pixel_iterator, &num_wands);

#if MagickLibVersion <= 0x628
	internpix->iterator_position++;
#endif
}
/* }}} */

/* {{{ proto array ImagickPixelIterator::getNextIteratorRow()
	Returns the next row as an array of pixel wands from the pixel iterator.
*/
PHP_METHOD(ImagickPixelIterator, getNextIteratorRow)
{
	php_imagickpixeliterator_object *internpix;
	PixelWand **wand_array;
	size_t num_wands;

	if (zend_parse_parameters_none() == FAILURE) {
		RETURN_THROWS();
	}

	internpix = Z_IMAGICKPIXELITERATOR_P(getThis());

	if (!internpix->initialized) {
		php_imagick_throw_exception (IMAGICKPIXELITERATOR_CLASS, "ImagickPixelIterator is not initialized correctly" TSRMLS_CC);
		RETURN_THROWS();
	}

	wand_array = PixelGetNextIteratorRow(internpix->pixel_iterator, &num_wands);

#if MagickLibVersion <= 0x628
	internpix->iterator_position++;
#endif

	if (!wand_array) {
		RETURN_NULL();
	}

	s_pixelwands_to_zval (wand_array, num_wands, return_value TSRMLS_CC);
}
/* }}} */

/* {{{ proto bool ImagickPixelIterator::clear()
	Clear resources associated with a PixelIterator.
*/
PHP_METHOD(ImagickPixelIterator, clear)
{
	php_imagickpixeliterator_object *internpix;

	if (zend_parse_parameters_none() == FAILURE) {
		RETURN_THROWS();
	}
	internpix = Z_IMAGICKPIXELITERATOR_P(getThis());

	if (!internpix->initialized) {
		php_imagick_throw_exception (IMAGICKPIXELITERATOR_CLASS, "ImagickPixelIterator is not initialized correctly" TSRMLS_CC);
		RETURN_THROWS();
	}
/* Assertion failure with earlier versions */
#if MagickLibVersion <= 0x628
	RETURN_TRUE;
#else
	ClearPixelIterator(internpix->pixel_iterator);
	RETURN_TRUE;
#endif
}
/* }}} */

/* {{{ proto bool ImagickPixelIterator::valid()
	The if the current iterator row is valid
*/
PHP_METHOD(ImagickPixelIterator, valid)
{
	php_imagickpixeliterator_object *internpix;

	if (zend_parse_parameters_none() == FAILURE) {
		RETURN_THROWS();
	}

	internpix = Z_IMAGICKPIXELITERATOR_P(getThis());

	if (!internpix->initialized) {
		php_imagick_throw_exception (IMAGICKPIXELITERATOR_CLASS, "ImagickPixelIterator is not initialized correctly" TSRMLS_CC);
		RETURN_THROWS();
	}
#if MagickLibVersion <= 0x628
	if (internpix->iterator_position < internpix->rows) {
		RETURN_TRUE;
	}
#else
	/* Test if the current row is valid */
	if (PixelSetIteratorRow(internpix->pixel_iterator, PixelGetIteratorRow(internpix->pixel_iterator))) {
		 RETURN_TRUE;
	}
#endif
	RETURN_FALSE;
}
/* }}} */

/* END OF PIXELITERATOR */
