/*
 * This file is part of libdom.
 * Licensed under the MIT License,
 *                http://www.opensource.org/licenses/mit-license.php
 * Copyright 2007 John-Mark Bell <jmb@netsurf-browser.org>
 */

#ifndef dom_core_node_h_
#define dom_core_node_h_

#include <inttypes.h>
#include <stdbool.h>

#include <dom/core/exceptions.h>
#include <dom/core/string.h>
#include <dom/events/event_target.h>

struct dom_document;
struct dom_nodelist;
struct dom_namednodemap;
struct dom_node;

/**
 * Bits defining position of a node in a document relative to some other node
 */
typedef enum {
	DOM_DOCUMENT_POSITION_DISCONNECTED		= 0x01,
	DOM_DOCUMENT_POSITION_PRECEDING			= 0x02,
	DOM_DOCUMENT_POSITION_FOLLOWING			= 0x04,
	DOM_DOCUMENT_POSITION_CONTAINS			= 0x08,
	DOM_DOCUMENT_POSITION_CONTAINED_BY		= 0x10,
	DOM_DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC	= 0x20
} dom_document_position;

/**
 * Type of node operation being notified to user_data_handler
 */
typedef enum {
	DOM_NODE_CLONED		= 1,
	DOM_NODE_IMPORTED	= 2,
	DOM_NODE_DELETED	= 3,
	DOM_NODE_RENAMED	= 4,
	DOM_NODE_ADOPTED	= 5
} dom_node_operation;

/**
 * Type of handler function for user data registered on a DOM node
 */
typedef void (*dom_user_data_handler)(dom_node_operation operation,
		dom_string *key, void *data, struct dom_node *src,
		struct dom_node *dst);

/**
 * Type of a DOM node
 */
typedef enum {
	DOM_ELEMENT_NODE		= 1,
	DOM_ATTRIBUTE_NODE		= 2,
	DOM_TEXT_NODE			= 3,
	DOM_CDATA_SECTION_NODE		= 4,
	DOM_ENTITY_REFERENCE_NODE	= 5,
	DOM_ENTITY_NODE			= 6,
	DOM_PROCESSING_INSTRUCTION_NODE	= 7,
	DOM_COMMENT_NODE		= 8,
	DOM_DOCUMENT_NODE		= 9,
	DOM_DOCUMENT_TYPE_NODE		= 10,
	DOM_DOCUMENT_FRAGMENT_NODE	= 11,
	DOM_NOTATION_NODE		= 12,

	/* And a count of the number of node types */
	DOM_NODE_TYPE_COUNT
} dom_node_type;

typedef struct dom_node_internal dom_node_internal;

/**
 * DOM node type
 */
typedef struct dom_node {
	void *vtable;
	uint32_t refcnt;
} dom_node;

