/*
 * 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>
 * Copyright 2009 Bo Yang <struggleyb.nku@gmail.com>
 */

#include <assert.h>
#include <stdlib.h>

#include <dom/core/characterdata.h>
#include <dom/core/string.h>
#include <dom/events/events.h>

#include "core/characterdata.h"
#include "core/document.h"
#include "core/node.h"
#include "utils/utils.h"
#include "events/mutation_event.h"

/* The virtual functions for dom_characterdata, we make this vtable
 * public to each child class */
const struct dom_characterdata_vtable characterdata_vtable = {
	{
		{
			DOM_NODE_EVENT_TARGET_VTABLE
		},
		DOM_NODE_VTABLE_CHARACTERDATA
	},
	DOM_CHARACTERDATA_VTABLE
};


/* Create a DOM characterdata node and compose the vtable */
dom_characterdata *_dom_characterdata_create(void)
{
	dom_characterdata *cdata = malloc(sizeof(struct dom_characterdata));
	if (cdata == NULL)
		return NULL;

	cdata->base.base.vtable = &characterdata_vtable;
	cdata->base.vtable = NULL;

	return cdata;
}

/**
 * Initialise a character data node
 *
 * \param cdata  The character data node to initialise
 * \param doc    The document which owns the node
 * \param type   The node type required
 * \param name   The node name, or NULL
 * \param value  The node value, or NULL
 * \return DOM_NO_ERR on success.
 *
 * \p doc, \p name and \p value will have their reference counts increased.
 */
dom_exception _dom_characterdata_initialise(struct dom_characterdata *cdata,
		struct dom_document *doc, dom_node_type type,
		dom_string *name, dom_string *value)
{
	return _dom_node_initialise(&cdata->base, doc, type, 
			name, value, NULL, NULL);
}

/**
 * Finalise a character data node
 *
 * \param cdata  The node to finalise
 *
 * The contents of \p cdata will be cleaned up. \p cdata will not be freed.
 */
void _dom_characterdata_finalise(struct dom_characterdata *cdata)
{
	_dom_node_finalise(&cdata->base);
}


/*----------------------------------------------------------------------*/

/* The public virtual functions */

/**
 * Retrieve data from a character data node
 *
 * \param cdata  Character data node to retrieve data from
 * \param data   Pointer to location to receive data
 * \return DOM_NO_ERR.
 *
 * The returned string will have its reference count increased. It is
 * the responsibility of the caller to unref the string once it has
 * finished with it.
 *
 * DOM3Core states that this can raise DOMSTRING_SIZE_ERR. It will not in
 * this implementation; dom_strings are unbounded.
 */
dom_exception _dom_characterdata_get_data(struct dom_characterdata *cdata,
		dom_string **data)
{
	struct dom_node_internal *c = (struct dom_node_internal *) cdata;

	if (c->value != NULL) {
		dom_string_ref(c->value);
	}
	*data = c->value;

	return DOM_NO_ERR;
}

/**
 * Set the content of a character data node
 *
 * \param cdata  Node to set the content of
 * \param data   New value for node
 * \return DOM_NO_ERR                      on success,
 *         DOM_NO_MODIFICATION_ALLOWED_ERR if \p cdata is readonly.
 *
 * The new content will have its reference count increased, so the caller
 * should unref it after the call (as the caller should have already claimed
 * a reference on the string). The node's existing content will be unrefed.
 */
dom_exception _dom_characterdata_set_data(struct dom_characterdata *cdata,
		dom_string *data)
{
	struct dom_node_internal *c = (struct dom_node_internal *) cdata;
	dom_exception err;
	struct dom_document *doc;
	bool success = true;

	if (_dom_node_readonly(c)) {
		return DOM_NO_MODIFICATION_ALLOWED_ERR;
	}

	/* Dispatch a DOMCharacterDataModified event */
	doc = dom_node_get_owner(cdata);
	err = _dom_dispatch_characterdata_modified_event(doc, c, c->value,
			data, &success);
	if (err != DOM_NO_ERR)
		return err;

	if (c->value != NULL) {
		dom_string_unref(c->value);
	}

	dom_string_ref(data);
	c->value = data;

	success = true;
	return _dom_dispatch_subtree_modified_event(doc, c->parent, &success);
}

/**
 * Get the length (in characters) of a character data node's content
 *
 * \param cdata   Node to read content length of
 * \param length  Pointer to location to receive character length of content
 * \return DOM_NO_ERR.
 */
dom_exception _dom_characterdata_get_length(struct dom_characterdata *cdata,
		uint32_t *length)
{
	struct dom_node_internal *c = (struct dom_node_internal *) cdata;

	if (c->value != NULL) {
		*length = dom_string_length(c->value);
	} else {
		*length = 0;
	}

	return DOM_NO_ERR;
}

