/**
 * @file oval_varModel.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>
 */

#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"
#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_variable_model_frame {
	char *id;
	char *comment;
	struct oval_collection *values;
	oval_datatype_t datatype;
} _oval_variable_model_frame_t;

typedef struct oval_variable_model {
	struct oval_generator *generator;
	struct oval_string_map *varmap;
} oval_variable_model_t;

static _oval_variable_model_frame_t *_oval_variable_model_frame_new(char *id, const char *comm, oval_datatype_t datatype);
static int _oval_variable_model_parse_tag(xmlTextReader * reader, struct oval_parser_context *context,
					  struct oval_variable_model *model);
static void _oval_variable_model_frame_free(_oval_variable_model_frame_t * frame);
static int _oval_variable_model_parse_variable_values(xmlTextReader * reader, struct oval_parser_context *context,
						      _oval_variable_model_frame_t * frame);
static int _oval_variable_model_parse_variable(xmlTextReader * reader, struct oval_parser_context *context,
					       struct oval_variable_model *model);
static int _oval_variable_model_parse_variables(xmlTextReader * reader, struct oval_parser_context *context,
						struct oval_variable_model *model);
static int _oval_variable_model_parse(struct oval_variable_model *model, xmlTextReader * reader, void *user_param);

static _oval_variable_model_frame_t *_oval_variable_model_frame_new(char *id, const char *comm, oval_datatype_t datatype)
{
	_oval_variable_model_frame_t *frame = (_oval_variable_model_frame_t *) malloc(sizeof(_oval_variable_model_frame_t));
	if (frame == NULL)
		return NULL;
	frame->id = oscap_strdup(id);
	frame->comment = oscap_strdup(comm);
	frame->datatype = datatype;
	frame->values = oval_collection_new();
	return frame;
}

bool oval_variable_model_iterator_has_more(struct oval_variable_model_iterator *itr)
{
	return oval_collection_iterator_has_more((struct oval_iterator *) itr);
}

struct oval_variable_model *oval_variable_model_iterator_next(struct oval_variable_model_iterator *itr)
{
	return (struct oval_variable_model *) oval_collection_iterator_next((struct oval_iterator *) itr);
}

void oval_variable_model_iterator_free(struct oval_variable_model_iterator *itr)
{
	oval_collection_iterator_free((struct oval_iterator *) itr);
}

static void _oval_variable_model_frame_free(_oval_variable_model_frame_t * frame)
{
	if (frame) {
		if (frame->id)
			free(frame->id);
		if (frame->comment)
			free(frame->comment);
		oval_collection_free_items(frame->values, (oscap_destruct_func) oval_value_free);
		frame->id = NULL;
		frame->comment = NULL;
		frame->values = NULL;
		frame->datatype = 0;
		free(frame);
	}
}

struct oval_variable_model *oval_variable_model_new()
{
	oval_variable_model_t *model = (oval_variable_model_t *) malloc(sizeof(oval_variable_model_t));
	if (model == NULL)
		return NULL;
	model->generator = oval_generator_new();
	model->varmap = oval_string_map_new();
	return model;
}

struct oval_variable_model *oval_variable_model_clone(struct oval_variable_model *old_model)
{
	struct oval_variable_model *new_model = oval_variable_model_new();
	struct oval_string_iterator *old_varids = oval_variable_model_get_variable_ids(old_model);
	while (oval_string_iterator_has_more(old_varids)) {
		char *varid = oval_string_iterator_next(old_varids);
		oval_datatype_t datatype = oval_variable_model_get_datatype(old_model, varid);
		struct oval_value_iterator *values = oval_variable_model_get_values(old_model, varid);
		const char *comm = oval_variable_model_get_comment(old_model, varid);
		while (oval_value_iterator_has_more(values)) {
			struct oval_value *ov;
			char *text;

			ov = oval_value_iterator_next(values);
			text = oval_value_get_text(ov);
			oval_variable_model_add(new_model, varid, comm, datatype, text);
		}
		oval_value_iterator_free(values);
	} oval_string_iterator_free(old_varids);
	return new_model;
}

