/**
 * @file oval_criteriaNode.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>
 *      "Peter Vrabec" <pvrabec@redhat.com>
 */

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

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

#include "oval_definitions_impl.h"
#include "adt/oval_collection_impl.h"
#include "oval_agent_api_impl.h"
#include "common/util.h"
#include "common/debug_priv.h"
#include "common/_error.h"

/***************************************************************************/
/* Variable definitions
 * */

typedef struct oval_criteria_node {
	struct oval_definition_model *model;
	oval_criteria_node_type_t type;
	int negate;
	char *comment;
	int applicability_check;
} oval_criteria_node_t;

typedef struct oval_criteria_node_CRITERIA {
	struct oval_definition_model *model;
	oval_criteria_node_type_t type;
	int negate;
	char *comment;
	int applicability_check;
	oval_operator_t operator;	/*type==NODETYPE_CRITERIA */
	struct oval_collection *subnodes;	/*type==NODETYPE_CRITERIA */
} oval_criteria_node_CRITERIA_t;

typedef struct oval_criteria_node_CRITERION {
	struct oval_definition_model *model;
	oval_criteria_node_type_t type;
	int negate;
	char *comment;
	int applicability_check;
	struct oval_test *test;	/*type==NODETYPE_CRITERION */
} oval_criteria_node_CRITERION_t;

typedef struct oval_criteria_node_EXTENDDEF {
	struct oval_definition_model *model;
	oval_criteria_node_type_t type;
	int negate;
	char *comment;
	int applicability_check;
	struct oval_definition *definition;	/*type==NODETYPE_EXTENDDEF */
} oval_criteria_node_EXTENDDEF_t;

/* End of variable definitions
 * */
/***************************************************************************/

bool oval_criteria_node_iterator_has_more(struct oval_criteria_node_iterator
					  *oc_criteria_node)
{
	return oval_collection_iterator_has_more((struct oval_iterator *)
						 oc_criteria_node);
}

struct oval_criteria_node *oval_criteria_node_iterator_next(struct
							    oval_criteria_node_iterator
							    *oc_criteria_node)
{
	return (struct oval_criteria_node *)
	    oval_collection_iterator_next((struct oval_iterator *)
					  oc_criteria_node);
}

void oval_criteria_node_iterator_free(struct
				      oval_criteria_node_iterator
				      *oc_criteria_node)
{
	oval_collection_iterator_free((struct oval_iterator *)
				      oc_criteria_node);
}

oval_criteria_node_type_t oval_criteria_node_get_type(struct oval_criteria_node
						      *node)
{
	__attribute__nonnull__(node);

	return ((struct oval_criteria_node *)node)->type;
}

bool oval_criteria_node_get_negate(struct oval_criteria_node *node)
{
	__attribute__nonnull__(node);

	return ((struct oval_criteria_node *)node)->negate;
}

bool oval_criteria_node_get_applicability_check(struct oval_criteria_node *node)
{
	__attribute__nonnull__(node);

	return ((struct oval_criteria_node *)node)->applicability_check;
}

char *oval_criteria_node_get_comment(struct oval_criteria_node *node)
{
	__attribute__nonnull__(node);

	return ((struct oval_criteria_node *)node)->comment;
}

oval_operator_t oval_criteria_node_get_operator(struct oval_criteria_node *node)
{
	__attribute__nonnull__(node);

	return (node->type == OVAL_NODETYPE_CRITERIA)
	    ? ((struct oval_criteria_node_CRITERIA *)node)->operator : OVAL_OPERATOR_UNKNOWN;
}

struct oval_criteria_node_iterator *oval_criteria_node_get_subnodes(struct
								    oval_criteria_node
								    *node)
{
	__attribute__nonnull__(node);

	struct oval_criteria_node_iterator *subnodes = NULL;
	if (node->type == OVAL_NODETYPE_CRITERIA) {
		struct oval_criteria_node_CRITERIA *criteria = (struct oval_criteria_node_CRITERIA *)node;
		subnodes = (struct oval_criteria_node_iterator *)
		    oval_collection_iterator(criteria->subnodes);
	}
	return subnodes;
}

