/**
 * File: contextxmlparser.cpp
 * Created by: <Joern Reimerdes>
 * Created on: 2004/08/31 13:48:00
 * @file
 * $Id$
 * $Revision$
 * $Author$
 * $Date$
 *
 * This file is a part of The iWear Framework.
 * In particular is this file a part of the Framework context Library
 *
 * The iWear Framework 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 as in version 2 of the License.

 * 
 * The iWear Framework 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
 * The iWear Framework; if not, write to the Free Software Foundation, Inc., 59
 * Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

#include <iwear-context/contextxmlparser.h>

namespace iwear {
namespace context {

    // {{{ Definition of extern const attributes

    /* XML nodes names */
    const XMLCh* NODE_CONTEXTS = NULL;
    const XMLCh* NODE_CONTEXT = NULL;

    const XMLCh* NODE_ATOMIC = NULL;
    const XMLCh* NODE_SENSOR = NULL;
    const XMLCh* NODE_COMPOSITE = NULL;
    const XMLCh* NODE_LOGIC = NULL;
    const XMLCh* NODE_CONTEXT_REF = NULL;
    
    const XMLCh* NODE_DEFINITIONS = NULL;
    const XMLCh* NODE_COMPARISON = NULL;
    const XMLCh* NODE_LOGIC_RELATION = NULL;
    const XMLCh* NODE_VALUE_TYPE = NULL;

    /* XML attribute names */

    const XMLCh* ATTRIBUTE_NAME = NULL;
    const XMLCh* ATTRIBUTE_TYPE = NULL;
    const XMLCh* ATTRIBUTE_COMPARE_VALUE = NULL;
    const XMLCh* ATTRIBUTE_COMPARISON = NULL;
    const XMLCh* ATTRIBUTE_UNIT = NULL;
    const XMLCh* ATTRIBUTE_ACCURACY = NULL;
    const XMLCh* ATTRIBUTE_VALUE_TYPE = NULL;
    const XMLCh* ATTRIBUTE_LOGIC_RELATION = NULL;
    const XMLCh* ATTRIBUTE_OPERATOR = NULL;

    /* values */
    const XMLCh* VALUE_AND = NULL;
    const XMLCh* VALUE_OR = NULL;

    const XMLCh* VALUE_EQUALS = NULL;
    const XMLCh* VALUE_NOT_EQUALS = NULL;
    const XMLCh* VALUE_GREATER = NULL;
    const XMLCh* VALUE_LOWER = NULL;
    const XMLCh* VALUE_GREATER_EQUALS = NULL;
    const XMLCh* VALUE_LOWER_EQUALS = NULL;

    const XMLCh* VALUE_BOOLEAN = NULL;
    const XMLCh* VALUE_DATE = NULL;
    const XMLCh* VALUE_NOMINAL = NULL;
    const XMLCh* VALUE_ORDINAL = NULL;
    const XMLCh* VALUE_DISCRETE = NULL;
    const XMLCh* VALUE_CONTINOUS = NULL;
    const XMLCh* VALUE_ACCURACY = NULL;

    // }}}

    /** Creates a new instance of ContextXMLParser.
     *
     */
    ContextXMLParser::ContextXMLParser(ContextManager* context_manager)
    : context_nodes(NULL)
    {
	this->xml_handler = new XMLHandler();
	this->context_xml_handler = new ContextXMLHandler(this->xml_handler);
	this->init_constants();
	if (NODE_CONTEXT == NULL){
	    d_err << "ERROR: Konstants not initialized" << endl;
	}
	this->context_manager = context_manager;
	this->context_xml_factory_atomic = 
	    new ContextXMLFactoryAtomic(this->context_manager, 
					this->context_xml_handler);
    }

    /** Distroys the instance of ContextXMLParser.
     * 
     */
    ContextXMLParser::~ContextXMLParser(){
	delete this->context_xml_factory_atomic;
	delete this->context_xml_handler;
	delete this->xml_handler;

	delete context_nodes;
	delete value_type_enum_map;
	delete composite_type_enum_map;
	delete logic_relation_enum_map;
	delete logic_relation_map;
	delete value_type_map;
	delete context_type_enum_map;
    }

    /** 
     * Creates a list of ContextObjects parsing an xml file.
     *
     */
    list<ContextObject*>* ContextXMLParser::load_contexts_from_file(string uri){
	list<ContextObject*>* context_list = new list<ContextObject*>();
	d_dbg << "Loading context file: " << uri << " ... " << endl;
	DOMDocument* document_element = 
	    this->xml_handler->load_dom_document_from_file(uri);
	if (document_element == NULL){
	    return NULL;
	}
	this->context_nodes = this->get_context_element_list(document_element);
	d_dbg << "Done loading. " << endl
	      << context_nodes->size() << " Contexts where loaded. " << endl;

	list<DOMElement*>::iterator context_iterator(context_nodes->begin());
	while(context_iterator != context_nodes->end()){
	    try {
		context_list->
		    push_back(this->create_composite_context(*context_iterator));
	    }
	    catch (const consistency_error& constistency_e){
		d_err << "Error: Consistency error occured while loding the " 
		      << endl << "\tcontext file: " << uri << " "<< endl;
		d_dbg << constistency_e.what() << endl;
	    }
	    context_iterator++;
	}
	return context_list;
    }

    /** Creates a new ContextObject from a string.
     * 
     */
    ContextObject* 
    ContextXMLParser::create_composite_context(DOMElement* context_node){ 
	ContextObject* context_object = NULL;
	/* get name */
	const char * str = XMLString::transcode(context_node->getAttribute(ATTRIBUTE_NAME));
	string context_name =  str;
	delete[] str;
	// d_dbg << __FFL__ << "Creating context " << context_name << endl;
	/* If a the contex is already defined the new one will be discarded. */
	if (this->context_manager->get_context(context_name) != NULL){
	    d_warn << "Warning: Context \"" << context_name 
		   << "\" was already defined." << endl 
		   << "\t" << "Discarding new context definition." << endl;
	    return NULL;
	}
	/* If the context does not exist, create it. */
	else {
	    // uint32_t context_level;
	    const string context_type_string = this->get_type_from_node(context_node);
	    /* get type */
	    ContextType context_type = 
		context_type_enum_map->find(context_type_string)->second;
	    /* if context is an atomic */
	    switch(context_type) {
	    case ATOMIC: 
		{
		    /* get atomic node */
		    DOMElement* atomic_node = 
			this->xml_handler->get_first_element_by_name(context_node, 
								     NODE_ATOMIC);
		    context_object = 
			(this->context_xml_factory_atomic)->
			create_atomic_context(context_name, atomic_node);
		    break;
		}
		/* else if context is a composite */
	    case COMPOSITE:
		// {{{ else if composite 
		{
		    /* get type */
		    DOMElement* composite_node = 
			this->xml_handler->get_first_element_by_name(context_node, 
								     NODE_COMPOSITE);
		    /* if composite context is a logic composite context */
		    const string composite_type_string = 
			this->get_type_from_node(composite_node);
		    d_dbg << "Composite Type " << composite_type_string << endl;
		    CompositeType composite_type = 
			composite_type_enum_map->find(composite_type_string)->second;
		    switch (composite_type) {
		    case LOGIC: 
			{
			    /* get logic realtion */
			    d_dbg << "get logic realtion" << endl;
			    DOMElement* logic_node = 
				this->xml_handler->get_first_element_by_name(composite_node, 
									     NODE_LOGIC);
			    const string logic_relation_string = 
				this->get_logic_relation_from_node(logic_node);

			    /* Create LogicCompositeContext */
			    d_dbg << "Create LogicCompositeContext" << endl;
			    LogicCompositeContext* new_logic_composite_context =
				new LogicCompositeContext(context_name);
			    LogicContextTerm* logic_context_term = 
				this->create_logic_context_term(logic_node, 
								new_logic_composite_context);
			    new_logic_composite_context->
				set_logic_context_term(logic_context_term);
			    context_object = 
				dynamic_cast<ContextObject*>(new_logic_composite_context);

			    d_dbg << "Register Context" << endl;
			    if (context_object != NULL){
				this->context_manager->register_context(context_object);
			    }
			    break;
			}
		    default: 
			{
			    stringstream error_message;
			    error_message << __FFL__ << ":\n"
					  << "There is no handling for the " 
					  << "specified composite type \"" 
					  << composite_type_string 
					  << "\" in \"" << context_type_string 
					  <<  "\". " << endl;
			    throw consistency_error(error_message.str());
			    return NULL;
			}
		    } // composite_type switch
		    break;
		}
	    default:
		{
		    stringstream error_message;
		    error_message << __FFL__ << ":\n"
				  << "There is no handling for the specified "
				  << "context type \"" << context_type_string 
				  << "\" in \"" << context_name <<  "\". " << endl;
		    throw consistency_error(error_message.str());
		    return NULL;
		}
	    } // context_type switch
	    // }}}
	    /* register context */
	    if (context_object != NULL){
		this->context_manager->register_context(context_object);
	    }
	    return context_object;
	}
    }

    /** Creates a LogicContextTerm from a logic_node.
     *
     */
    LogicContextTerm* 
    ContextXMLParser::create_logic_context_term(DOMElement* logic_node,
			     LogicCompositeContext* upper_logic_composite){
	const string logic_relation_string = 
	    this->get_logic_relation_from_node(logic_node);
	LogicRelation logic_relation = 
	    logic_relation_enum_map->find(logic_relation_string)->second;

	/* Create LogicCompositeContext */
	LogicContextTerm* logic_context_term = new LogicContextTerm();
	logic_context_term->set_logic_relation(logic_relation);
	/* ContextTerm is returned and added within the upper function */
	//	upper_logic_composite->add_context_part(logic_context_term);

	// {{{ references  
	/* get all context references */
		    
	list<DOMElement*>* context_references = this->xml_handler->
	    get_element_list_by_name(logic_node, NODE_CONTEXT_REF);

	list<DOMElement*>::iterator reference_iterator = 
	    context_references->begin();
	while(reference_iterator != context_references->end()){
	    /* for each reference get name */
	    const XMLCh* reference_name = 
		(*reference_iterator)->getAttribute(ATTRIBUTE_NAME);
	    const char * reference_string = 
		XMLString::transcode(reference_name);
	    string rstr = reference_string;
	    delete[] reference_string;
	    ContextObject* child = 
		this->context_manager->get_context(rstr);
	    if (child != NULL){
		upper_logic_composite->add_child(child);
		ContextPart* part = dynamic_cast<ContextPart*>(child);
		logic_context_term->add_context_part(part);
	    }
	    // @todo: add something that handles if
	    // references are defined later in the xml document
	    reference_iterator++;
	}
	delete context_references;

	// }}}
		    
	// {{{ contexts 

	/* get all contexts  */
	list<DOMElement*>*  context_node_list = this->xml_handler->
	    get_element_list_by_name(logic_node, NODE_CONTEXT);

	/* for each context create it */
	/* call create context from node */
	list<DOMElement*>::iterator context_iterator = 
	    context_node_list->begin();
	while(context_iterator != context_node_list->end()){
	    /* for each context get name */
	    const XMLCh* new_context_name = 
		(*context_iterator)->getAttribute(ATTRIBUTE_NAME);
	    const string new_context_string = 
		XMLString::transcode(new_context_name);
	    /* Try to get the context from the context
	       manager to verify that it is not allready registered */
	    ContextObject* child = 
		this->context_manager->get_context(new_context_string);
	    /* If it is allready registered use it and do
	       not create a new context */
	    if (child != NULL){
		upper_logic_composite->add_child(child);
		ContextPart* part = dynamic_cast<ContextPart*>(child);
		logic_context_term->add_context_part(part);
	    }
	    /* Recursiv call of context creation. 
	       If the context is new call this method to create it. */
	    else {
		child = this->create_composite_context(*context_iterator);
		/* If an error Occured skip this context. */
		if (child == NULL){
		    break;
		}
		upper_logic_composite->add_child(child);
		ContextPart* part = dynamic_cast<ContextPart*>(child);
		logic_context_term->add_context_part(part);
	    }
	    context_iterator++;
	}
	delete context_node_list;

	// }}}
		    
	// {{{ logic elements

	/* get all logic elements */
	list<DOMElement*>* logic_node_list = this->xml_handler->
	    get_element_list_by_name(logic_node, NODE_LOGIC);
		    
	/* for each logic node add a logic_context_term  */
	list<DOMElement*>::iterator logic_iterator = 
	    logic_node_list->begin();
	while(logic_iterator != logic_node_list->end()){
	    /* recursiv creation of a logic relation tree */
	    LogicContextTerm* child_context_term;
	    child_context_term = this->create_logic_context_term(*logic_iterator, 
							   upper_logic_composite);
	    ContextPart* context_part = dynamic_cast<ContextPart*>(child_context_term);
	    logic_context_term->add_context_part(context_part);
	}
	delete logic_node_list;

	// }}}
	
	return logic_context_term;
    }
    
    /** Get a list of all context nodes of the DOMDocument read by the
     * 	parser.
     */
    list<DOMElement*>* 
    ContextXMLParser::get_context_element_list(DOMDocument* document){
	list<DOMElement*>* context_element_list = new list<DOMElement*>;
	DOMNodeList* context_node_list = 
	    document->getElementsByTagName(NODE_CONTEXT);
	XMLSize_t list_length = context_node_list->getLength();
	for (XMLSize_t node_index = 0; node_index < list_length; node_index++ ){
	    DOMElement* context_element = 
		dynamic_cast<DOMElement*>(context_node_list->item(node_index));
	    if (context_element != NULL){
		context_element_list->push_back(context_element);
	    }
	}
	return context_element_list;
    }

    /** Initialises the node name and attribute name constants if they
     * were net initialised before.
     */
    void ContextXMLParser::init_constants(void){

	/* Context Types */
	context_type_enum_map = new map<const string, ContextType>;
	(*context_type_enum_map)["atomic"] = ATOMIC;
	(*context_type_enum_map)["composite"] = COMPOSITE;

	/* Composite Types */
	composite_type_enum_map = new map<const string, CompositeType>;
	(*composite_type_enum_map)["logic"] = LOGIC;

	/* LogicRelation Types */
	logic_relation_enum_map = new map<const string, LogicRelation>;
	(*logic_relation_enum_map)["and"] = AND;
	(*logic_relation_enum_map)["or"] = OR;

	logic_relation_map = new map<LogicRelation, const XMLCh*>;
	(*logic_relation_map)[AND] = VALUE_AND;
	(*logic_relation_map)[OR] = VALUE_OR;

	/* Value Types */
	value_type_enum_map = new map<const string, ContextValueType>;
	(*value_type_enum_map)["boolean"] = BOOLEAN;
	(*value_type_enum_map)["date"] = DATE;
	(*value_type_enum_map)["nominal"] = NOMINAL;
	(*value_type_enum_map)["ordinal"] = ORDINAL;
	(*value_type_enum_map)["discrete"] = DISCRETE;
	(*value_type_enum_map)["continous"] = CONTINOUS;

	value_type_map = new map<ContextValueType, const XMLCh* >;
	(*value_type_map)[CONTINOUS] = VALUE_CONTINOUS;
	(*value_type_map)[DATE] = VALUE_DATE;
	(*value_type_map)[NOMINAL] = VALUE_NOMINAL;
	(*value_type_map)[ORDINAL] = VALUE_ORDINAL;
	(*value_type_map)[DISCRETE] = VALUE_DISCRETE;
	(*value_type_map)[CONTINOUS] = VALUE_CONTINOUS;

    }

} // context
} // iwear

