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

/*
 * Copyright 2009--2014 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 <string.h>
#include <time.h>

#include "oval_definitions_impl.h"
#include "oval_agent_api_impl.h"
#include "oval_parser_impl.h"
#include "adt/oval_string_map_impl.h"
#include "oval_system_characteristics_impl.h"
#if defined(OVAL_PROBES_ENABLED)
# include "oval_probe_impl.h"
#endif
#include "common/util.h"
#include "common/debug_priv.h"
#include "common/_error.h"
#include "common/elements.h"
#include "oscap_source.h"
#include "source/oscap_source_priv.h"

typedef struct oval_definition_model {
	struct oval_generator *generator;
	struct oval_string_map *definition_map;
	struct oval_string_map *test_map;
	struct oval_string_map *object_map;
	struct oval_string_map *state_map;
	struct oval_string_map *variable_map;
	struct oval_collection *bound_variable_models;
        char *schema;
	struct oval_string_map *vardef_map;		///< look-up table for efficient @variable_instance processing
} oval_definition_model_t;

/* failed   - NULL
 * success  - oval_definition_model
 * */
struct oval_definition_model *oval_definition_model_new()
{
	oval_definition_model_t *newmodel = (oval_definition_model_t *) malloc(sizeof(oval_definition_model_t));
	if (newmodel == NULL)
		return NULL;

	newmodel->generator = oval_generator_new();
	newmodel->definition_map = oval_string_map_new();
	newmodel->object_map = oval_string_map_new();
	newmodel->state_map = oval_string_map_new();
	newmodel->test_map = oval_string_map_new();
	newmodel->variable_map = oval_string_map_new();
	newmodel->bound_variable_models = NULL;
	newmodel->schema = oscap_strdup(OVAL_DEF_SCHEMA_LOCATION);
	newmodel->vardef_map = NULL;

	return newmodel;
}

typedef void *(*_oval_clone_func) (void *, struct oval_definition_model *);

static void _oval_definition_model_clone(struct oval_string_map *oldmap,
					 struct oval_definition_model *newmodel,
					 _oval_clone_func cloner)
{
	struct oval_string_iterator *keys = (struct oval_string_iterator *)oval_string_map_keys(oldmap);
	while (oval_string_iterator_has_more(keys)) {
		char *key = oval_string_iterator_next(keys);
		void *olditem = oval_string_map_get_value(oldmap, key);
		(*cloner) (newmodel, olditem);
	}
	oval_string_iterator_free(keys);
}

/* failed   - NULL
 * success  - oval_definition_model
 * */
struct oval_definition_model *oval_definition_model_clone(struct oval_definition_model *oldmodel)
{
	__attribute__nonnull__(oldmodel);

	struct oval_definition_model *newmodel = oval_definition_model_new();
	if (newmodel == NULL)
		return NULL;

	_oval_definition_model_clone
	    (oldmodel->definition_map, newmodel, (_oval_clone_func) oval_definition_clone);
	_oval_definition_model_clone
	    (oldmodel->object_map, newmodel, (_oval_clone_func) oval_object_clone);
	_oval_definition_model_clone(oldmodel->state_map, newmodel, (_oval_clone_func) oval_state_clone);
	_oval_definition_model_clone(oldmodel->test_map, newmodel, (_oval_clone_func) oval_test_clone);
	_oval_definition_model_clone
	    (oldmodel->variable_map, newmodel, (_oval_clone_func) oval_variable_clone);
        newmodel->schema = oscap_strdup(oldmodel->schema);
	newmodel->vardef_map = NULL;
	return newmodel;
}

void oval_definition_model_free(struct oval_definition_model *model)
{
	if (model != NULL) {
		oval_string_map_free(model->definition_map, (oscap_destruct_func) oval_definition_free);
		oval_string_map_free(model->object_map, (oscap_destruct_func) oval_object_free);
		oval_string_map_free(model->state_map, (oscap_destruct_func) oval_state_free);
		oval_string_map_free(model->test_map, (oscap_destruct_func) oval_test_free);
		oval_string_map_free(model->variable_map, (oscap_destruct_func) oval_variable_free);
		if (model->vardef_map != NULL)
			oval_string_map_free(model->vardef_map, (oscap_destruct_func) oval_string_map_free0);
		if (model->bound_variable_models)
			oval_collection_free_items(model->bound_variable_models,
					   (oscap_destruct_func) oval_variable_model_free);

	        if (model->schema != NULL)
			free(model->schema);

		oval_generator_free(model->generator);
		free(model);
	}
}