struct oval_test *oval_criteria_node_get_test(struct oval_criteria_node *node)
{
	__attribute__nonnull__(node);

	/* type == NODETYPE_CRITERION */
	return (node->type == OVAL_NODETYPE_CRITERION)
	    ? ((struct oval_criteria_node_CRITERION *)node)->test : NULL;
}

struct oval_definition *oval_criteria_node_get_definition(struct oval_criteria_node *node) {
	__attribute__nonnull__(node);

	return (node->type == OVAL_NODETYPE_EXTENDDEF)
	    ? ((struct oval_criteria_node_EXTENDDEF *)node)->definition : NULL;
}

struct oval_criteria_node *oval_criteria_node_new(struct oval_definition_model *model, oval_criteria_node_type_t type)
{
	struct oval_criteria_node *node;
	switch (type) {
	case OVAL_NODETYPE_CRITERIA:{
			node = (struct oval_criteria_node *)
			    calloc(1, sizeof(oval_criteria_node_CRITERIA_t));
			if (node == NULL)
				return NULL;

			((struct oval_criteria_node_CRITERIA *)node)->operator = OVAL_OPERATOR_UNKNOWN;
			((struct oval_criteria_node_CRITERIA *)node)->subnodes = oval_collection_new();
		} break;
	case OVAL_NODETYPE_CRITERION:{
			node = (struct oval_criteria_node *)
			    calloc(1, sizeof(oval_criteria_node_CRITERION_t));
			if (node == NULL)
				return NULL;

			((struct oval_criteria_node_CRITERION *)node)->test = NULL;
		} break;
	case OVAL_NODETYPE_EXTENDDEF:{
			node = (struct oval_criteria_node *)
			    calloc(1, sizeof(oval_criteria_node_EXTENDDEF_t));
			if (node == NULL)
				return NULL;

			((struct oval_criteria_node_EXTENDDEF *)node)->definition = NULL;
		} break;
	case OVAL_NODETYPE_UNKNOWN:
	default:		/* TODO: Is constructor of criteria_node with unknown type valid ? */
		return NULL;
	}
	node->type = type;
	node->negate = 0;
	node->comment = NULL;
	node->applicability_check = 0;
	node->model = model;
	return node;
}

struct oval_criteria_node *oval_criteria_node_clone
    (struct oval_definition_model *new_model, struct oval_criteria_node *old_node) {
	__attribute__nonnull__(old_node);

	struct oval_criteria_node *new_node = oval_criteria_node_new(new_model, old_node->type);
	if (new_node == NULL)
		return NULL;

	char *cn_comment = oval_criteria_node_get_comment(old_node);
	oval_criteria_node_set_comment(new_node, cn_comment);
	int negate = oval_criteria_node_get_negate(old_node);
	oval_criteria_node_set_negate(new_node, negate);
	switch (new_node->type) {
	case OVAL_NODETYPE_CRITERIA:{
			oval_operator_t operator = oval_criteria_node_get_operator(old_node);
			oval_criteria_node_set_operator(new_node, operator);
			struct oval_criteria_node_iterator *subnodes = oval_criteria_node_get_subnodes(old_node);
			while (oval_criteria_node_iterator_has_more(subnodes)) {
				struct oval_criteria_node *subnode = oval_criteria_node_iterator_next(subnodes);
				oval_criteria_node_add_subnode(new_node, oval_criteria_node_clone(new_model, subnode));
			}
			oval_criteria_node_iterator_free(subnodes);
		} break;
	case OVAL_NODETYPE_EXTENDDEF:{
			struct oval_definition *new_definition = NULL;
			struct oval_definition *old_definition = oval_criteria_node_get_definition(old_node);
			if (old_definition)
				new_definition = oval_definition_clone(new_model, old_definition);
			oval_criteria_node_set_definition(new_node, new_definition);
		}
		break;
	case OVAL_NODETYPE_CRITERION:{
			struct oval_test *new_test = NULL;
			struct oval_test *old_test = oval_criteria_node_get_test(old_node);
			if (old_test)
				new_test = oval_test_clone(new_model, old_test);
			oval_criteria_node_set_test(new_node, new_test);
		}
		break;
	default:		/*NOOP -> this will never happen, because oval_criteria_node_new will return NULL 
				   in case of unknown nodetype */ ;
	}

	return new_node;
}