/* DOM node vtable */
typedef struct dom_node_vtable {
	dom_event_target_vtable base;
	/* pre-destruction hook */
	dom_exception (*dom_node_try_destroy)(dom_node_internal *node);
	/* The DOM level 3 node's oprations */
	dom_exception (*dom_node_get_node_name)(dom_node_internal *node,
			dom_string **result);
	dom_exception (*dom_node_get_node_value)(dom_node_internal *node,
			dom_string **result);
	dom_exception (*dom_node_set_node_value)(dom_node_internal *node,
			dom_string *value);
	dom_exception (*dom_node_get_node_type)(dom_node_internal *node,
			dom_node_type *result);
	dom_exception (*dom_node_get_parent_node)(dom_node_internal *node,
			dom_node_internal **result);
	dom_exception (*dom_node_get_child_nodes)(dom_node_internal *node,
			struct dom_nodelist **result);
	dom_exception (*dom_node_get_first_child)(dom_node_internal *node,
			dom_node_internal **result);
	dom_exception (*dom_node_get_last_child)(dom_node_internal *node,
			dom_node_internal **result);
	dom_exception (*dom_node_get_previous_sibling)(dom_node_internal *node,
			dom_node_internal **result);
	dom_exception (*dom_node_get_next_sibling)(dom_node_internal *node,
			dom_node_internal **result);
	dom_exception (*dom_node_get_attributes)(dom_node_internal *node,
			struct dom_namednodemap **result);
	dom_exception (*dom_node_get_owner_document)(dom_node_internal *node,
			struct dom_document **result);
	dom_exception (*dom_node_insert_before)(dom_node_internal *node,
			dom_node_internal *new_child, 
			dom_node_internal *ref_child,
			dom_node_internal **result);
	dom_exception (*dom_node_replace_child)(dom_node_internal *node,
			dom_node_internal *new_child, 
			dom_node_internal *old_child,
			dom_node_internal **result);
	dom_exception (*dom_node_remove_child)(dom_node_internal *node,
			dom_node_internal *old_child,
			dom_node_internal **result);
	dom_exception (*dom_node_append_child)(dom_node_internal *node,
			dom_node_internal *new_child,
			dom_node_internal **result);
	dom_exception (*dom_node_has_child_nodes)(dom_node_internal *node, 
			bool *result);
	dom_exception (*dom_node_clone_node)(dom_node_internal *node, bool deep,
			dom_node_internal **result);
	dom_exception (*dom_node_normalize)(dom_node_internal *node);
	dom_exception (*dom_node_is_supported)(dom_node_internal *node,
			dom_string *feature, dom_string *version,
			bool *result);
	dom_exception (*dom_node_get_namespace)(dom_node_internal *node,
			dom_string **result);
	dom_exception (*dom_node_get_prefix)(dom_node_internal *node,
			dom_string **result);
	dom_exception (*dom_node_set_prefix)(dom_node_internal *node,
			dom_string *prefix);
	dom_exception (*dom_node_get_local_name)(dom_node_internal *node,
			dom_string **result);
	dom_exception (*dom_node_has_attributes)(dom_node_internal *node, 
			bool *result);
	dom_exception (*dom_node_get_base)(dom_node_internal *node,
			dom_string **result);
	dom_exception (*dom_node_compare_document_position)(
			dom_node_internal *node, dom_node_internal *other,
			uint16_t *result);
	dom_exception (*dom_node_get_text_content)(dom_node_internal *node,
			dom_string **result);
	dom_exception (*dom_node_set_text_content)(dom_node_internal *node,
			dom_string *content);
	dom_exception (*dom_node_is_same)(dom_node_internal *node, 
			dom_node_internal *other, bool *result);
	dom_exception (*dom_node_lookup_prefix)(dom_node_internal *node,
			dom_string *namespace, 
			dom_string **result);
	dom_exception (*dom_node_is_default_namespace)(dom_node_internal *node,
			dom_string *namespace, bool *result);
	dom_exception (*dom_node_lookup_namespace)(dom_node_internal *node,
			dom_string *prefix, dom_string **result);
	dom_exception (*dom_node_is_equal)(dom_node_internal *node,
			dom_node_internal *other, bool *result);
	dom_exception (*dom_node_get_feature)(dom_node_internal *node,
			dom_string *feature, dom_string *version,
			void **result);
	dom_exception (*dom_node_set_user_data)(dom_node_internal *node,
			dom_string *key, void *data,
			dom_user_data_handler handler, void **result);
	dom_exception (*dom_node_get_user_data)(dom_node_internal *node,
			dom_string *key, void **result);
} dom_node_vtable;

/* The ref/unref methods define */

static inline dom_node *dom_node_ref(dom_node *node)
{
	if (node != NULL)
		node->refcnt++;
	
	return node;
}

#define dom_node_ref(n) dom_node_ref((dom_node *) (n))

static inline dom_exception dom_node_try_destroy(dom_node *node)
{
	return ((dom_node_vtable *) node->vtable)->dom_node_try_destroy(
			(dom_node_internal *) node);
}
#define dom_node_try_destroy(n) dom_node_try_destroy((dom_node *) (n))