struct oval_generator *oval_definition_model_get_generator(struct oval_definition_model *model)
{
	return model->generator;
}

void oval_definition_model_set_generator(struct oval_definition_model *model, struct oval_generator *generator)
{
	oval_generator_free(model->generator);
	model->generator = generator;
}

const char * oval_definition_model_get_schema(struct oval_definition_model * model)
{
        __attribute__nonnull__(model);

        return model->schema;
}

oval_schema_version_t oval_definition_model_get_core_schema_version(struct oval_definition_model *model)
{
	if (model == NULL || model->generator == NULL) {
		return OVAL_SCHEMA_VERSION_INVALID;
	}
	const char *version = oval_generator_get_core_schema_version(model->generator);
	return oval_schema_version_from_cstr(version);
}

oval_schema_version_t oval_definition_model_get_platform_schema_version(struct oval_definition_model *model, const char *platform)
{
	if (model == NULL || model->generator == NULL) {
		return OVAL_SCHEMA_VERSION_INVALID;
	}
	const char *version = oval_generator_get_platform_schema_version(model->generator, platform);
	return oval_schema_version_from_cstr(version);
}

void oval_definition_model_add_definition(struct oval_definition_model *model, struct oval_definition *definition)
{
	__attribute__nonnull__(model);
	char *key = oval_definition_get_id(definition);
	oval_string_map_put(model->definition_map, key, (void *)definition);
}

void oval_definition_model_set_schema(struct oval_definition_model *model, const char *version)
{
	__attribute__nonnull__(model);
	model->schema = oscap_strdup(version);
}

void oval_definition_model_add_test(struct oval_definition_model *model, struct oval_test *test)
{
	__attribute__nonnull__(model);
	char *key = oval_test_get_id(test);
	oval_string_map_put(model->test_map, key, (void *)test);
}

void oval_definition_model_add_object(struct oval_definition_model *model, struct oval_object *object)
{
	__attribute__nonnull__(model);
	char *key = oval_object_get_id(object);
	oval_string_map_put(model->object_map, key, (void *)object);
}

void oval_definition_model_add_state(struct oval_definition_model *model, struct oval_state *state)
{
	__attribute__nonnull__(model);
	char *key = oval_state_get_id(state);
	oval_string_map_put(model->state_map, key, (void *)state);
}

void oval_definition_model_add_variable(struct oval_definition_model *model, struct oval_variable *variable)
{
	__attribute__nonnull__(model);
	char *key = oval_variable_get_id(variable);
	oval_string_map_put(model->variable_map, key, (void *)variable);
}

static inline int _oval_definition_model_merge_source(struct oval_definition_model *model, struct oscap_source *source)
{
	/* setup context */
	struct oval_parser_context context;
	context.reader = oscap_source_get_xmlTextReader(source);
	if (context.reader == NULL) {
		return -1;
	}
	context.definition_model = model;
	context.user_data = NULL;
	/* jump into oval_definitions */
	while (xmlTextReaderRead(context.reader) == 1
		&& xmlTextReaderNodeType(context.reader) != XML_READER_TYPE_ELEMENT) ;
	/* start parsing */
	int ret = oval_definition_model_parse(context.reader, &context);
	xmlFreeTextReader(context.reader);
	return ret;
}

struct oval_definition_model *oval_definition_model_import_source(struct oscap_source *source)
{
        struct oval_definition_model *model = oval_definition_model_new();
	int ret = _oval_definition_model_merge_source(model, source);
        if (ret == -1 ) {
                oval_definition_model_free(model);
                model = NULL;
        }
	return model;
}

struct oval_definition *oval_definition_model_get_definition(struct oval_definition_model *model, const char *key)
{
	__attribute__nonnull__(model);

	return (struct oval_definition *)oval_string_map_get_value(model->definition_map, key);
}

struct oval_test *oval_definition_model_get_test(struct oval_definition_model *model, const char *key)
{
	__attribute__nonnull__(model);

	return (struct oval_test *)oval_string_map_get_value(model->test_map, key);
}

struct oval_object *oval_definition_model_get_object(struct oval_definition_model *model, const char *key)
{
	__attribute__nonnull__(model);

	return (struct oval_object *)oval_string_map_get_value(model->object_map, key);
}

struct oval_state *oval_definition_model_get_state(struct oval_definition_model *model, const char *key)
{
	__attribute__nonnull__(model);

	return (struct oval_state *)oval_string_map_get_value(model->state_map, key);
}

struct oval_variable *oval_definition_model_get_variable(struct oval_definition_model *model, const char *key)
{
	__attribute__nonnull__(model);