void oval_variable_model_free(struct oval_variable_model *model)
{
	if (model) {
		oval_string_map_free(model->varmap, (oscap_destruct_func) _oval_variable_model_frame_free);
		model->varmap = NULL;
		oval_generator_free(model->generator);
		free(model);
	}
}

struct oval_generator *oval_variable_model_get_generator(struct oval_variable_model *model)
{
	return model->generator;
}

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

void oval_variable_model_add(struct oval_variable_model *model, char *varid, const char *comm,
			     oval_datatype_t datatype, char *value)
{
	struct oval_value *ov;

	struct _oval_variable_model_frame *frame =
	    (struct _oval_variable_model_frame *)oval_string_map_get_value(model->varmap, varid);
	if (frame == NULL) {
		frame = _oval_variable_model_frame_new(varid, comm, datatype);
		oval_string_map_put(model->varmap, varid, frame);
	}
	ov = oval_value_new(datatype, value);
	oval_collection_add(frame->values, ov);
}

#define NAMESPACE_VARIABLES "http://oval.mitre.org/XMLSchema/oval-variables-5"

static int _oval_variable_model_parse_variable_values
    (xmlTextReader * reader, struct oval_parser_context *context, _oval_variable_model_frame_t * frame) {
	char *tagname = (char *)xmlTextReaderLocalName(reader);
	char *namespace = (char *)xmlTextReaderNamespaceUri(reader);
	int return_code;
	bool is_variable_ns = oscap_strcmp(NAMESPACE_VARIABLES, namespace) == 0;
	if (is_variable_ns && oscap_strcmp("value", tagname) == 0) {
		struct oval_value *ov;

		return_code = xmlTextReaderRead(reader);
		char *value = (char *)xmlTextReaderValue(reader);
		ov = oval_value_new(frame->datatype, value);
		oval_collection_add(frame->values, ov);
		free(value);
	} else {
		dW("Unprocessed tag: <%s:%s>.", namespace, tagname);
		oval_parser_skip_tag(reader, context);
		return_code = 0;
	}
	free(tagname);
	free(namespace);
	return return_code;
}

static int _oval_variable_model_parse_variable
    (xmlTextReader * reader, struct oval_parser_context *context, struct oval_variable_model *model) {
	char *id = (char *)xmlTextReaderGetAttribute(reader, BAD_CAST "id");
	char *comm = (char *)xmlTextReaderGetAttribute(reader, BAD_CAST "comment");
	oval_datatype_t datatype = oval_datatype_parse(reader, "datatype", OVAL_DATATYPE_STRING);
	_oval_variable_model_frame_t *frame = oval_string_map_get_value(model->varmap, id);
	int return_code = 1;
	if (frame) {
		if (frame->datatype != datatype) {
			dW("Unmatched variable datatypes: %s:%s.",
				      oval_datatype_get_text(frame->datatype), oval_datatype_get_text(datatype));
			oval_parser_skip_tag(reader, context);
			return_code = 0;
		}
		frame = NULL;
	} else {
		frame = _oval_variable_model_frame_new(id, comm, datatype);
		oval_string_map_put(model->varmap, id, frame);
	}
	return_code =
	    oval_parser_parse_tag(reader, context, (oval_xml_tag_parser) _oval_variable_model_parse_variable_values, frame);
	free(id);
	free(comm);
	return return_code;
}