void oval_criteria_node_free(struct oval_criteria_node *node)
{
	__attribute__nonnull__(node);

	oval_criteria_node_type_t type = node->type;
	switch (type) {
	case OVAL_NODETYPE_CRITERIA:{
			struct oval_criteria_node_CRITERIA *criteria = (struct oval_criteria_node_CRITERIA *)node;
			oval_collection_free_items(criteria->subnodes, (oscap_destruct_func) oval_criteria_node_free);
			criteria->subnodes = NULL;
		} break;
	case OVAL_NODETYPE_CRITERION:{
			//NOOP
		}
		break;
	case OVAL_NODETYPE_EXTENDDEF:{
			//NOOP
		}
	case OVAL_NODETYPE_UNKNOWN:{
			//NOOP
		}
	}
	if (node->comment != NULL) {
		free(node->comment);
	}
	node->comment = NULL;
	free(node);
}

void oval_criteria_set_node_type(struct oval_criteria_node *node, oval_criteria_node_type_t type)
{
	__attribute__nonnull__(node);

	node->type = type;
}

void oval_criteria_node_set_negate(struct oval_criteria_node *node, bool negate)
{
	__attribute__nonnull__(node);
	node->negate = negate;
}

void oval_criteria_node_set_applicability_check(struct oval_criteria_node *node, bool applicability_check)
{
	__attribute__nonnull__(node);
	node->applicability_check = applicability_check;
}

void oval_criteria_node_set_comment(struct oval_criteria_node *node, char *comm)
{
	__attribute__nonnull__(node);
	if (node->comment != NULL)
		free(node->comment);
	node->comment = (comm == NULL) ? NULL : oscap_strdup(comm);
}

void oval_criteria_node_set_operator(struct oval_criteria_node *node, oval_operator_t op)
{
	__attribute__nonnull__(node);
	if (node->type == OVAL_NODETYPE_CRITERIA) {
		struct oval_criteria_node_CRITERIA *criteria = (struct oval_criteria_node_CRITERIA *)node;
		criteria->operator = op;
	}
}

void oval_criteria_node_add_subnode(struct oval_criteria_node *node, struct oval_criteria_node *subnode)
{
	__attribute__nonnull__(node);
	if (node->type == OVAL_NODETYPE_CRITERIA) {
		struct oval_criteria_node_CRITERIA *criteria = (struct oval_criteria_node_CRITERIA *)node;
		oval_collection_add(criteria->subnodes, (void *)subnode);
	}
}

void oval_criteria_node_set_test(struct oval_criteria_node *node, struct oval_test *test)
{
	__attribute__nonnull__(node);
	if (node->type == OVAL_NODETYPE_CRITERION) {
		struct oval_criteria_node_CRITERION *criterion = (struct oval_criteria_node_CRITERION *)node;
		criterion->test = test;
	}
}

void oval_criteria_node_set_definition(struct oval_criteria_node *node, struct oval_definition *definition)
{
	__attribute__nonnull__(node);
	if (node->type == OVAL_NODETYPE_EXTENDDEF) {
		struct oval_criteria_node_EXTENDDEF *extenddef = (struct oval_criteria_node_EXTENDDEF *)node;
		extenddef->definition = definition;
	}
}

