/*
   +----------------------------------------------------------------------+
   | Copyright © The PHP Group and Contributors.                          |
   +----------------------------------------------------------------------+
   | This source file is subject to the Modified BSD License that is      |
   | bundled with this package in the file LICENSE, and is available      |
   | through the World Wide Web at <https://www.php.net/license/>.        |
   |                                                                      |
   | SPDX-License-Identifier: BSD-3-Clause                                |
   +----------------------------------------------------------------------+
   | Authors: Christian Stocker <chregu@php.net>                          |
   |          Rob Richards <rrichards@php.net>                            |
   +----------------------------------------------------------------------+
*/

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include "php.h"
#if defined(HAVE_LIBXML) && defined(HAVE_DOM)
#include "php_dom.h"
#include "obj_map.h"

typedef struct nodeIterator {
	int cur;
	int index;
	xmlNode *node;
} nodeIterator;

/* Function pointer typedef changed in 2.9.8, see https://github.com/GNOME/libxml2/commit/e03f0a199a67017b2f8052354cf732b2b4cae787 */
#if LIBXML_VERSION >= 20908
static void itemHashScanner (void *payload, void *data, const xmlChar *name) /* {{{ */
#else
static void itemHashScanner (void *payload, void *data, xmlChar *name)
#endif
{
	nodeIterator *priv = data;

	if (priv->cur < priv->index) {
		priv->cur++;
	} else {
		if (priv->node == NULL) {
			priv->node = payload;
		}
	}
}
/* }}} */

static dom_nnodemap_object *php_dom_iterator_get_nnmap(const php_dom_iterator *iterator)
{
	const zval *object = &iterator->intern.data;
	dom_object *nnmap = Z_DOMOBJ_P(object);
	return nnmap->ptr;
}

xmlNodePtr create_notation(xmlDtdPtr parent_dtd, const xmlChar *name, const xmlChar *ExternalID, const xmlChar *SystemID) /* {{{ */
{
	xmlEntityPtr ret = xmlMalloc(sizeof(xmlEntity));
	memset(ret, 0, sizeof(xmlEntity));
	ret->type = XML_NOTATION_NODE;
	ret->name = xmlStrdup(name);
	ret->ExternalID = xmlStrdup(ExternalID);
	ret->SystemID = xmlStrdup(SystemID);
	if (parent_dtd != NULL) {
		ret->parent = parent_dtd;
		ret->doc = parent_dtd->doc;
	}
	return (xmlNodePtr) ret;
}
/* }}} */

void dom_free_notation(xmlEntityPtr entity) /* {{{ */
{
	xmlFree((xmlChar *) entity->name);
	xmlFree((xmlChar *) entity->ExternalID);
	xmlFree((xmlChar *) entity->SystemID);
	xmlFree(entity);
}
/* }}} */

xmlNodePtr php_dom_libxml_hash_iter(xmlHashTable *ht, zend_long index)
{
	int htsize;

	if (index < 0) {
		return NULL;
	}
	if (UNEXPECTED(ZEND_LONG_INT_OVFL(index))) {
		zend_value_error("must be between 0 and %d", INT_MAX);
		return NULL;
	}

	if ((htsize = xmlHashSize(ht)) > 0 && index < htsize) {
		nodeIterator iter;
		iter.cur = 0;
		iter.index = (int) index;
		iter.node = NULL;
		xmlHashScan(ht, itemHashScanner, &iter);
		return iter.node;
	} else {
		return NULL;
	}
}

static void php_dom_iterator_dtor(zend_object_iterator *iter) /* {{{ */
{
	php_dom_iterator *iterator = (php_dom_iterator *)iter;

	zval_ptr_dtor(&iterator->intern.data);
	zval_ptr_dtor(&iterator->curobj);
}
/* }}} */

static zend_result php_dom_iterator_valid(zend_object_iterator *iter) /* {{{ */
{
	php_dom_iterator *iterator = (php_dom_iterator *)iter;

	if (!Z_ISNULL(iterator->curobj)) {
		return SUCCESS;
	} else {
		return FAILURE;
	}
}
/* }}} */

