/**
 * @file oval_syschar.c
 * \brief Open Vulnerability and Assessment Language
 *
 * See more details at http://oval.mitre.org/
 */

/*
 * Copyright 2009--2013 Red Hat Inc., Durham, North Carolina.
 * All Rights Reserved.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 *
 * Authors:
 *      "David Niemoller" <David.Niemoller@g2-inc.com>
 *      Šimon Lukašík
 */

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

#include <stdlib.h>
#include <stdio.h>
#include <string.h>

#include "oval_system_characteristics_impl.h"
#include "adt/oval_collection_impl.h"
#include "oval_agent_api_impl.h"
#include "oval_definitions_impl.h"

#include "common/util.h"
#include "common/debug_priv.h"

typedef struct oval_syschar {
	struct oval_syschar_model *model;
	oval_syschar_collection_flag_t flag;
	struct oval_collection *messages;
	struct oval_object *object;
	struct oval_collection *variable_bindings;	///< Represents <variable_value> elements
	struct oval_collection *sysitem;		///< Represents <reference> elements
	int variable_instance;				///< Represents variable_instance attribute
	int variable_instance_hint;			///< Internal hint of the next possible variable_instance attribute
} oval_syschar_t;					///< Represents a single collected <object> element

oval_syschar_collection_flag_t oval_syschar_get_flag(struct oval_syschar
						     *syschar)
{
	__attribute__nonnull__(syschar);

	return ((struct oval_syschar *)syschar)->flag;
}

void oval_syschar_set_flag(struct oval_syschar *syschar, oval_syschar_collection_flag_t flag) {
	__attribute__nonnull__(syschar);
	syschar->flag = flag;
}

void oval_syschar_set_object(struct oval_syschar *syschar, struct oval_object *object)
{
	__attribute__nonnull__(syschar);
	syschar->object = object;
}

struct oval_message_iterator *oval_syschar_get_messages(struct oval_syschar *syschar)
{
	return (struct oval_message_iterator *)oval_collection_iterator(syschar->messages);
}

void oval_syschar_add_message(struct oval_syschar *syschar, struct oval_message *message)
{
	__attribute__nonnull__(syschar);
	oval_collection_add(syschar->messages, message);
}

void oval_syschar_add_new_message(struct oval_syschar *syschar, char *text, oval_message_level_t level)
{
	__attribute__nonnull__(syschar);
	struct oval_message *msg;

	msg = oval_message_new();
	oval_message_set_text(msg, text);
	oval_message_set_level(msg, level);
	oval_collection_add(syschar->messages, msg);
}

struct oval_object *oval_syschar_get_object(struct oval_syschar *syschar)
{
	__attribute__nonnull__(syschar);

	return ((struct oval_syschar *)syschar)->object;
}

struct oval_syschar_model *oval_syschar_get_model(struct oval_syschar *syschar)
{
	__attribute__nonnull__(syschar);

	return ((struct oval_syschar *)syschar)->model;
}

struct oval_variable_binding_iterator *oval_syschar_get_variable_bindings(struct
									  oval_syschar
									  *syschar)
{
	__attribute__nonnull__(syschar);

	return (struct oval_variable_binding_iterator *)
	    oval_collection_iterator(syschar->variable_bindings);
}

struct oval_sysitem_iterator *oval_syschar_get_sysitem(struct oval_syschar *syschar)
{
	__attribute__nonnull__(syschar);

	return (struct oval_sysitem_iterator *)
	    oval_collection_iterator(syschar->sysitem);
}

void oval_syschar_add_sysitem(struct oval_syschar *syschar, struct oval_sysitem *sysitem) {
	__attribute__nonnull__(syschar);

	oval_collection_add(syschar->sysitem, sysitem);
}

void oval_syschar_add_variable_binding(struct oval_syschar *syschar, struct oval_variable_binding *binding) {
	__attribute__nonnull__(syschar);

	oval_collection_add(syschar->variable_bindings, binding);
}