static inline void dom_node_unref(dom_node *node)
{
	if (node != NULL) {
		if (--node->refcnt == 0)
			dom_node_try_destroy(node);
	}
		
}
#define dom_node_unref(n) dom_node_unref((dom_node *) (n))

/* Contains is non-virtual since it doesn't need to be */

dom_exception _dom_node_contains(struct dom_node_internal *node,
				struct dom_node_internal *other,
				bool *contains);
#define dom_node_contains(n, o, c) \
	_dom_node_contains((dom_node_internal *)(n), (dom_node_internal *)(o), (c))

/* All the rest are virtual */

static inline dom_exception dom_node_get_node_name(struct dom_node *node,
		dom_string **result)
{
	return ((dom_node_vtable *) node->vtable)->dom_node_get_node_name(
			(dom_node_internal *) node, result);
}
#define dom_node_get_node_name(n, r) dom_node_get_node_name((dom_node *) (n), (r))

static inline dom_exception dom_node_get_node_value(struct dom_node *node,
		dom_string **result)
{
	return ((dom_node_vtable *) node->vtable)->dom_node_get_node_value(
			(dom_node_internal *) node, result);
}
#define dom_node_get_node_value(n, r) dom_node_get_node_value( \
		(dom_node *) (n), (r))

static inline dom_exception dom_node_set_node_value(struct dom_node *node,
		dom_string *value)
{
	return ((dom_node_vtable *) node->vtable)->dom_node_set_node_value(
			(dom_node_internal *) node, value);
}
#define dom_node_set_node_value(n, v) dom_node_set_node_value( \
		(dom_node *) (n), (v))

static inline dom_exception dom_node_get_node_type(struct dom_node *node,
		dom_node_type *result)
{
	return ((dom_node_vtable *) node->vtable)->dom_node_get_node_type(
			(dom_node_internal *) node, result);
}
#define dom_node_get_node_type(n, r) dom_node_get_node_type( \
		(dom_node *) (n), (dom_node_type *) (r))

static inline dom_exception dom_node_get_parent_node(struct dom_node *node,
		dom_node **result)
{
	return ((dom_node_vtable *) node->vtable)->dom_node_get_parent_node(
			(dom_node_internal *) node, 
			(dom_node_internal **) result);
}
#define dom_node_get_parent_node(n, r) dom_node_get_parent_node( \
		(dom_node *) (n), (dom_node **) (r))

static inline dom_exception dom_node_get_child_nodes(struct dom_node *node,
		struct dom_nodelist **result)
{
	return ((dom_node_vtable *) node->vtable)->dom_node_get_child_nodes(
			(dom_node_internal *) node, result);
}
#define dom_node_get_child_nodes(n, r) dom_node_get_child_nodes( \
		(dom_node *) (n), (struct dom_nodelist **) (r))

static inline dom_exception dom_node_get_first_child(struct dom_node *node,
		dom_node **result)
{
	return ((dom_node_vtable *) node->vtable)->dom_node_get_first_child(
			(dom_node_internal *) node, 
			(dom_node_internal **) result);
}
#define dom_node_get_first_child(n, r) dom_node_get_first_child( \
		(dom_node *) (n), (dom_node **) (r))

static inline dom_exception dom_node_get_last_child(struct dom_node *node,
		dom_node **result)
{
	return ((dom_node_vtable *) node->vtable)->dom_node_get_last_child(
			(dom_node_internal *) node, 
			(dom_node_internal **) result);
}
#define dom_node_get_last_child(n, r) dom_node_get_last_child( \
		(dom_node *) (n), (dom_node **) (r))

