/*
    Copyright (C) 2005-2007  Michel de Boer <michel@twinklephone.com>

    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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*/

#include "pidf_body.h"

#include <cassert>
#include <libxml/parser.h>

#include "log.h"
#include "audits/memman.h"

#define PIDF_XML_VERSION	"1.0"
#define PIDF_NAMESPACE		"urn:ietf:params:xml:ns:pidf"

#define IS_PIDF_TAG(node, tag)	((node)->type == XML_ELEMENT_NODE &&\
				(node)->ns &&\
				xmlStrEqual((node)->ns->href, BAD_CAST PIDF_NAMESPACE) &&\
				xmlStrEqual((node)->name, BAD_CAST (tag)))
				
#define IS_PIDF_ATTR(attr, attr_name) ((attr)->type == XML_ATTRIBUTE_NODE &&\
				 (attr)->ns &&\
				 xmlStrEqual((attr)->ns->href, BAD_CAST PIDF_NAMESPACE) &&\
				 xmlStrEqual((attr)->name, BAD_CAST (attr_name)))

bool t_pidf_xml_body::extract_status(void) {
	assert(pidf_doc);

	xmlNode *root_element = NULL;
	
	// Get root
	root_element = xmlDocGetRootElement(pidf_doc);
	if (!root_element) {
		log_file->write_report("PIDF document has no root element.",
			"t_pidf_xml_body::extract_status",
			LOG_NORMAL, LOG_WARNING);
		return false;
	}
	
	// Check if root is <presence>
	if (!IS_PIDF_TAG(root_element, "presence")) {
		log_file->write_report("PIDF document has invalid root element.",
			"t_pidf_xml_body::extract_status",
			LOG_NORMAL, LOG_WARNING);
		return false;
	}
	
	
	
	pres_entity.clear();
	tuple_id.clear();
	basic_status.clear();
	
	// Get presence entity
	xmlChar *prop_entity = xmlGetProp(root_element, BAD_CAST "entity");
	if (prop_entity) {
		pres_entity = (char *)prop_entity;
	} else {
		log_file->write_report("Presence entity is missing.",
			"t_pidf_xml_body::extract_status",
			LOG_NORMAL, LOG_WARNING);
	}
	
	xmlNode *child = root_element->children;
	
	// Process children of root.
	for (xmlNode *cur_node = child; cur_node; cur_node = cur_node->next) {
		// Process tuple
		if (IS_PIDF_TAG(cur_node, "tuple")) {
			process_pidf_tuple(cur_node);
			
			// Process the first tuple and then stop.
			// Currently there is no support for multiple tuples
			// or additional elements.
			break;
		}
	}
	
	return true;
}

void t_pidf_xml_body::process_pidf_tuple(xmlNode *tuple) {
	assert(tuple);
	
	// Get tuple id.
	xmlChar *id = xmlGetProp(tuple, BAD_CAST "id");
	if (id) {
		tuple_id = (char *)id;
	} else {
		log_file->write_report("Tuple id is missing.",
			"t_pidf_xml_body::process_pidf_tuple",
			LOG_NORMAL, LOG_WARNING);
	}
	
	// Find status element
	xmlNode *child = tuple->children;
	for (xmlNode *cur_node = child; cur_node; cur_node = cur_node->next) {
		// Process status
		if (IS_PIDF_TAG(cur_node, "status")) {
			process_pidf_status(cur_node);
			break;
		}
	}
}

void t_pidf_xml_body::process_pidf_status(xmlNode *status) {
	assert(status);
	
	xmlNode *child = status->children;
	for (xmlNode *cur_node = child; cur_node; cur_node = cur_node->next) {
		// Process status
		if (IS_PIDF_TAG(cur_node, "basic")) {
			process_pidf_basic(cur_node);
			break;
		}
	}
}

void t_pidf_xml_body::process_pidf_basic(xmlNode *basic) {
	assert(basic);
	
	xmlNode *child = basic->children;
	if (child && child->type == XML_TEXT_NODE) {
		basic_status = (char*)child->content;
	} else {
		log_file->write_report("<basic> element has no content.",
			"t_pidf_xml_body::process_pidf_basic",
			LOG_NORMAL, LOG_WARNING);
	}
}

