/*
** SellaNMS
** $Id: map_statement_term.cxx,v 1.22 2006/01/24 07:08:09 sella Exp $
** Copyright (c) 2001-2005 Digital Genesis Software, LLC. All Rights Reserved.
** Released under the GPL Version 2 License.
** http://www.digitalgenesis.com
*/

static const char rcsid[] = "$Id: map_statement_term.cxx,v 1.22 2006/01/24 07:08:09 sella Exp $";

#include "map_statement_term.h"
#include "common/snmp_util.h"
#include "common/util.h"

MapStatementTerm::MapStatementTerm() {
}

MapStatementTerm::MapStatementTerm(Term const &src) {
	*this = src;
}

MapStatementTerm::MapStatementTerm(MapStatementTerm const &src) {
	*this = src;
}

MapStatementTerm::~MapStatementTerm() {
}

Term const& MapStatementTerm::operator=(Term const &src) {
	MapStatementTerm *real_src = (MapStatementTerm*) &src;

	return operator=(*real_src);
}

MapStatementTerm const& MapStatementTerm::operator=(MapStatementTerm const &src) {
	/* Wipe object. */
	this->clear();

	/* Parent first. */
	Term::operator=(src);

	/* Do object specifics. */
	this->table = src.table;
	this->defined = src.defined;
	this->definedExcept = src.definedExcept;
	this->query = src.query;

	this->fields = src.fields;
	this->variables = src.variables;
	
	return *this;
}

RESULT MapStatementTerm::clear() {
	this->table.erase();
	this->defined.clear();
	this->definedExcept.clear();
	this->query.erase();

	this->fields.clear();
	this->variables.clear();

	/* Call parent clean */
	return Term::clear();
}

ACTION MapStatementTerm::evaluate(variable_binding_t &input) {
	StorageData reader;

	string		set_var;
	string		set_val;

	string		sql;

	string		variable;
	string		value;

	int			x, size, except_count;

	TRACE(TRACE_POLICY_MAP, "Evaluating map term %s", this->getName().c_str());

	/* Check each item within 'defined'. If any are missing, skip term. */
	if (!this->defined.empty()) {
		for (x = 0, size = this->defined.size(); x < size; x++) {
			/* Do variable substitution. */
			variable = this->defined[x];
			this->replaceVariables(input, variable);

			/* If not found, then don't execute map. */
			TRACE(TRACE_POLICY_MAP, "Checking for %s (was %s)", variable.c_str(), this->defined[x].c_str());
			if (input.find(variable) == input.end()) {
				TRACE(TRACE_POLICY_MAP, "map 'defined' variable '%s' not found - skipping term %s", variable.c_str(), this->getName().c_str());

				return ACTION_THEN;
			}
		}
	}

	/* Check each item within 'defined-except'. If all are present, skip term. */
	if (!this->definedExcept.empty()) {
		for (x = 0, except_count = 0, size = this->definedExcept.size(); x < size; x++) {
			/* Do variable substitution. */
			variable = this->definedExcept[x];
			this->replaceVariables(input, variable);

			/* If found, then don't execute map. */
			if (input.find(variable) != input.end()) {
				except_count++;

				TRACE(TRACE_POLICY_MAP, "map 'defined-except' variable '%s' found. Found %lu of %lu in term %s.", variable.c_str(), except_count, size, this->getName().c_str());
			}
		}
		/* All defined-except items were found, skip term. */
		if (except_count >= size) {
			TRACE(TRACE_POLICY_MAP, "map 'defined-except' variables all (%lu) found - skipping term %s", except_count, this->getName().c_str());

			return ACTION_THEN;
		}
	}

	if (this->query.empty()) {
		if (!this->defined.empty()) {
			TRACE(TRACE_POLICY_MAP, "empty for %s, variables.size() %d term %s", variable.c_str(), this->variables.size(), this->getName().c_str());
		} else {
			TRACE(TRACE_POLICY_MAP, "empty for (no defined), variables.size() %d term %s", this->variables.size(), this->getName().c_str());
		}
		/* If there is no query, just set variables. */
		for (slist<pair<string, string> >::iterator v = this->variables.begin(); v != this->variables.end(); ++v) {
			set_var = v->first;
			this->replaceVariables(input, set_var);
			if ((set_var.size() > 2) && ((set_var[0] == '@') && (set_var[v->first.size() - 1] == '@'))) {
				set_var = set_var.substr(1, set_var.size() - 2);
			}

			set_val = v->second;
			this->replaceVariables(input, set_val);
			if ((set_val.size() > 2) && ((set_val[0] == '@') && (set_val[set_val.size() - 1] == '@'))) {
				set_val = set_val.substr(1, set_val.size() - 2);
			}
			input[set_var] = set_val;

			TRACE(TRACE_POLICY_MAP, "map set policy_input[%s] = '%s' (in term %s)", set_var.c_str(), input[set_var].c_str(), this->getName().c_str());
		}
	} else {
		/* If there is a query. */
		sql = this->query;
		this->replaceVariables(input, sql);

		TRACE(TRACE_POLICY_MAP, "Expanded SQL: %s", sql.c_str());

		if (this->storage->query(reader, sql.c_str()) != SUCCESS) {
			return ACTION_FAILURE;
		} else {
			TRACE(TRACE_POLICY_MAP, "map query returned %lu results in term %s", reader.getNumRowAffected(), this->getName().c_str());

			if (!reader.empty()) {
				for (slist<pair<string, string> >::iterator v = this->variables.begin(); v != this->variables.end(); ++v) {
					if (reader.firstRow()) {
						set_var = v->first;
						this->replaceVariables(input, set_var);
						if ((set_var.size() > 2) && ((set_var[0] == '@') && (set_var[v->first.size() - 1] == '@'))) {
							set_var = set_var.substr(1, set_var.size() - 2);
						}

						set_val = v->second;
						this->replaceVariables(input, set_val);
						if ((set_val.size() > 2) && ((set_val[0] == '@') && (set_val[set_val.size() - 1] == '@'))) {
							set_val = set_val.substr(1, set_val.size() - 2);
						}

						do {
							if (reader.getFieldIdx(set_var) > 0) {
								reader.getString(variable, set_var);
							} else {
								variable.erase();
							}
							if (reader.getFieldIdx(set_val) > 0) {
								reader.getAuto(value, set_val);
							} else {
								value.erase();
							}

							if (!variable.empty()) {
#if 0
/* This code was inserting the variable name for database entries with empty strings. For example when an interface has no ifAlias. */
								if (!value.empty()) {
									input[variable] = value;
								} else {
									input[variable] = set_val;
								}
#else
								input[variable] = value;
#endif
								TRACE(TRACE_POLICY_MAP, "map set policy_input[%s] = '%s' [recursive] (in term %s)", variable.c_str(), input[variable].c_str(), this->getName().c_str());
							} else {
								//TRACE(TRACE_POLICY_MAP, "input[%s] = VAL: %s/set_val: %s", set_var.c_str(), value.c_str(), set_val.c_str());
#if 0
/* This code was inserting the variable name for database entries with empty strings. For example when an interface has no ifAlias. */
								if (!value.empty()) {
									input[set_var] = value;
								} else {
									input[set_var] = set_val;
								}
#else
								input[set_var] = value;
#endif
								TRACE(TRACE_POLICY_MAP, "map set policy_input[%s] = '%s' [non-recursive] (in term %s)", set_var.c_str(), input[set_var].c_str(), this->getName().c_str());
								break; /* Accept first input[set_var]. Don't bother overwritting. */
							}
						} while (reader.nextRow());
					}
				}
			}
		}
	}

	return ACTION_THEN;
}