static inline dom_exception dom_node_get_previous_sibling(
		struct dom_node *node, dom_node **result)
{
	return ((dom_node_vtable *) node->vtable)->
			dom_node_get_previous_sibling(
			(dom_node_internal *) node, 
			(dom_node_internal **) result);
}
#define dom_node_get_previous_sibling(n, r) dom_node_get_previous_sibling( \
		(dom_node *) (n), (dom_node **) (r))

static inline dom_exception dom_node_get_next_sibling(struct dom_node *node,
		dom_node **result)
{
	return ((dom_node_vtable *) node->vtable)->dom_node_get_next_sibling(
			(dom_node_internal *) node, 
			(dom_node_internal **) result);
}
#define dom_node_get_next_sibling(n, r) dom_node_get_next_sibling( \
		(dom_node *) (n), (dom_node **) (r))

static inline dom_exception dom_node_get_attributes(struct dom_node *node,
		struct dom_namednodemap **result)
{
	return ((dom_node_vtable *) node->vtable)->dom_node_get_attributes(
			(dom_node_internal *) node, result);
}
#define dom_node_get_attributes(n, r) dom_node_get_attributes( \
		(dom_node *) (n), (struct dom_namednodemap **) (r))

static inline dom_exception dom_node_get_owner_document(struct dom_node *node,
		struct dom_document **result)
{
	return ((dom_node_vtable *) node->vtable)->dom_node_get_owner_document(
			(dom_node_internal *) node, result);
}
#define dom_node_get_owner_document(n, r) dom_node_get_owner_document( \
		(dom_node *) (n), (struct dom_document **) (r))

static inline dom_exception dom_node_insert_before(struct dom_node *node,
		struct dom_node *new_child, struct dom_node *ref_child,
		struct dom_node **result)
{
	return ((dom_node_vtable *) node->vtable)->dom_node_insert_before(
			(dom_node_internal *) node,
			(dom_node_internal *) new_child,
			(dom_node_internal *) ref_child,
			(dom_node_internal **) result);
}
#define dom_node_insert_before(n, nn, ref, ret) dom_node_insert_before( \
		(dom_node *) (n), (dom_node *) (nn), (dom_node *) (ref),\
		(dom_node **) (ret))

static inline dom_exception dom_node_replace_child(struct dom_node *node,
		struct dom_node *new_child, struct dom_node *old_child,
		struct dom_node **result)
{
	return ((dom_node_vtable *) node->vtable)->dom_node_replace_child( 
			(dom_node_internal *) node,
			(dom_node_internal *) new_child,
			(dom_node_internal *) old_child,
			(dom_node_internal **) result);
}
#define dom_node_replace_child(n, nn, old, ret) dom_node_replace_child( \
		(dom_node *) (n), (dom_node *) (nn), (dom_node *) (old),\
		(dom_node **) (ret))

static inline dom_exception dom_node_remove_child(struct dom_node *node,
		struct dom_node *old_child,
		struct dom_node **result)
{
	return ((dom_node_vtable *) node->vtable)->dom_node_remove_child( 
			(dom_node_internal *) node,
			(dom_node_internal *) old_child,
			(dom_node_internal **) result);
}
#define dom_node_remove_child(n, old, ret) dom_node_remove_child( \
		(dom_node *) (n), (dom_node *) (old), (dom_node **) (ret))

static inline dom_exception dom_node_append_child(struct dom_node *node,
		struct dom_node *new_child,
		struct dom_node **result)
{
	return ((dom_node_vtable *) node->vtable)->dom_node_append_child(
			(dom_node_internal *) node,
			(dom_node_internal *) new_child,
			(dom_node_internal **) result);
}
#define dom_node_append_child(n, nn, ret) dom_node_append_child( \
		(dom_node *) (n), (dom_node *) (nn), (dom_node **) (ret))

static inline dom_exception dom_node_has_child_nodes(struct dom_node *node, 
		bool *result)
{
	return ((dom_node_vtable *) node->vtable)->dom_node_has_child_nodes(
			(dom_node_internal *) node, result);
}
#define dom_node_has_child_nodes(n, r) dom_node_has_child_nodes( \
		(dom_node *) (n), (bool *) (r))