struct oval_syschar *oval_syschar_new(struct oval_syschar_model *model, struct oval_object *object)
{
	__attribute__nonnull__(model);
	oval_syschar_t *syschar;

	syschar = (oval_syschar_t *) malloc(sizeof(oval_syschar_t));
	if (syschar == NULL)
		return NULL;

	syschar->flag = SYSCHAR_FLAG_UNKNOWN;
	syschar->variable_instance = 1;
	syschar->variable_instance_hint = 1;
	syschar->object = object;
	syschar->messages = oval_collection_new();
	syschar->sysitem = oval_collection_new();
	syschar->variable_bindings = oval_collection_new();
	syschar->model = model;

	oval_syschar_model_add_syschar(model, syschar);

	return syschar;
}

struct oval_syschar *oval_syschar_clone(struct oval_syschar_model *new_model, struct oval_syschar *old_syschar)
{
	struct oval_definition_model *def_model = oval_syschar_model_get_definition_model(new_model);
	struct oval_object *old_object = oval_syschar_get_object(old_syschar);
	struct oval_object *new_object = oval_definition_model_get_object(def_model, oval_object_get_id(old_object));
	if (new_object == NULL) {
		new_object = oval_object_clone(def_model, old_object);
	}

	struct oval_syschar *new_syschar = oval_syschar_new(new_model, new_object);

	oval_syschar_collection_flag_t flag = oval_syschar_get_flag(old_syschar);
	oval_syschar_set_flag(new_syschar, flag);
	int variable_instance = oval_syschar_get_variable_instance(old_syschar);
	oval_syschar_set_variable_instance(new_syschar, variable_instance);
	oval_syschar_set_variable_instance_hint(new_syschar, variable_instance);

	struct oval_message_iterator *old_messages = oval_syschar_get_messages(old_syschar);
	while (oval_message_iterator_has_more(old_messages)) {
		struct oval_message *old_message = oval_message_iterator_next(old_messages);
		struct oval_message *new_message = oval_message_clone(old_message);
		oval_syschar_add_message(new_syschar, new_message);
	}
	oval_message_iterator_free(old_messages);

	struct oval_sysitem_iterator *old_sysitems = oval_syschar_get_sysitem(old_syschar);
	while (oval_sysitem_iterator_has_more(old_sysitems)) {
		struct oval_sysitem *old_sysitem = oval_sysitem_iterator_next(old_sysitems);
		struct oval_sysitem *new_sysitem =
		    oval_syschar_model_get_sysitem(new_model, oval_sysitem_get_id(old_sysitem));
		if (new_sysitem == NULL)
			new_sysitem = oval_sysitem_clone(new_model, old_sysitem);
		oval_syschar_add_sysitem(new_syschar, new_sysitem);
	}
	oval_sysitem_iterator_free(old_sysitems);

	struct oval_variable_binding_iterator *old_bindings = oval_syschar_get_variable_bindings(old_syschar);
	while (oval_variable_binding_iterator_has_more(old_bindings)) {
		struct oval_variable_binding *old_binding = oval_variable_binding_iterator_next(old_bindings);
		struct oval_variable_binding *new_binding = oval_variable_binding_clone(old_binding, def_model);
		oval_syschar_add_variable_binding(new_syschar, new_binding);
	}
	oval_variable_binding_iterator_free(old_bindings);

	return new_syschar;
}

void oval_syschar_free(struct oval_syschar *syschar)
{
	if (syschar == NULL)
		return;

	oval_collection_free_items(syschar->messages, (oscap_destruct_func) oval_message_free);
	oval_collection_free_items(syschar->sysitem, NULL);	//sysitems are shared with syschar_model
	oval_collection_free_items(syschar->variable_bindings, (oscap_destruct_func) oval_variable_binding_free);

	syschar->messages = NULL;
	syschar->object = NULL;
	syschar->sysitem = NULL;
	syschar->variable_bindings = NULL;
	free(syschar);
}