	return (struct oval_variable *)oval_string_map_get_value(model->variable_map, key);
}

int oval_definition_model_bind_variable_model(struct oval_definition_model *defmodel,
					       struct oval_variable_model *varmodel)
{
	struct oval_string_iterator *evar_id_itr;

	if (!defmodel->bound_variable_models)
		defmodel->bound_variable_models = oval_collection_new();

	oval_collection_add(defmodel->bound_variable_models, varmodel);

	/* todo: keep reference count for each variable model if it can be bound to multiple definition models */

	evar_id_itr = oval_variable_model_get_variable_ids(varmodel);
	while (oval_string_iterator_has_more(evar_id_itr)) {
		char *evar_id;
		struct oval_variable *var;

		evar_id = oval_string_iterator_next(evar_id_itr);
		var = oval_definition_model_get_variable(defmodel, evar_id);
		if (!var)
			continue;

		oval_variable_bind_ext_var(var, varmodel, evar_id);
	}
	oval_string_iterator_free(evar_id_itr);

	return 0;
}

struct oval_variable_model_iterator *oval_definition_model_get_variable_models(struct oval_definition_model *model)
{
	if (model->bound_variable_models)
		return (struct oval_variable_model_iterator *) oval_collection_iterator(model->bound_variable_models);
	else
		return (struct oval_variable_model_iterator *) oval_collection_iterator_new();
}

void oval_definition_model_clear_external_variables(struct oval_definition_model *model)
{
	struct oval_variable_iterator *vars_itr;

	vars_itr = oval_definition_model_get_variables(model);
	while (oval_variable_iterator_has_more(vars_itr)) {
		struct oval_variable *var;

		var = oval_variable_iterator_next(vars_itr);
		if (oval_variable_get_type(var) != OVAL_VARIABLE_EXTERNAL)
			continue;

		oval_variable_clear_values(var);
	}
	oval_variable_iterator_free(vars_itr);
}

struct oval_definition_iterator *oval_definition_model_get_definitions(struct oval_definition_model
								       *model)
{
	__attribute__nonnull__(model);

	struct oval_definition_iterator *iterator =
	    (struct oval_definition_iterator *)oval_string_map_values(model->definition_map);

	return iterator;
}

struct oval_string_iterator *oval_definition_model_get_definitions_dependent_on_variable(struct oval_definition_model *model, struct oval_variable *variable)
{
	__attribute__nonnull__(model);
	__attribute__nonnull__(variable);

	if (model->vardef_map == NULL)
		model->vardef_map = oval_definition_model_build_vardef_mapping(model);

	struct oval_string_map *def_list = (struct oval_string_map *) oval_string_map_get_value(model->vardef_map, oval_variable_get_id(variable));
	return (struct oval_string_iterator *) (def_list != NULL ?
		oval_string_map_keys(def_list) : oval_collection_iterator_new());
}

struct oval_test_iterator *oval_definition_model_get_tests(struct oval_definition_model *model)
{
	__attribute__nonnull__(model);

	struct oval_test_iterator *iterator = (struct oval_test_iterator *)oval_string_map_values(model->test_map);

	return iterator;
}

struct oval_object_iterator *oval_definition_model_get_objects(struct oval_definition_model *model)
{
	__attribute__nonnull__(model);

	struct oval_object_iterator *iterator =
	    (struct oval_object_iterator *)oval_string_map_values(model->object_map);

	return iterator;
}

struct oval_state_iterator *oval_definition_model_get_states(struct oval_definition_model *model)
{
	__attribute__nonnull__(model);

	struct oval_state_iterator *iterator = (struct oval_state_iterator *)oval_string_map_values(model->state_map);

	return iterator;
}

struct oval_variable_iterator *oval_definition_model_get_variables(struct oval_definition_model
								   *model)
{
	__attribute__nonnull__(model);

	struct oval_variable_iterator *iterator =
	    (struct oval_variable_iterator *)oval_string_map_values(model->variable_map);

	return iterator;
}

struct oval_definition *oval_definition_model_get_new_definition(struct oval_definition_model *model, const char *id)
{
	struct oval_definition *definition = oval_definition_model_get_definition(model, id);
	if (definition == NULL) {
		definition = oval_definition_new(model, id);
	}
	return definition;
}