static inline dom_exception dom_node_clone_node(struct dom_node *node, 
		bool deep, struct dom_node **result)
{
	return ((dom_node_vtable *) node->vtable)->dom_node_clone_node(
			(dom_node_internal *) node, deep,
			(dom_node_internal **) result);
}
#define dom_node_clone_node(n, d, r) dom_node_clone_node((dom_node *) (n), \
		(bool) (d), (dom_node **) (r))

static inline dom_exception dom_node_normalize(struct dom_node *node)
{
	return ((dom_node_vtable *) node->vtable)->dom_node_normalize(
			(dom_node_internal *) node);
}
#define dom_node_normalize(n) dom_node_normalize((dom_node *) (n))

static inline dom_exception dom_node_is_supported(struct dom_node *node,
		dom_string *feature, dom_string *version,
		bool *result)
{
	return ((dom_node_vtable *) node->vtable)->dom_node_is_supported(
			(dom_node_internal *) node, feature, 
			version, result);
}
#define dom_node_is_supported(n, f, v, r) dom_node_is_supported( \
		(dom_node *) (n), (f), (v), (bool *) (r))

static inline dom_exception dom_node_get_namespace(struct dom_node *node,
		dom_string **result)
{
	return ((dom_node_vtable *) node->vtable)->dom_node_get_namespace(
			(dom_node_internal *) node, result);
}
#define dom_node_get_namespace(n, r) dom_node_get_namespace((dom_node *) (n), (r))

static inline dom_exception dom_node_get_prefix(struct dom_node *node,
		dom_string **result)
{
	return ((dom_node_vtable *) node->vtable)->dom_node_get_prefix(
			(dom_node_internal *) node, result);
}
#define dom_node_get_prefix(n, r) dom_node_get_prefix((dom_node *) (n), (r))

static inline dom_exception dom_node_set_prefix(struct dom_node *node,
		dom_string *prefix)
{
	return ((dom_node_vtable *) node->vtable)->dom_node_set_prefix(
			(dom_node_internal *) node, prefix);
}
#define dom_node_set_prefix(n, p) dom_node_set_prefix((dom_node *) (n), (p))

static inline dom_exception dom_node_get_local_name(struct dom_node *node,
		dom_string **result)
{
	return ((dom_node_vtable *) node->vtable)->dom_node_get_local_name(
			(dom_node_internal *) node, result);
}
#define dom_node_get_local_name(n, r) dom_node_get_local_name((dom_node *) (n), (r))

static inline dom_exception dom_node_has_attributes(struct dom_node *node, 
		bool *result)
{
	return ((dom_node_vtable *) node->vtable)->dom_node_has_attributes(
			(dom_node_internal *) node, result);
}
#define dom_node_has_attributes(n, r) dom_node_has_attributes( \
		(dom_node *) (n), (bool *) (r))

static inline dom_exception dom_node_get_base(struct dom_node *node,
		dom_string **result)
{
	return ((dom_node_vtable *) node->vtable)->dom_node_get_base(
			(dom_node_internal *) node, result);
}
#define dom_node_get_base(n, r) dom_node_get_base((dom_node *) (n), (r))

static inline dom_exception dom_node_compare_document_position(
		struct dom_node *node, struct dom_node *other,
		uint16_t *result)
{
	return ((dom_node_vtable *) node->vtable)->
			dom_node_compare_document_position(
			(dom_node_internal *) node,
			(dom_node_internal *) other, result);
}
#define dom_node_compare_document_position(n, o, r) \
		dom_node_compare_document_position((dom_node *) (n), \
		(dom_node *) (o), (uint16_t *) (r))

static inline dom_exception dom_node_get_text_content(struct dom_node *node,
		dom_string **result)
{
	return ((dom_node_vtable *) node->vtable)->dom_node_get_text_content(
			(dom_node_internal *) node, result);
}
#define dom_node_get_text_content(n, r) dom_node_get_text_content( \
		(dom_node *) (n), (r))