zval *php_dom_iterator_current_data(zend_object_iterator *iter) /* {{{ */
{
	php_dom_iterator *iterator = (php_dom_iterator *)iter;
	return Z_ISNULL(iterator->curobj) ? NULL : &iterator->curobj;
}
/* }}} */

static void php_dom_iterator_current_key(zend_object_iterator *iter, zval *key) /* {{{ */
{
	php_dom_iterator *iterator = (php_dom_iterator *)iter;
	dom_nnodemap_object *objmap = php_dom_iterator_get_nnmap(iterator);

	/* Only dtd named node maps, i.e. the ones based on a libxml hash table or attribute collections,
	 * are keyed by the name because in that case the name is unique. */
	if (objmap->handler->nameless) {
		ZVAL_LONG(key, iterator->index);
	} else {
		dom_object *intern = Z_DOMOBJ_P(&iterator->curobj);

		if (intern->ptr != NULL) {
			xmlNodePtr curnode = ((php_libxml_node_ptr *)intern->ptr)->node;
			if (curnode->type == XML_ATTRIBUTE_NODE && php_dom_follow_spec_intern(intern)) {
				ZVAL_STR(key, dom_node_get_node_name_attribute_or_element(curnode, false));
			} else {
				ZVAL_STRINGL_FAST(key, (const char *) curnode->name, xmlStrlen(curnode->name));
			}
		} else {
			ZVAL_NULL(key);
		}
	}
}
/* }}} */

static void php_dom_iterator_move_forward(zend_object_iterator *iter) /* {{{ */
{
	php_dom_iterator *iterator = (php_dom_iterator *)iter;
	if (Z_ISNULL(iterator->curobj)) {
		return;
	}

	iterator->index++;
	zval garbage;
	ZVAL_COPY_VALUE(&garbage, &iterator->curobj);
	ZVAL_NULL(&iterator->curobj);

	dom_object *intern = Z_DOMOBJ_P(&iterator->curobj);
	dom_nnodemap_object *objmap = php_dom_iterator_get_nnmap(iterator);

	if (intern->ptr != NULL) {
		/* Note: keep legacy behaviour for non-spec mode. */
		/* TODO: make this prettier */
		if (!php_dom_follow_spec_intern(intern) && (objmap->handler == &php_dom_obj_map_attributes || objmap->handler == &php_dom_obj_map_child_nodes)) {
			xmlNodePtr curnode = ((php_libxml_node_ptr *) intern->ptr)->node;
			curnode = curnode->next;
			if (curnode) {
				php_dom_create_object(curnode, &iterator->curobj, objmap->baseobj);
			}
		} else {
			objmap->handler->get_item(objmap, (zend_long) iterator->index, &iterator->curobj);
		}
	}

	zval_ptr_dtor(&garbage);
}
/* }}} */

static const zend_object_iterator_funcs php_dom_iterator_funcs = {
	php_dom_iterator_dtor,
	php_dom_iterator_valid,
	php_dom_iterator_current_data,
	php_dom_iterator_current_key,
	php_dom_iterator_move_forward,
	NULL,
	NULL,
	NULL, /* get_gc */
};

zend_object_iterator *php_dom_get_iterator(zend_class_entry *ce, zval *object, int by_ref) /* {{{ */
{
	dom_object *intern;
	dom_nnodemap_object *objmap;
	php_dom_iterator *iterator;

	if (by_ref) {
		zend_throw_error(NULL, "An iterator cannot be used with foreach by reference");
		return NULL;
	}
	iterator = emalloc(sizeof(*iterator));
	memset(iterator, 0, sizeof(*iterator));
	zend_iterator_init(&iterator->intern);

	ZVAL_OBJ_COPY(&iterator->intern.data, Z_OBJ_P(object));
	iterator->intern.funcs = &php_dom_iterator_funcs;

	intern = Z_DOMOBJ_P(object);
	objmap = (dom_nnodemap_object *)intern->ptr;
	objmap->handler->get_item(objmap, 0, &iterator->curobj);

	return &iterator->intern;
}
/* }}} */

#endif
