/**
 * @file oval_sysModel.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 "adt/oval_smc_impl.h"
#include "adt/oval_smc_iterator_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_syschar_model {
	struct oval_generator *generator;
	struct oval_sysinfo *sysinfo;
	struct oval_definition_model *definition_model;
	struct oval_smc *syschar_map;				///< Represents objects within <collected_objects> element
	struct oval_string_map *sysitem_map;			///< Represents items within <system_data> element
        char *schema;
} oval_syschar_model_t;						///< Represents <oval_system_characteristics> element


/* failed   - NULL
 * success  - oval_syschar_model
 * */
struct oval_syschar_model *oval_syschar_model_new(struct oval_definition_model *definition_model)
{
	oval_syschar_model_t *newmodel = (oval_syschar_model_t *) malloc(sizeof(oval_syschar_model_t));
	if (newmodel == NULL)
		return NULL;

	struct oval_generator *generator = oval_definition_model_get_generator(definition_model);
	newmodel->generator = oval_generator_clone(generator);
	oval_generator_update_timestamp(newmodel->generator);

	newmodel->sysinfo = NULL;
	newmodel->definition_model = definition_model;
	newmodel->syschar_map = oval_smc_new();
	newmodel->sysitem_map = oval_string_map_new();
        newmodel->schema = oscap_strdup(OVAL_SYS_SCHEMA_LOCATION);

	/* check possible allocation problems */
	if ((newmodel->syschar_map == NULL) || (newmodel->sysitem_map == NULL) ) {
		oval_syschar_model_free(newmodel);
		return NULL;
	}

	return newmodel;
}

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

static void _oval_syschar_model_clone(struct oval_string_map *oldmap,
				      struct oval_syschar_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);
}

static void _oval_syschar_model_clone_helper(struct oval_smc *oldmap, struct oval_syschar_model *newmodel, _oval_clone_func cloner)
{
	struct oval_smc_iterator *it = oval_smc_iterator_new(oldmap);
	while (oval_smc_iterator_has_more(it)) {
		void *olditem = oval_smc_iterator_next(it);
		(*cloner) (newmodel, olditem);
	}
	oval_smc_iterator_free(it);
}

struct oval_syschar_model *oval_syschar_model_clone(struct oval_syschar_model *old_model)
{

	__attribute__nonnull__(old_model);

	struct oval_syschar_model *new_model = oval_syschar_model_new(old_model->definition_model);

	_oval_syschar_model_clone_helper(old_model->syschar_map, new_model, (_oval_clone_func) oval_syschar_clone);
	_oval_syschar_model_clone(old_model->sysitem_map, new_model,
				  (_oval_clone_func) oval_sysitem_clone);

	struct oval_sysinfo *old_sysinfo = oval_syschar_model_get_sysinfo(old_model);
	struct oval_sysinfo *new_sysinfo = oval_sysinfo_clone(new_model, old_sysinfo);
	oval_syschar_model_set_sysinfo(new_model, new_sysinfo);
	oval_sysinfo_free(new_sysinfo);
        new_model->schema = oscap_strdup(old_model->schema);

	return new_model;
}

void oval_syschar_model_free(struct oval_syschar_model *model)
{
	if (model != NULL) {
		oval_sysinfo_free(model->sysinfo);
		oval_smc_free(model->syschar_map, (oscap_destruct_func) oval_syschar_free);
		if (model->sysitem_map)
			oval_string_map_free(model->sysitem_map, (oscap_destruct_func) oval_sysitem_free);
		free(model->schema);
		oval_generator_free(model->generator);
		free(model);
	}
}

void oval_syschar_model_reset(struct oval_syschar_model *model) 
{
        if (model->syschar_map)
                oval_smc_free(model->syschar_map, (oscap_destruct_func) oval_syschar_free);
        if (model->sysitem_map)
                oval_string_map_free(model->sysitem_map, (oscap_destruct_func) oval_sysitem_free);
        model->syschar_map = oval_smc_new();
        model->sysitem_map = oval_string_map_new();
}

struct oval_generator *oval_syschar_model_get_generator(struct oval_syschar_model *model)
{
	return model->generator;
}

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

struct oval_definition_model *oval_syschar_model_get_definition_model(struct oval_syschar_model *model)
{
	__attribute__nonnull__(model);