static void _oval_criteria_subnode_consume(struct oval_criteria_node *subnode, void *criteria)
{
	oval_criteria_node_add_subnode((struct oval_criteria_node *) criteria, subnode);
}

static int _oval_criteria_subnode_consumer(xmlTextReaderPtr reader, struct oval_parser_context *context, void *user)
{
	struct oval_criteria_node_CRITERIA *criteria = (struct oval_criteria_node_CRITERIA *)user;
	int return_code = oval_criteria_parse_tag(reader, context, &_oval_criteria_subnode_consume, criteria);
	return return_code;
}

//typedef void (*oval_criteria_consumer)(struct oval_criteria_node *node, void*);
int oval_criteria_parse_tag(xmlTextReaderPtr reader, struct oval_parser_context *context, oval_criteria_consumer consumer, void *user)
{
	char *tagname = (char *)xmlTextReaderLocalName(reader);
	xmlChar *namespace = xmlTextReaderNamespaceUri(reader);
	oval_criteria_node_type_t type = OVAL_NODETYPE_UNKNOWN;
	if (strcmp(tagname, "criteria") == 0)
		type = OVAL_NODETYPE_CRITERIA;
	else if (strcmp(tagname, "criterion") == 0)
		type = OVAL_NODETYPE_CRITERION;
	else if (strcmp(tagname, "extend_definition") == 0)
		type = OVAL_NODETYPE_EXTENDDEF;
	int return_code;
	if (type != OVAL_NODETYPE_UNKNOWN) {
		assert(context != NULL);	/* This is not asserted as attribute, because we
						   can pass NULL pointer in case of OVAL_NODETYPE_UNKNOWN */
		struct oval_criteria_node *node = oval_criteria_node_new(context->definition_model, type);
		node->type = type;
		char *comm = (char *)xmlTextReaderGetAttribute(reader, BAD_CAST "comment");
		if (comm != NULL) {
			oval_criteria_node_set_comment(node, comm);
			free(comm);
			comm = NULL;
		}
		oval_criteria_node_set_negate(node, oval_parser_boolean_attribute(reader, "negate", 0));
		oval_criteria_node_set_applicability_check(node, oval_parser_boolean_attribute(reader, "applicability_check", 0));
		return_code = 0;
		switch (oval_criteria_node_get_type(node)) {
		case OVAL_NODETYPE_CRITERIA:{
				struct oval_criteria_node_CRITERIA *criteria =
				    (struct oval_criteria_node_CRITERIA *)node;
				oval_operator_t operator = oval_operator_parse(reader, "operator", OVAL_OPERATOR_AND);
				oval_criteria_node_set_operator((struct oval_criteria_node *)criteria, operator);
				return_code = oval_parser_parse_tag(reader, context, &_oval_criteria_subnode_consumer, criteria);
			} break;
		case OVAL_NODETYPE_CRITERION:{
				char *test_ref = (char *)xmlTextReaderGetAttribute(reader, BAD_CAST "test_ref");
				struct oval_definition_model *model = context->definition_model;
				struct oval_test *test = oval_definition_model_get_new_test(model, test_ref);
				free(test_ref);
				test_ref = NULL;
				oval_criteria_node_set_test(node, test);
			} break;
		case OVAL_NODETYPE_EXTENDDEF:{
				char *definition_ref = (char *)xmlTextReaderGetAttribute(reader, BAD_CAST "definition_ref");
				struct oval_definition_model *model = context->definition_model;
				struct oval_definition *definition = oval_definition_model_get_new_definition(model, definition_ref);
				oval_criteria_node_set_definition(node, definition);
				free(definition_ref);
				definition_ref = NULL;
			}
		case OVAL_NODETYPE_UNKNOWN:
			break;
		}
		//oval_parser_parse_tag(reader, context,&_oval_criteria_parse_tag,node);
		(*consumer) (node, user);
	} else {
		return_code = 1;
		dW("Invalid node type: OVAL_NODETYPE_UNKNOWN.");
		oval_parser_skip_tag(reader, context);
	}
	free(tagname);
	free(namespace);
	return return_code;
}