/**
 * Extract a range of data from a character data node
 *
 * \param cdata   The node to extract data from
 * \param offset  The character offset of substring to extract
 * \param count   The number of characters to extract
 * \param data    Pointer to location to receive substring
 * \return DOM_NO_ERR         on success,
 *         DOM_INDEX_SIZE_ERR if \p offset is negative or greater than the 
 *                            number of characters in \p cdata or 
 *                            \p count is negative.
 *
 * The returned string will have its reference count increased. It is
 * the responsibility of the caller to unref the string once it has
 * finished with it.
 *
 * DOM3Core states that this can raise DOMSTRING_SIZE_ERR. It will not in
 * this implementation; dom_strings are unbounded.
 */
dom_exception _dom_characterdata_substring_data(
		struct dom_characterdata *cdata, uint32_t offset,
		uint32_t count, dom_string **data)
{
	struct dom_node_internal *c = (struct dom_node_internal *) cdata;
	uint32_t len, end;

	if ((int32_t) offset < 0 || (int32_t) count < 0) {
		return DOM_INDEX_SIZE_ERR;
	}

	if (c->value != NULL) {
		len = dom_string_length(c->value);
	} else {
		len = 0;
	}

	if (offset > len) {
		return DOM_INDEX_SIZE_ERR;
	}

	end = (offset + count) >= len ? len : offset + count;

	return dom_string_substr(c->value, offset, end, data);
}

/**
 * Append data to the end of a character data node's content
 *
 * \param cdata  The node to append data to
 * \param data   The data to append
 * \return DOM_NO_ERR                      on success,
 *         DOM_NO_MODIFICATION_ALLOWED_ERR if \p cdata is readonly.
 */
dom_exception _dom_characterdata_append_data(struct dom_characterdata *cdata,
		dom_string *data)
{
	struct dom_node_internal *c = (struct dom_node_internal *) cdata;
	dom_string *temp;
	dom_exception err;
	struct dom_document *doc;
	bool success = true;

	if (_dom_node_readonly(c)) {
		return DOM_NO_MODIFICATION_ALLOWED_ERR;
	}

	err = dom_string_concat(c->value, data, &temp);
	if (err != DOM_NO_ERR) {
		return err;
	}

	/* Dispatch a DOMCharacterDataModified event */
	doc = dom_node_get_owner(cdata);
	err = _dom_dispatch_characterdata_modified_event(doc, c, c->value,
			temp, &success);
	if (err != DOM_NO_ERR) {
		dom_string_unref(temp);
		return err;
	}

	if (c->value != NULL) {
		dom_string_unref(c->value);
	}

	c->value = temp;

	success = true;
	return _dom_dispatch_subtree_modified_event(doc, c->parent, &success);
}

/**
 * Insert data into a character data node's content
 *
 * \param cdata   The node to insert into
 * \param offset  The character offset to insert at
 * \param data    The data to insert
 * \return DOM_NO_ERR                      on success,
 *         DOM_INDEX_SIZE_ERR              if \p offset is negative or greater 
 *                                         than the number of characters in 
 *                                         \p cdata,
 *         DOM_NO_MODIFICATION_ALLOWED_ERR if \p cdata is readonly.
 */
dom_exception _dom_characterdata_insert_data(struct dom_characterdata *cdata,
		uint32_t offset, dom_string *data)
{
	struct dom_node_internal *c = (struct dom_node_internal *) cdata;
	dom_string *temp;
	uint32_t len;
	dom_exception err;
	struct dom_document *doc;
	bool success = true;

	if (_dom_node_readonly(c)) {
		return DOM_NO_MODIFICATION_ALLOWED_ERR;
	}

	if ((int32_t) offset < 0) {
		return DOM_INDEX_SIZE_ERR;
	}

	if (c->value != NULL) {
		len = dom_string_length(c->value);
	} else {
		len = 0;
	}

	if (offset > len) {
		return DOM_INDEX_SIZE_ERR;
	}

	err = dom_string_insert(c->value, data, offset, &temp);
	if (err != DOM_NO_ERR) {
		return err;
	}

	/* Dispatch a DOMCharacterDataModified event */
	doc = dom_node_get_owner(cdata);
	err = _dom_dispatch_characterdata_modified_event(doc, c, c->value,
			temp, &success);
	if (err != DOM_NO_ERR)
		return err;

	if (c->value != NULL) {
		dom_string_unref(c->value);
	}

	c->value = temp;

	success = true;
	return _dom_dispatch_subtree_modified_event(doc, c->parent, &success);
}

/**
 * Delete data from a character data node's content
 *
 * \param cdata   The node to delete from
 * \param offset  The character offset to start deletion from
 * \param count   The number of characters to delete
 * \return DOM_NO_ERR                      on success,
 *         DOM_INDEX_SIZE_ERR              if \p offset is negative or greater 
 *                                         than the number of characters in 
 *                                         \p cdata or \p count is negative,
 *         DOM_NO_MODIFICATION_ALLOWED_ERR if \p cdata is readonly.
 */