static int _oval_variable_model_parse_variables
    (xmlTextReader * reader, struct oval_parser_context *context, struct oval_variable_model *model) {
	char *tagname = (char *)xmlTextReaderLocalName(reader);
	char *namespace = (char *)xmlTextReaderNamespaceUri(reader);
	int return_code;
	bool is_variable_ns = oscap_strcmp(NAMESPACE_VARIABLES, namespace) == 0;
	if (is_variable_ns && oscap_strcmp("variable", tagname) == 0) {
		return_code = _oval_variable_model_parse_variable(reader, context, model);
	} else {
		dW("Unprocessed tag: <%s:%s>.", namespace, tagname);
		oval_parser_skip_tag(reader, context);

		/*oscap_seterr */
		return_code = 0;
	}
	free(tagname);
	free(namespace);
	return return_code;
}

static int _oval_variable_model_parse_tag
    (xmlTextReader * reader, struct oval_parser_context *context, struct oval_variable_model *model) {
	char *tagname = (char *)xmlTextReaderLocalName(reader);
	char *namespace = (char *)xmlTextReaderNamespaceUri(reader);
	int return_code;
	bool is_variable_ns = oscap_strcmp(NAMESPACE_VARIABLES, namespace) == 0;
	if (is_variable_ns && oscap_strcmp("generator", tagname) == 0) {
		struct oval_generator *gen;

		gen = oval_variable_model_get_generator(context->variable_model);
		return_code = oval_generator_parse_tag(reader, context, gen);
	} else if (is_variable_ns && oscap_strcmp("variables", tagname) == 0) {
		return_code =
		    oval_parser_parse_tag(reader, context, (oval_xml_tag_parser) _oval_variable_model_parse_variables, model);
	} else {
		dW("Unprocessed tag: <%s:%s>.", namespace, tagname);
		oval_parser_skip_tag(reader, context);
		return_code = 0;
	}
	free(tagname);
	free(namespace);
	return return_code;
}

static int _oval_variable_model_parse(struct oval_variable_model *model, xmlTextReader * reader, void *user_param)
{
	int return_code = 0;
	struct oval_parser_context context;
	context.variable_model = model;
	context.reader = reader;
	context.user_data = user_param;
	char *tagname = (char *)xmlTextReaderLocalName(reader);
	char *namespace = (char *)xmlTextReaderNamespaceUri(reader);
	bool is_variables = (oscap_strcmp(NAMESPACE_VARIABLES, namespace) == 0) && (oscap_strcmp(OVAL_ROOT_ELM_VARIABLES, tagname) == 0);
	if (is_variables) {
		return_code =
		    oval_parser_parse_tag(reader, &context, (oval_xml_tag_parser) _oval_variable_model_parse_tag, model);
	} else {
		dW("Unprocessed tag: <%s:%s>.", namespace, tagname);
		return_code = oval_parser_skip_tag(reader, &context);
	}
	free(tagname);
	free(namespace);
	return return_code;
}

struct oval_variable_model *oval_variable_model_import_source(struct oscap_source *source)
{
	int ret;
	xmlTextReader *reader = oscap_source_get_xmlTextReader(source);
	if (reader == NULL) {
                return NULL;
	}

	xmlTextReaderRead(reader);
	struct oval_variable_model *model = oval_variable_model_new();
	ret = _oval_variable_model_parse(model, reader, NULL);
	if (ret != 1) {
		oval_variable_model_free(model);
		model = NULL;
	}
	xmlFreeTextReader(reader);
	return model;

}