static xmlNode *_oval_CRITERIA_to_dom(struct oval_criteria_node *cnode, xmlDoc * doc, xmlNode * parent) {
	xmlNs *ns_definitions = xmlSearchNsByHref(doc, parent, OVAL_DEFINITIONS_NAMESPACE);
	xmlNode *criteria_node = xmlNewTextChild(parent, ns_definitions, BAD_CAST "criteria", NULL);

	oval_operator_t operator = oval_criteria_node_get_operator(cnode);
	if (operator!= OVAL_OPERATOR_AND)
		xmlNewProp(criteria_node, BAD_CAST "operator", BAD_CAST oval_operator_get_text(operator));

	struct oval_criteria_node_iterator *subnodes = oval_criteria_node_get_subnodes(cnode);
	while (oval_criteria_node_iterator_has_more(subnodes)) {
		struct oval_criteria_node *subnode = oval_criteria_node_iterator_next(subnodes);
		oval_criteria_node_to_dom(subnode, doc, criteria_node);
	}
	oval_criteria_node_iterator_free(subnodes);

	return criteria_node;
}

static xmlNode *_oval_CRITERION_to_dom(struct oval_criteria_node *cnode, xmlDoc * doc, xmlNode * parent) {
	xmlNs *ns_definitions = xmlSearchNsByHref(doc, parent, OVAL_DEFINITIONS_NAMESPACE);
	xmlNode *criterion_node = xmlNewTextChild(parent, ns_definitions, BAD_CAST "criterion", NULL);

	struct oval_test *test = oval_criteria_node_get_test(cnode);
	char *test_ref = oval_test_get_id(test);
	xmlNewProp(criterion_node, BAD_CAST "test_ref", BAD_CAST test_ref);

	return criterion_node;
}

static xmlNode *_oval_EXTENDDEF_to_dom(struct oval_criteria_node *cnode, xmlDoc * doc, xmlNode * parent) {
	xmlNs *ns_definitions = xmlSearchNsByHref(doc, parent, OVAL_DEFINITIONS_NAMESPACE);
	xmlNode *extenddef_node = xmlNewTextChild(parent, ns_definitions, BAD_CAST "extend_definition", NULL);

	struct oval_definition *definition = oval_criteria_node_get_definition(cnode);
	char *definition_ref = oval_definition_get_id(definition);
	xmlNewProp(extenddef_node, BAD_CAST "definition_ref", BAD_CAST definition_ref);

	return extenddef_node;
}

xmlNode *oval_criteria_node_to_dom(struct oval_criteria_node * cnode, xmlDoc * doc, xmlNode * parent) {
	xmlNode *criteria_node;
	switch (oval_criteria_node_get_type(cnode)) {
	case OVAL_NODETYPE_CRITERIA:
		criteria_node = _oval_CRITERIA_to_dom(cnode, doc, parent);
		break;
	case OVAL_NODETYPE_CRITERION:
		criteria_node = _oval_CRITERION_to_dom(cnode, doc, parent);
		break;
	case OVAL_NODETYPE_EXTENDDEF:
		criteria_node = _oval_EXTENDDEF_to_dom(cnode, doc, parent);
		break;
	default:
		criteria_node = NULL;
		break;
	}

	bool negate = oval_criteria_node_get_negate(cnode);
	if (negate)
		xmlNewProp(criteria_node, BAD_CAST "negate", BAD_CAST "true");

	bool applicability_check = oval_criteria_node_get_applicability_check(cnode);
	if (applicability_check)
		xmlNewProp(criteria_node, BAD_CAST "applicability_check", BAD_CAST "true");

	char *comm = oval_criteria_node_get_comment(cnode);
	if (comm)
		xmlNewProp(criteria_node, BAD_CAST "comment", BAD_CAST comm);

	return criteria_node;
}