dom_exception _dom_characterdata_delete_data(struct dom_characterdata *cdata,
		uint32_t offset, uint32_t count)
{
	struct dom_node_internal *c = (struct dom_node_internal *) cdata;
	dom_string *temp;
	uint32_t len, end;
	dom_exception err;
	struct dom_document *doc;
	bool success = true;
	dom_string *empty;

	if (_dom_node_readonly(c)) {
		return DOM_NO_MODIFICATION_ALLOWED_ERR;
	}

	if ((int32_t) offset < 0 || (int32_t) count < 0) {
		return DOM_INDEX_SIZE_ERR;
	}

	if (c->value != NULL) {
		len = dom_string_length(c->value);
	} else {
		len = 0;
	}

	if (offset > len) {
		return DOM_INDEX_SIZE_ERR;
	}

	end = (offset + count) >= len ? len : offset + count;

	empty = ((struct dom_document *)
		 ((struct dom_node_internal *)c)->owner)->_memo_empty;

	err = dom_string_replace(c->value, empty, offset, end, &temp);
	if (err != DOM_NO_ERR) {
		return err;
	}

	/* Dispatch a DOMCharacterDataModified event */
	doc = dom_node_get_owner(cdata);
	err = _dom_dispatch_characterdata_modified_event(doc, c, c->value,
			temp, &success);
	if (err != DOM_NO_ERR)
		return err;

	if (c->value != NULL) {
		dom_string_unref(c->value);
	}

	c->value = temp;

	success = true;
	return _dom_dispatch_subtree_modified_event(doc, c->parent, &success);
}

/**
 * Replace a section of a character data node's content
 *
 * \param cdata   The node to modify
 * \param offset  The character offset of the sequence to replace
 * \param count   The number of characters to replace
 * \param data    The replacement data
 * \return DOM_NO_ERR                      on success,
 *         DOM_INDEX_SIZE_ERR              if \p offset is negative or greater 
 *                                         than the number of characters in 
 *                                         \p cdata or \p count is negative,
 *         DOM_NO_MODIFICATION_ALLOWED_ERR if \p cdata is readonly.
 */
dom_exception _dom_characterdata_replace_data(struct dom_characterdata *cdata,
		uint32_t offset, uint32_t count,
		dom_string *data)
{
	struct dom_node_internal *c = (struct dom_node_internal *) cdata;
	dom_string *temp;
	uint32_t len, end;
	dom_exception err;
	struct dom_document *doc;
	bool success = true;

	if (_dom_node_readonly(c)) {
		return DOM_NO_MODIFICATION_ALLOWED_ERR;
	}

	if ((int32_t) offset < 0 || (int32_t) count < 0) {
		return DOM_INDEX_SIZE_ERR;
	}

	if (c->value != NULL) {
		len = dom_string_length(c->value);
	} else {
		len = 0;
	}

	if (offset > len) {
		return DOM_INDEX_SIZE_ERR;
	}

	end = (offset + count) >= len ? len : offset + count;

	err = dom_string_replace(c->value, data, offset, end, &temp);
	if (err != DOM_NO_ERR) {
		return err;
	}

	/* Dispatch a DOMCharacterDataModified event */
	doc = dom_node_get_owner(cdata);
	err = _dom_dispatch_characterdata_modified_event(doc, c, c->value, temp,
			&success);
	if (err != DOM_NO_ERR)
		return err;

	if (c->value != NULL) {
		dom_string_unref(c->value);
	}

	c->value = temp;

	success = true;
	return _dom_dispatch_subtree_modified_event(doc, c->parent, &success);
}

dom_exception _dom_characterdata_get_text_content(dom_node_internal *node,
						  dom_string **result)
{
	dom_characterdata *cdata = (dom_characterdata *)node;
	
	return dom_characterdata_get_data(cdata, result);
}

dom_exception _dom_characterdata_set_text_content(dom_node_internal *node,
						  dom_string *content)
{
	dom_characterdata *cdata = (dom_characterdata *)node;
	
	return dom_characterdata_set_data(cdata, content);
}

/*----------------------------------------------------------------------*/

/* The protected virtual functions of Node, see core/node.h for details */
void _dom_characterdata_destroy(struct dom_node_internal *node)
{
	assert("Should never be here" == NULL);
	UNUSED(node);
}

/* The copy constructor of this class */
dom_exception _dom_characterdata_copy(dom_node_internal *old, 
		dom_node_internal **copy)
{
	dom_characterdata *new_node;
	dom_exception err;

	new_node = malloc(sizeof(dom_characterdata));
	if (new_node == NULL)
		return DOM_NO_MEM_ERR;

	err = dom_characterdata_copy_internal(old, new_node);
	if (err != DOM_NO_ERR) {
		free(new_node);
		return err;
	}

	*copy = (dom_node_internal *) new_node;

	return DOM_NO_ERR;
}

dom_exception _dom_characterdata_copy_internal(dom_characterdata *old,
		dom_characterdata *new)
{
	return dom_node_copy_internal(old, new);
}