struct oval_variable *oval_definition_model_get_new_variable(struct oval_definition_model *model, const char *id, oval_variable_type_t type)
{
	struct oval_variable *variable = oval_definition_model_get_variable(model, id);
	if (variable == NULL) {
		variable = oval_variable_new(model, id, type);
	} else if (type != OVAL_VARIABLE_UNKNOWN) {
		oval_variable_set_type(variable, type);
	}
	return variable;
}

struct oval_state *oval_definition_model_get_new_state(struct oval_definition_model *model, const char *id)
{
	struct oval_state *state = oval_definition_model_get_state(model, id);
	if (state == NULL) {
		state = oval_state_new(model, id);
	}
	return state;
}

struct oval_object *oval_definition_model_get_new_object(struct oval_definition_model *model, const char *id)
{
	struct oval_object *object = oval_definition_model_get_object(model, id);
	if (object == NULL) {
		object = oval_object_new(model, id);
	}
	return object;
}

struct oval_test *oval_definition_model_get_new_test(struct oval_definition_model *model, const char *id)
{
	struct oval_test *test = oval_definition_model_get_test(model, id);
	if (test == NULL) {
		test = oval_test_new(model, id);
	}
	return test;
}

xmlNode *oval_definition_model_to_dom(struct oval_definition_model *definition_model, xmlDocPtr doc, xmlNode * parent)
{

	xmlNodePtr root_node = NULL;

	if (parent) { /* result file */
		root_node = xmlNewTextChild(parent, NULL, BAD_CAST OVAL_ROOT_ELM_DEFINITIONS, NULL);
	} else {      /* definitions file, we are the root */
		root_node = xmlNewNode(NULL, BAD_CAST OVAL_ROOT_ELM_DEFINITIONS);
		xmlDocSetRootElement(doc, root_node);
	}
	xmlNewNsProp(root_node, lookup_xsi_ns(doc), BAD_CAST "schemaLocation", BAD_CAST definition_model->schema);

	xmlNs *ns_common = xmlNewNs(root_node, OVAL_COMMON_NAMESPACE, BAD_CAST "oval");
	xmlNs *ns_unix = xmlNewNs(root_node, OVAL_DEFINITIONS_UNIX_NS, BAD_CAST "unix-def");
	xmlNs *ns_ind = xmlNewNs(root_node, OVAL_DEFINITIONS_IND_NS, BAD_CAST "ind-def");
	xmlNs *ns_lin = xmlNewNs(root_node, OVAL_DEFINITIONS_LIN_NS, BAD_CAST "lin-def");
	xmlNs *ns_win = xmlNewNs(root_node, OVAL_DEFINITIONS_WIN_NS, BAD_CAST "win-def");
	xmlNs *ns_mac = xmlNewNs(root_node, OVAL_DEFINITIONS_MAC_NS, BAD_CAST "mac-def");
	xmlNs *ns_defntns = xmlNewNs(root_node, OVAL_DEFINITIONS_NAMESPACE, NULL);

	xmlSetNs(root_node, ns_common);
	xmlSetNs(root_node, ns_unix);
	xmlSetNs(root_node, ns_ind);
	xmlSetNs(root_node, ns_lin);
	xmlSetNs(root_node, ns_win);
	xmlSetNs(root_node, ns_mac);
	xmlSetNs(root_node, ns_defntns);

	/* Always report the generator */
	oval_generator_to_dom(definition_model->generator, doc, root_node);

	/* Report definitions */
	struct oval_definition_iterator *definitions = oval_definition_model_get_definitions(definition_model);
	if (oval_definition_iterator_has_more(definitions)) {
		xmlNode *definitions_node = NULL;
		while(oval_definition_iterator_has_more(definitions)) {
			struct oval_definition *definition = oval_definition_iterator_next(definitions);
			if (definitions_node == NULL) {
				definitions_node = xmlNewTextChild(root_node, ns_defntns, BAD_CAST "definitions", NULL);
			}
			oval_definition_to_dom(definition, doc, definitions_node);
		}
	}
        oval_definition_iterator_free(definitions);

	/* Report tests */
	struct oval_test_iterator *tests = oval_definition_model_get_tests(definition_model);
	if (oval_test_iterator_has_more(tests)) {
		xmlNode *tests_node = xmlNewTextChild(root_node, ns_defntns, BAD_CAST "tests", NULL);
		while (oval_test_iterator_has_more(tests)) {
			struct oval_test *test = oval_test_iterator_next(tests);
			oval_test_to_dom(test, doc, tests_node);
		}
	}
	oval_test_iterator_free(tests);

	/* Report objects */
	struct oval_object_iterator *objects = oval_definition_model_get_objects(definition_model);
	if (oval_object_iterator_has_more(objects)) {
		xmlNode *objects_node = xmlNewTextChild(root_node, ns_defntns, BAD_CAST "objects", NULL);
		while(oval_object_iterator_has_more(objects)) {
			struct oval_object *object = oval_object_iterator_next(objects);
			if (oval_object_get_base_obj(object))
				/* Skip internal objects */
				continue;
			oval_object_to_dom(object, doc, objects_node);
		}
	}
	oval_object_iterator_free(objects);

	/* Report states */
	struct oval_state_iterator *states = oval_definition_model_get_states(definition_model);
	if (oval_state_iterator_has_more(states)) {
		xmlNode *states_node = xmlNewTextChild(root_node, ns_defntns, BAD_CAST "states", NULL);
		while (oval_state_iterator_has_more(states)) {
			struct oval_state *state = oval_state_iterator_next(states);
			oval_state_to_dom(state, doc, states_node);
		}
	}
	oval_state_iterator_free(states);

	/* Report variables */
	struct oval_variable_iterator *variables = oval_definition_model_get_variables(definition_model);
	if (oval_variable_iterator_has_more(variables)) {
		xmlNode *variables_node = xmlNewTextChild(root_node, ns_defntns, BAD_CAST "variables", NULL);
		while (oval_variable_iterator_has_more(variables)) {
			struct oval_variable *variable = oval_variable_iterator_next(variables);
			oval_variable_to_dom(variable, doc, variables_node);
		}
	}
	oval_variable_iterator_free(variables);

	return root_node;
}