static void add_oval_syschar_message(struct oval_syschar *syschar, struct oval_message *message) {
	__attribute__nonnull__(syschar);

	oval_collection_add(syschar->messages, message);
}

static void _oval_syschar_parse_subtag_consume_message(struct oval_message *message, void *syschar)
{
	add_oval_syschar_message((struct oval_syschar *)syschar, message);
}

struct oval_syschar_parse_subtag_varval_context {
	struct oval_syschar_model *model;
	struct oval_syschar *syschar;
};
static void _oval_syschar_parse_subtag_consume_variable_binding(struct oval_variable_binding *binding, void *user)
{
	struct oval_syschar_parse_subtag_varval_context *ctx = user;

	__attribute__nonnull__(ctx);

	oval_syschar_add_variable_binding(ctx->syschar, binding);
}

static int _oval_syschar_parse_subtag(xmlTextReaderPtr reader, struct oval_parser_context *context, void *client)
{

	__attribute__nonnull__(context);

	struct oval_syschar *syschar = client;
	char *tagname = (char *)xmlTextReaderLocalName(reader);
	char *namespace = (char *)xmlTextReaderNamespaceUri(reader);
	int return_code = 0;

	if (strcmp("message", tagname) == 0) {
		return_code = oval_message_parse_tag
		    (reader, context, (oscap_consumer_func) _oval_syschar_parse_subtag_consume_message, syschar);
	} else if (strcmp("variable_value", tagname) == 0) {
		struct oval_syschar_parse_subtag_varval_context ctx = { context->syschar_model, syschar };
		return_code = oval_variable_binding_parse_tag
		    (reader, context, &_oval_syschar_parse_subtag_consume_variable_binding, &ctx);
	} else if (strcmp("reference", tagname) == 0) {
		char *itemid = (char *)xmlTextReaderGetAttribute(reader, BAD_CAST "item_ref");
		struct oval_sysitem *sysitem = oval_syschar_model_get_new_sysitem(context->syschar_model, itemid);
		free(itemid);
		oval_syschar_add_sysitem(syschar, sysitem);
	}

	if (return_code != 0) {
                dW("Parsing of <%s> terminated by an error at line %d.", tagname, xmlTextReaderGetParserLineNumber(reader));
        }

	free(tagname);
	free(namespace);

	return return_code;
}

int oval_syschar_parse_tag(xmlTextReaderPtr reader, struct oval_parser_context *context, void *usr)
{
	__attribute__nonnull__(context);

	char *tagname = (char *)xmlTextReaderLocalName(reader);
	char *namespace = (char *)xmlTextReaderNamespaceUri(reader);
	int return_code = 0;

	int is_ovalsys = strcmp((const char *)OVAL_SYSCHAR_NAMESPACE, namespace) == 0;
	if (is_ovalsys && (strcmp(tagname, "object") == 0)) {
		char *object_id = (char *)xmlTextReaderGetAttribute(reader, BAD_CAST "id");
		struct oval_object *object = oval_definition_model_get_new_object(context->definition_model, object_id);
		free(object_id);

		oval_syschar_t *syschar = oval_syschar_model_get_new_syschar(context->syschar_model, object);
		char *flag = (char *)xmlTextReaderGetAttribute(reader, BAD_CAST "flag");
		oval_syschar_collection_flag_t flag_enum = oval_syschar_flag_parse(reader, "flag", SYSCHAR_FLAG_UNKNOWN);
		free(flag);
		oval_syschar_set_flag(syschar, flag_enum);

		return_code = oval_parser_parse_tag(reader, context, &_oval_syschar_parse_subtag, syschar);
	} else {
                dW("Skipping tag: %s", tagname);
                oval_parser_skip_tag(reader, context);
	}

        if (return_code != 0) {
                dW("Parsing of <%s> terminated by an error at line %d.", tagname, xmlTextReaderGetParserLineNumber(reader));
        }

	free(tagname);
	free(namespace);

	return return_code;
}