	return model->definition_model;
}

struct oval_syschar_iterator *oval_syschar_model_get_syschars(struct oval_syschar_model *model)
{
	__attribute__nonnull__(model);

	return oval_syschar_iterator_new(model->syschar_map);
}

struct oval_sysinfo *oval_syschar_model_get_sysinfo(struct oval_syschar_model *model)
{
	__attribute__nonnull__(model);

	return model->sysinfo;
}

const char * oval_syschar_model_get_schema(struct oval_syschar_model * model)
{
        __attribute__nonnull__(model);

        return model->schema;
}

void oval_syschar_model_set_sysinfo(struct oval_syschar_model *model, struct oval_sysinfo *sysinfo)
{
	__attribute__nonnull__(model);
	if (model->sysinfo)
		oval_sysinfo_free(model->sysinfo);
	model->sysinfo = oval_sysinfo_clone(model, sysinfo);
}

void oval_syschar_model_set_schema(struct oval_syschar_model *model, const char * schema)
{
	__attribute__nonnull__(model);
	model->schema = oscap_strdup(schema);
}


void oval_syschar_model_add_syschar(struct oval_syschar_model *model, struct oval_syschar *syschar)
{
	__attribute__nonnull__(model);
	const char *id = oval_syschar_get_id(syschar);
	if (id != NULL)
		oval_smc_put_last(model->syschar_map, id, syschar);
}

void oval_syschar_model_add_sysitem(struct oval_syschar_model *model, struct oval_sysitem *sysitem)
{
	__attribute__nonnull__(model);
	char *id = oval_sysitem_get_id(sysitem);
	if (id != NULL) {
		oval_string_map_put(model->sysitem_map, id, sysitem);
	}
}

int oval_syschar_model_import_source(struct oval_syschar_model *model, struct oscap_source *source)
{
	int ret = 0;
	/* setup context */
        struct oval_parser_context context;
        context.reader = oscap_source_get_xmlTextReader(source);
	if (context.reader == NULL) {
		return -1;
	}
        context.definition_model = oval_syschar_model_get_definition_model(model);
        context.syschar_model = model;
        context.user_data = NULL;

	/* jump into oval_system_characteristics */
	xmlTextReaderRead(context.reader);
	/* make sure this is syschar */
	char *tagname = (char *)xmlTextReaderLocalName(context.reader);
	char *namespace = (char *)xmlTextReaderNamespaceUri(context.reader);
	int is_ovalsys = strcmp((const char *)OVAL_SYSCHAR_NAMESPACE, namespace) == 0;
	/* start parsing */
	if (is_ovalsys && (strcmp(tagname, OVAL_ROOT_ELM_SYSCHARS) == 0)) {
		ret = oval_syschar_model_parse(context.reader, &context);
	} else {
		oscap_seterr(OSCAP_EFAMILY_OSCAP, "Missing \"oval_system_characteristics\" element");
		dE("Unprocessed tag: <%s:%s>.", namespace, tagname);
		ret = -1;
	}

	free(tagname);
	free(namespace);
	xmlFreeTextReader(context.reader);
	return ret;
}

int oval_syschar_model_bind_variable_model(struct oval_syschar_model *sysmodel, struct oval_variable_model *varmodel)
{
	__attribute__nonnull__(sysmodel);

	return oval_definition_model_bind_variable_model(sysmodel->definition_model, varmodel);
}

struct oval_syschar *oval_syschar_model_get_syschar(struct oval_syschar_model *model, const char *object_id)
{
	__attribute__nonnull__(model);

	return (struct oval_syschar *)oval_smc_get_last(model->syschar_map, object_id);
}

struct oval_sysitem *oval_syschar_model_get_sysitem(struct oval_syschar_model *model, const char *id)
{
	__attribute__nonnull__(model);

	return (struct oval_sysitem *)oval_string_map_get_value(model->sysitem_map, id);
}


struct oval_syschar *oval_syschar_model_get_new_syschar(struct oval_syschar_model *model, struct oval_object *object)
{
	char *object_id = oval_object_get_id(object);
	struct oval_syschar *syschar = oval_syschar_model_get_syschar(model, object_id);
	if (syschar == NULL) {
		syschar = oval_syschar_new(model, object);
	}
	return syschar;
}