int oval_definition_model_export(struct oval_definition_model *model, const char *file)
{

	__attribute__nonnull__(model);

	LIBXML_TEST_VERSION;

	xmlDocPtr doc = xmlNewDoc(BAD_CAST "1.0");
	if (doc == NULL) {
		oscap_setxmlerr(xmlGetLastError());
		return -1;
	}

	oval_definition_model_to_dom(model, doc, NULL);
	return oscap_xml_save_filename_free(file, doc);
}

static void _fp_set_recurse(struct oval_definition_model *model, struct oval_setobject *set, char *set_id)
{
	struct oval_setobject_iterator *subset_itr;

	subset_itr = oval_setobject_get_subsets(set);
	while (oval_setobject_iterator_has_more(subset_itr)) {
		struct oval_setobject *subset;

		subset = oval_setobject_iterator_next(subset_itr);
		if (oval_setobject_get_type(subset) == OVAL_SET_COLLECTIVE)
			oval_set_propagate_filters(model, subset, set_id);
		else
			_fp_set_recurse(model, subset, set_id);
	}
	oval_setobject_iterator_free(subset_itr);
}

void oval_definition_model_optimize_by_filter_propagation(struct oval_definition_model *model)
{
	struct oval_object_iterator *obj_itr;
	struct oval_string_map *processed_obj_map;

	processed_obj_map = oval_string_map_new();

	obj_itr = oval_definition_model_get_objects(model);
	while (oval_object_iterator_has_more(obj_itr)) {
		struct oval_object *obj;
		char *obj_id;
		struct oval_object_content_iterator *cont_itr;
		struct oval_object_content *cont;
		struct oval_setobject *set;
		struct oval_filter_iterator *filter_itr;

		obj = oval_object_iterator_next(obj_itr);
		obj_id = oval_object_get_id(obj);
		if (oval_string_map_get_value(processed_obj_map, obj_id) != NULL)
			continue;

		oval_string_map_put(processed_obj_map, obj_id, obj);

		cont_itr = oval_object_get_object_contents(obj);
		if (!oval_object_content_iterator_has_more(cont_itr)) {
			oval_object_content_iterator_free(cont_itr);
			continue;
		}

		cont = oval_object_content_iterator_next(cont_itr);
		oval_object_content_iterator_free(cont_itr);
		if (oval_object_content_get_type(cont) != OVAL_OBJECTCONTENT_SET)
			continue;

		set = oval_object_content_get_setobject(cont);
		if (oval_setobject_get_type(set) == OVAL_SET_AGGREGATE) {
			_fp_set_recurse(model, set, obj_id);
			continue;
		}

		filter_itr = oval_setobject_get_filters(set);
		if (!oval_filter_iterator_has_more(filter_itr)) {
			oval_filter_iterator_free(filter_itr);
			continue;
		}
		oval_filter_iterator_free(filter_itr);

		oval_set_propagate_filters(model, set, obj_id);
	}
	oval_object_iterator_free(obj_itr);

	oval_string_map_free(processed_obj_map, NULL);
}