void oval_syschar_to_dom(struct oval_syschar *syschar, xmlDoc * doc, xmlNode * tag_parent)
{

	if (syschar) {
		xmlNs *ns_syschar = xmlSearchNsByHref(doc, tag_parent, OVAL_SYSCHAR_NAMESPACE);
		xmlNode *tag_syschar = xmlNewTextChild(tag_parent, ns_syschar, BAD_CAST "object", NULL);

		{		/*attributes */
			struct oval_object *object = oval_syschar_get_object(syschar);
			xmlNewProp(tag_syschar, BAD_CAST "id", BAD_CAST oval_object_get_id(object));
			char version[17];
			snprintf(version, sizeof(version), "%d", oval_object_get_version(object));
			xmlNewProp(tag_syschar, BAD_CAST "version", BAD_CAST version);
			oval_syschar_collection_flag_t flag = oval_syschar_get_flag(syschar);
			xmlNewProp(tag_syschar, BAD_CAST "flag", BAD_CAST oval_syschar_collection_flag_get_text(flag));

			int instance = oval_syschar_get_variable_instance(syschar);
			if (instance != 1 ||
				oval_syschar_get_variable_instance_hint(syschar) != instance) {
				char instance_att[10] = "";
				snprintf(instance_att, sizeof(instance_att), "%d", instance);
				xmlNewProp(tag_syschar, BAD_CAST "variable_instance", BAD_CAST instance_att);
			}
		}
		{		/*messages */
			struct oval_message_iterator *messages = oval_syschar_get_messages(syschar);
			while (oval_message_iterator_has_more(messages)) {
				struct oval_message *message = oval_message_iterator_next(messages);
				oval_message_to_dom(message, doc, tag_syschar);
			}
			oval_message_iterator_free(messages);
		}
		{		/*variable values */
			struct oval_variable_binding_iterator *bindings = oval_syschar_get_variable_bindings(syschar);
			while (oval_variable_binding_iterator_has_more(bindings)) {
				struct oval_variable_binding *binding = oval_variable_binding_iterator_next(bindings);
				oval_variable_binding_to_dom(binding, doc, tag_syschar);
			}
			oval_variable_binding_iterator_free(bindings);
		}
		{		/*references */
			struct oval_sysitem_iterator *sysitems = oval_syschar_get_sysitem(syschar);
			while (oval_sysitem_iterator_has_more(sysitems)) {
				struct oval_sysitem *sysitem = oval_sysitem_iterator_next(sysitems);
				xmlNode *tag_reference = xmlNewTextChild
				    (tag_syschar, ns_syschar, BAD_CAST "reference", NULL);
				xmlNewProp(tag_reference, BAD_CAST "item_ref", BAD_CAST oval_sysitem_get_id(sysitem));
			}
			oval_sysitem_iterator_free(sysitems);
		}
	}
}

int oval_syschar_get_variable_instance(const struct oval_syschar *syschar)
{
	__attribute__nonnull__(syschar);
	return syschar->variable_instance;
}

void oval_syschar_set_variable_instance(struct oval_syschar *syschar, int variable_instance_in)
{
	__attribute__nonnull__(syschar);
	syschar->variable_instance = variable_instance_in;
}

int oval_syschar_get_variable_instance_hint(const struct oval_syschar *syschar)
{
	__attribute__nonnull__(syschar);
	return syschar->variable_instance_hint;
}

void oval_syschar_set_variable_instance_hint(struct oval_syschar *syschar, int variable_instance_hint_in)
{
	__attribute__nonnull__(syschar);
	syschar->variable_instance_hint = variable_instance_hint_in;
}

const char *oval_syschar_get_id(const struct oval_syschar *syschar)
{
	__attribute__nonnull__(syschar);
	struct oval_object *obj = oval_syschar_get_object((struct oval_syschar *) syschar);
	return (obj == NULL) ? NULL : oval_object_get_id(obj);
}