void t_pidf_xml_body::create_pidf(void) {
	clear_pidf();
	
	// XML doc
	pidf_doc = xmlNewDoc(BAD_CAST PIDF_XML_VERSION);
	MEMMAN_NEW(pidf_doc);
	pidf_doc->encoding = xmlCharStrdup("UTF-8");
	
	// presence
	xmlNode *node_presence = xmlNewNode(NULL, BAD_CAST "presence");
	xmlNs *ns_pidf = xmlNewNs(node_presence, BAD_CAST PIDF_NAMESPACE, NULL);
	xmlNewProp(node_presence, BAD_CAST "entity", BAD_CAST pres_entity.c_str());
	xmlDocSetRootElement(pidf_doc, node_presence);
	
	// tuple
	xmlNode *node_tuple = xmlNewChild(node_presence, ns_pidf, BAD_CAST "tuple", NULL);
	xmlNewProp(node_tuple, BAD_CAST "id", BAD_CAST tuple_id.c_str());
	
	// status
	xmlNode *node_status = xmlNewChild(node_tuple, ns_pidf, BAD_CAST "status", NULL);
	
	// basic
	xmlNode *node_basic = xmlNewChild(node_status, ns_pidf, 
		BAD_CAST "basic", BAD_CAST basic_status.c_str());
}

void t_pidf_xml_body::clear_pidf(void) {
	if (pidf_doc) {
		MEMMAN_DELETE(pidf_doc);
		xmlFreeDoc(pidf_doc);
		pidf_doc = NULL;
	}
}

t_pidf_xml_body::t_pidf_xml_body() : t_sip_body (),
	pidf_doc(NULL) 
{
}

t_pidf_xml_body::~t_pidf_xml_body() {
	clear_pidf();
}

string t_pidf_xml_body::encode(void) const {
	if (!pidf_doc) {
		t_pidf_xml_body *self = const_cast<t_pidf_xml_body *>(this);
		self->create_pidf();
	}
	assert(pidf_doc);
	
	xmlChar *buf;
	int buf_size;
	
	xmlDocDumpMemory(pidf_doc, &buf, &buf_size);
	string result((char*)buf);
	xmlFree(buf);
	
	return result;
}

t_sip_body *t_pidf_xml_body::copy(void) const {
	t_pidf_xml_body *body = new t_pidf_xml_body(*this);
	MEMMAN_NEW(body);
	
	if (pidf_doc) {
		body->pidf_doc = xmlCopyDoc(pidf_doc, 1);
		if (!body->pidf_doc) {
			log_file->write_report("Failed to copy pidf xml document.",
				"t_pidf_xml_body::copy",
				LOG_NORMAL, LOG_CRITICAL);
		} else {
			MEMMAN_NEW(body->pidf_doc);
		}
	}
	
	return body;
}

t_body_type t_pidf_xml_body::get_type(void) const {
	return BODY_PIDF_XML;
}

t_media t_pidf_xml_body::get_media(void) const {
	return t_media("application", "pidf+xml");
}

string t_pidf_xml_body::get_pres_entity(void) const {
	return pres_entity;
}

string t_pidf_xml_body::get_tuple_id(void) const {
	return tuple_id;
}

string t_pidf_xml_body::get_basic_status(void) const {
	return basic_status;
}

void t_pidf_xml_body::set_pres_entity(const string &_pres_entity) {
	clear_pidf();
	pres_entity = _pres_entity;
}

void t_pidf_xml_body::set_tuple_id(const string &_tuple_id) {
	clear_pidf();
	tuple_id = _tuple_id;
}

void t_pidf_xml_body::set_basic_status(const string &_basic_status) {
	clear_pidf();
	basic_status = _basic_status;
}

bool t_pidf_xml_body::parse(const string &s) {
	assert(pidf_doc == NULL);

	pidf_doc = xmlReadMemory(s.c_str(), s.size(), "noname.xml", NULL, 
		XML_PARSE_NOBLANKS | XML_PARSE_NOERROR | XML_PARSE_NOWARNING);
	if (!pidf_doc) {
		log_file->write_report("Failed to parse pidf xml document.",
			"t_pidf_xml_body::parse",
			LOG_NORMAL, LOG_WARNING);
	} else {
		MEMMAN_NEW(pidf_doc);
		if (!extract_status()) {
			MEMMAN_DELETE(pidf_doc);
			xmlFreeDoc(pidf_doc);
			pidf_doc = NULL;
		}
	}
	
	return (pidf_doc != NULL);
}


syntax highlighted by Code2HTML, v. 0.9.1