struct oval_sysitem *oval_syschar_model_get_new_sysitem(struct oval_syschar_model *model, const char *id)
{
	struct oval_sysitem *sysitem = oval_syschar_model_get_sysitem(model, id);
	if (sysitem == NULL) {
		sysitem = oval_sysitem_new(model, id);
	}
	return sysitem;
}

xmlNode *oval_syschar_model_to_dom(struct oval_syschar_model * syschar_model, xmlDocPtr doc, xmlNode * parent, 
			           oval_syschar_resolver resolver, void *user_arg, bool export_syschar)
{

	xmlNodePtr root_node = NULL;

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

	xmlNs *ns_common = xmlNewNs(root_node, OVAL_COMMON_NAMESPACE, BAD_CAST "oval");
	xmlNs *ns_unix = xmlNewNs(root_node, OVAL_SYSCHAR_UNIX_NS, BAD_CAST "unix-sys");
	xmlNs *ns_ind = xmlNewNs(root_node, OVAL_SYSCHAR_IND_NS, BAD_CAST "ind-sys");
	xmlNs *ns_lin = xmlNewNs(root_node, OVAL_SYSCHAR_LIN_NS, BAD_CAST "lin-sys");
	xmlNs *ns_win = xmlNewNs(root_node, OVAL_SYSCHAR_WIN_NS, BAD_CAST "win-sys");
	xmlNs *ns_syschar = xmlNewNs(root_node, OVAL_SYSCHAR_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_syschar);

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

        /* Report sysinfo */
	oval_sysinfo_to_dom(oval_syschar_model_get_sysinfo(syschar_model), doc, root_node);

	if (!export_syschar) {
		return root_node;
	}

	struct oval_smc *resolved_smc = NULL;
	struct oval_syschar_iterator *syschars = oval_syschar_model_get_syschars(syschar_model);
	if (resolver) {
		resolved_smc = oval_smc_new();
		while (oval_syschar_iterator_has_more(syschars)) {
			struct oval_syschar *syschar = oval_syschar_iterator_next(syschars);
			if ((*resolver) (syschar, user_arg)) {
				oval_smc_put_last(resolved_smc, oval_syschar_get_id(syschar), syschar);
			}
		}
		oval_syschar_iterator_free(syschars);
		syschars = oval_syschar_iterator_new(resolved_smc);
	}

	struct oval_string_map *sysitem_map = oval_string_map_new();
	if (oval_syschar_iterator_has_more(syschars)) {
		xmlNode *tag_objects = xmlNewTextChild(root_node, ns_syschar, BAD_CAST "collected_objects", NULL);

		while (oval_syschar_iterator_has_more(syschars)) {
			struct oval_syschar *syschar = oval_syschar_iterator_next(syschars);
			struct oval_object *object = oval_syschar_get_object(syschar);
			if (oval_syschar_get_flag(syschar) == SYSCHAR_FLAG_UNKNOWN /* Skip unneeded syschars */
			    || oval_object_get_base_obj(object)) /* Skip internal objects */
				continue;
			oval_syschar_to_dom(syschar, doc, tag_objects);
			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);
				oval_string_map_put(sysitem_map, oval_sysitem_get_id(sysitem), sysitem);
			}
			oval_sysitem_iterator_free(sysitems);
		}
	}
	oval_smc_free0(resolved_smc);
	oval_syschar_iterator_free(syschars);

	struct oval_iterator *sysitems = oval_string_map_values(sysitem_map);
	if (oval_collection_iterator_has_more(sysitems)) {
		xmlNode *tag_items = xmlNewTextChild(root_node, ns_syschar, BAD_CAST "system_data", NULL);
		while (oval_collection_iterator_has_more(sysitems)) {
			struct oval_sysitem *sysitem = (struct oval_sysitem *)
			    oval_collection_iterator_next(sysitems);
			oval_sysitem_to_dom(sysitem, doc, tag_items);
		}
	}
	oval_collection_iterator_free(sysitems);
	oval_string_map_free(sysitem_map, NULL);

	return root_node;
}

int oval_syschar_model_export(struct oval_syschar_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_syschar_model_to_dom(model, doc, NULL, NULL, NULL, true);
	return oscap_xml_save_filename_free(file, doc);
}

