/*
 * Copyright 2015 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
 *
 */

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

#include "xccdf_policy_resolve.h"
#include "item.h"
#include "common/_error.h"

struct xccdf_refine_rule_internal {
	char* selector;
	xccdf_role_t role;
	xccdf_level_t severity;
	xccdf_numeric weight;
};

OSCAP_GETTER(char*, xccdf_refine_rule_internal, selector);
OSCAP_GETTER(xccdf_role_t, xccdf_refine_rule_internal, role);
OSCAP_GETTER(xccdf_level_t, xccdf_refine_rule_internal, severity);
OSCAP_GETTER(xccdf_numeric, xccdf_refine_rule_internal, weight);

/**
 * Merge refine rule from profile with internal refine rule
 * Defined src refine-rule will override dst values
 * @param dst Internal refine rule
 * @param src Refine rule from profile
 */
static void _merge_refine_rules(struct xccdf_refine_rule_internal* dst, const struct xccdf_refine_rule* src)
{
	bool new_weight_defined = xccdf_refine_rule_weight_defined(src);
	if (new_weight_defined) {
		xccdf_numeric new_weight = xccdf_refine_rule_get_weight(src);
		dst->weight = new_weight;
	}

	const char* new_selector = xccdf_refine_rule_get_selector(src);
	if (new_selector != NULL) {
		free(dst->selector);
		dst->selector = oscap_strdup( new_selector );
	}

	xccdf_role_t new_role = xccdf_refine_rule_get_role(src);
	if (new_role != 0) {
		dst->role = new_role;
	}

	xccdf_level_t new_severity = xccdf_refine_rule_get_severity(src);
	if (new_severity != XCCDF_LEVEL_NOT_DEFINED) {
		dst->severity = new_severity;
	}
}

/**
 * Allocate memory for new struct and init it with refine-rule values
 * @param rr Refine rule from profile
 * @return allocated internal refine-rule
 */
static inline struct xccdf_refine_rule_internal* _xccdf_refine_rule_internal_new_from_refine_rule(const struct xccdf_refine_rule* rr)
{
	struct xccdf_refine_rule_internal* new_rr = calloc(1, sizeof(struct xccdf_refine_rule_internal));
	if (new_rr != NULL) {
		new_rr->selector = oscap_strdup(xccdf_refine_rule_get_selector(rr));
		new_rr->weight = rr->weight;
		new_rr->role = rr->role;
		new_rr->severity = rr->severity;
	}
	return new_rr;
}

struct xccdf_refine_rule_internal* xccdf_policy_get_refine_rule_by_item(struct xccdf_policy* policy, struct xccdf_item* rule)
{
	const char* item_id = xccdf_item_get_id(rule);
	return oscap_htable_get(policy->refine_rules_internal, item_id);
}

xccdf_role_t xccdf_get_final_role(const struct xccdf_rule* rule, const struct xccdf_refine_rule_internal* r_rule)
{
	if (r_rule == NULL) {
		return xccdf_rule_get_role(rule);
	} else {
		return r_rule->role;
	}
}

/**
 * Return true, if value of weight is valid
 */
bool xccdf_weight_defined(xccdf_numeric weight){
	return (!isnan(weight));
}

float xccdf_get_final_weight(const struct xccdf_rule* rule, const struct xccdf_refine_rule_internal* r_rule)
{
	if (r_rule != NULL && xccdf_weight_defined(r_rule->weight)){
		return r_rule->weight;
	}
	return xccdf_rule_get_weight(rule);
}

xccdf_level_t xccdf_get_final_severity(const struct xccdf_rule* rule, const struct xccdf_refine_rule_internal* r_rule)
{
	if (r_rule != NULL && r_rule->severity != XCCDF_LEVEL_NOT_DEFINED){
		return r_rule->severity;
	}
	return xccdf_rule_get_severity(rule);
}

/**
 * Put refine-rule into hash table with item_id as key. Refine-rules are with same key are merged.
 * @param refine_rules_internal hash table
 * @param new_rr refine-rule to add
 * @param item_id key to hash table
 */
static void _add_refine_rule(struct oscap_htable* refine_rules_internal, const struct xccdf_refine_rule* new_rr, const char* item_id)
{
	struct xccdf_refine_rule_internal* old = oscap_htable_get(refine_rules_internal, item_id);
	if (old != NULL) { // modify refine-rule in hash-table
		_merge_refine_rules(old, new_rr);
	} else { // add new refine-rule to hash-table
		struct xccdf_refine_rule_internal* new_internal_rr = _xccdf_refine_rule_internal_new_from_refine_rule(new_rr);
		oscap_htable_add(refine_rules_internal, item_id, new_internal_rr);
	}
}

void xccdf_refine_rule_internal_free(struct xccdf_refine_rule_internal* item)
{
	free(item->selector);
	free(item);
}

static inline void _xccdf_policy_add_xccdf_refine_rule_internal(struct xccdf_policy* policy, struct xccdf_benchmark* benchmark, const struct xccdf_refine_rule* refine_rule)
{
	const char* rr_item_id = xccdf_refine_rule_get_item(refine_rule);
	struct xccdf_item* item = xccdf_benchmark_get_member(benchmark, XCCDF_ITEM, rr_item_id);
	if (item != NULL) { // get item by id
		_add_refine_rule(policy->refine_rules_internal, refine_rule, rr_item_id);
		return;
	}

	// try to get items by cluster-id
	struct oscap_htable_iterator* hit = xccdf_benchmark_get_cluster_items(benchmark, rr_item_id);
	if (hit == NULL) {
		oscap_seterr(OSCAP_EFAMILY_XCCDF, "Selector ID(%s) does not exist in Benchmark.", rr_item_id);
		return;
	}

	while (oscap_htable_iterator_has_more(hit)) { // iterate through every item in cluster
		const char* item_id = oscap_htable_iterator_next_key(hit);
		if (item_id == NULL) {
			assert(item_id != NULL);
			continue;
		}
		_add_refine_rule(policy->refine_rules_internal,refine_rule, item_id);
	}
	oscap_htable_iterator_free(hit);
}

void xccdf_policy_add_profile_refine_rules(struct xccdf_policy* policy, struct xccdf_benchmark* benchmark, struct xccdf_profile* profile)
{
	struct xccdf_refine_rule_iterator* rr_it = xccdf_profile_get_refine_rules(profile);
	/* Iterate through refine_rules in profile */
	while (xccdf_refine_rule_iterator_has_more(rr_it)) {
		struct xccdf_refine_rule* rr = xccdf_refine_rule_iterator_next(rr_it);
		if (rr == NULL) {
			assert(false);
			continue;
		}
		_xccdf_policy_add_xccdf_refine_rule_internal(policy, benchmark, rr);
	}
	xccdf_refine_rule_iterator_free(rr_it);
}