RESULT MapStatementTerm::compile(ConfigNode *config) {
	RESULT result = SUCCESS;

	pair<string, string>	var;

	vector<string>			vars;

	int						size;

	/* Get 'defined' statements. */
	if (config->assignValues("from defined", this->defined)) {
		size = this->defined.size();
		for (int x = 0; x < size; x++) {
			resolveMIBObject(this->defined[x]);
		}
	}

	/* Get 'definedExcept' statements. */
	if (config->assignValues("from defined-except", this->definedExcept)) {
		size = this->definedExcept.size();
		for (int x = 0; x < size; x++) {
			resolveMIBObject(this->definedExcept[x]);
		}
	}

	/* Get query database. */
	if (!config->assignValue("then query database sql", this->query)) {
		this->query.erase();
	} else {
		resolveMIBObject(this->query);
	}

	/* Get set variables. */
	if (config->assignValues("then set", vars)) {
		for (vector<string>::iterator v = vars.begin(); v != vars.end(); ++v) {
			var.first = *v;

			if (!config->assignValue("then set " + var.first, var.second)) {
				var.second.erase();
			} else {
				resolveMIBObject(var.first);
				resolveMIBObject(var.second);
			}

			this->variables.insert(this->variables.end(), var);
		}
	} else {
		this->variables.clear();
	}
	return result;
}

/*
	Print all information from this term.
	
	Return Values
		SUCCESS:	if the term was successfully printed.
		FAILURE: if the term failed to print.
*/
RESULT MapStatementTerm::print(unsigned long depth) {
	string	tab = "", str;

	for (unsigned long i = 0; i < depth; i++) {
		tab.append("   ");
	}

	LMSG("%sterm %s {", tab.c_str(), this->getName().c_str());
	if (!this->defined.empty() || !this->definedExcept.empty()) {
		LMSG("%s   from {\n", tab.c_str());
		if (this->defined.empty()) {
			LMSG("%s   defined { }\n", tab.c_str());
		} else {
			LMSG("%s   defined {\n", tab.c_str());
			for (size_t x = 0; x < this->defined.size(); x++) {
				LMSG("%s         %s;\n", tab.c_str(), this->defined[x].c_str());
			}
			LMSG("%s   }\n", tab.c_str());
		}
		if (this->definedExcept.empty()) {
			LMSG("%s   defined-except { }\n", tab.c_str());
		} else {
			LMSG("%s   defined-except {\n", tab.c_str());
			for (size_t x = 0; x < this->definedExcept.size(); x++) {
				LMSG("%s         %s;\n", tab.c_str(), this->definedExcept[x].c_str());
			}
			LMSG("%s   }\n", tab.c_str());
		}
		LMSG("%s   }", tab.c_str());
	}
	if (!this->variables.empty() || !this->query.empty()) {
		LMSG("%s   then {", tab.c_str());
		if (!this->query.empty()) {
			str = this->query; /* We don't want to modify the original. */
			SubStringReplace(str, "\t", "");

			LMSG("%s      query database sql '%s'", tab.c_str(), str.c_str());
		}
		LMSG("%s      set {", tab.c_str());
		for (slist<pair<string, string> >::iterator variable = this->variables.begin(); variable != this->variables.end(); ++variable) {
			LMSG("%s         %s  %s;\n", tab.c_str(), variable->first.c_str(), variable->second.c_str());
		}
		LMSG("%s      }", tab.c_str());
		LMSG("%s   }", tab.c_str());
	}
	LMSG("%s}", tab.c_str());

	return SUCCESS;
}

/*
** Local Variables:
** c-basic-offset: 3
** tab-width: 3
** End:
** vim:
** vim: noet ts=3 sw=3 sts=3
*/