static inline dom_exception dom_node_set_text_content(struct dom_node *node,
		dom_string *content)
{
	return ((dom_node_vtable *) node->vtable)->dom_node_set_text_content(
			(dom_node_internal *) node, content);
}
#define dom_node_set_text_content(n, c) dom_node_set_text_content( \
		(dom_node *) (n), (c))

static inline dom_exception dom_node_is_same(struct dom_node *node, 
		struct dom_node *other, bool *result)
{
	return ((dom_node_vtable *) node->vtable)->dom_node_is_same(
			(dom_node_internal *) node,
			(dom_node_internal *) other,
			result);
}
#define dom_node_is_same(n, o, r) dom_node_is_same((dom_node *) (n), \
		(dom_node *) (o), (bool *) (r))

static inline dom_exception dom_node_lookup_prefix(struct dom_node *node,
		dom_string *namespace, dom_string **result)
{
	return ((dom_node_vtable *) node->vtable)->dom_node_lookup_prefix(
			(dom_node_internal *) node, namespace, result);
}
#define dom_node_lookup_prefix(n, ns, r) dom_node_lookup_prefix( \
		(dom_node *) (n), (ns), (r))

static inline dom_exception dom_node_is_default_namespace(
		struct dom_node *node, dom_string *namespace,
		bool *result)
{
	return ((dom_node_vtable *) node->vtable)->
			dom_node_is_default_namespace(
			(dom_node_internal *) node, namespace, result);
}
#define dom_node_is_default_namespace(n, ns, r) dom_node_is_default_namespace(\
		(dom_node *) (n), (ns), (bool *) (r))

static inline dom_exception dom_node_lookup_namespace(struct dom_node *node,
		dom_string *prefix, dom_string **result)
{
	return ((dom_node_vtable *) node->vtable)->dom_node_lookup_namespace(
			(dom_node_internal *) node, prefix, result);
}
#define dom_node_lookup_namespace(n, p, r) dom_node_lookup_namespace( \
		(dom_node *) (n), (p), (r))

static inline dom_exception dom_node_is_equal(struct dom_node *node,
		struct dom_node *other, bool *result)
{
	return ((dom_node_vtable *) node->vtable)->dom_node_is_equal(
			(dom_node_internal *) node,
			(dom_node_internal *) other,
			result);
}
#define dom_node_is_equal(n, o, r) dom_node_is_equal((dom_node *) (n), \
		(dom_node *) (o), (bool *) (r))

static inline dom_exception dom_node_get_feature(struct dom_node *node,
		dom_string *feature, dom_string *version,
		void **result)
{
	return ((dom_node_vtable *) node->vtable)->dom_node_get_feature(
			(dom_node_internal *) node, feature, version, result);
}
#define dom_node_get_feature(n, f, v, r) dom_node_get_feature( \
		(dom_node *) (n), (f), (v), (void **) (r))

static inline dom_exception dom_node_set_user_data(struct dom_node *node,
		dom_string *key, void *data,
		dom_user_data_handler handler, void **result)
{
	return ((dom_node_vtable *) node->vtable)->dom_node_set_user_data(
			(dom_node_internal *) node, key, data, handler,
			result);
}
#define dom_node_set_user_data(n, k, d, h, r) dom_node_set_user_data( \
		(dom_node *) (n), (k), (void *) (d), \
		(dom_user_data_handler) h, (void **) (r))

static inline dom_exception dom_node_get_user_data(struct dom_node *node,
		dom_string *key, void **result)
{
	return ((dom_node_vtable *) node->vtable)->dom_node_get_user_data(
			(dom_node_internal *) node, key, result);
}
#define dom_node_get_user_data(n, k, r) dom_node_get_user_data( \
		(dom_node *) (n), (k), (void **) (r))

#endif