static xmlNode *oval_variable_model_to_dom(struct oval_variable_model * variable_model,
				   xmlDocPtr doc, xmlNode * parent, void *user_arg)
{

	xmlNodePtr root_node;

	if (parent) {
		root_node = xmlNewTextChild(parent, NULL, BAD_CAST OVAL_ROOT_ELM_VARIABLES, NULL);
	} else {
		root_node = xmlNewNode(NULL, BAD_CAST OVAL_ROOT_ELM_VARIABLES);
		xmlDocSetRootElement(doc, root_node);
	}

	xmlNewNsProp(root_node, lookup_xsi_ns(doc), BAD_CAST "schemaLocation", BAD_CAST OVAL_VAR_SCHEMA_LOCATION);

	xmlNs *ns_common = xmlNewNs(root_node, OVAL_COMMON_NAMESPACE, BAD_CAST "oval");
	xmlNs *ns_variables = xmlNewNs(root_node, OVAL_VARIABLES_NAMESPACE, NULL);

	xmlSetNs(root_node, ns_common);
	xmlSetNs(root_node, ns_variables);

	oval_generator_to_dom(variable_model->generator, doc, root_node);

	xmlNode *variables = xmlNewTextChild(root_node, ns_variables, BAD_CAST "variables", NULL);


	struct oval_string_iterator *varids = oval_variable_model_get_variable_ids(variable_model);
	while (oval_string_iterator_has_more(varids)) {
		char *varid = oval_string_iterator_next(varids);
		oval_datatype_t datatype = oval_variable_model_get_datatype(variable_model, varid);
		const char *comm = oval_variable_model_get_comment(variable_model, varid);

                xmlNode *variable = xmlNewTextChild(variables, ns_variables, BAD_CAST "variable", NULL);
                xmlNewProp(variable, BAD_CAST "id", BAD_CAST varid);
                xmlNewProp(variable, BAD_CAST "datatype", BAD_CAST oval_datatype_get_text(datatype));
                xmlNewProp(variable, BAD_CAST "comment", BAD_CAST comm);

		struct oval_value_iterator *value_itr = oval_variable_model_get_values(variable_model, varid);
		while (oval_value_iterator_has_more(value_itr)) {
			struct oval_value *value;
			char *text;

			value = oval_value_iterator_next(value_itr);
			text = oval_value_get_text(value);
			xmlNewTextChild(variable, ns_variables, BAD_CAST "value", BAD_CAST text);
		}
                oval_value_iterator_free(value_itr);
	}
        oval_string_iterator_free(varids);

	return root_node;
}

int oval_variable_model_export(struct oval_variable_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_variable_model_to_dom(model, doc, NULL, NULL);
	return oscap_xml_save_filename_free(file, doc);
}

bool oval_variable_model_has_variable(struct oval_variable_model *model, const char * id)
{
        __attribute__nonnull__(model);
        struct oval_string_iterator * str_it = (struct oval_string_iterator *)oval_string_map_keys(model->varmap);
        while (oval_string_iterator_has_more(str_it)) {
            if (!oscap_strcmp(oval_string_iterator_next(str_it), id)) {
                oval_string_iterator_free(str_it);
                return true;
            }
        }
        oval_string_iterator_free(str_it);
        return false;
}

struct oval_string_iterator *oval_variable_model_get_variable_ids(struct oval_variable_model *model)
{
	__attribute__nonnull__(model);
	return (struct oval_string_iterator *)oval_string_map_keys(model->varmap);
}
oval_datatype_t oval_variable_model_get_datatype(struct oval_variable_model *model, char *varid)
{

	__attribute__nonnull__(model);
	_oval_variable_model_frame_t *frame = oval_string_map_get_value(model->varmap, varid);
	return (frame) ? frame->datatype : OVAL_DATATYPE_UNKNOWN;
}

const char *oval_variable_model_get_comment(struct oval_variable_model *model, char *varid)
{
	__attribute__nonnull__(model);
	_oval_variable_model_frame_t *frame = oval_string_map_get_value(model->varmap, varid);
	return (frame) ? frame->comment : NULL;
}

struct oval_value_iterator *oval_variable_model_get_values(struct oval_variable_model *model, char *varid)
{
	__attribute__nonnull__(model);
	_oval_variable_model_frame_t *frame = oval_string_map_get_value(model->varmap, varid);
	return (frame) ? (struct oval_value_iterator *) oval_collection_iterator(frame->values) : NULL;
}

struct oval_collection *oval_variable_model_get_values_ref(struct oval_variable_model *model, char *varid)
{
	_oval_variable_model_frame_t *frame;

	frame = oval_string_map_get_value(model->varmap, varid);
	return (frame) ? frame->values : NULL;
}
