/*
** SellaNMS
** $Id: event.cxx,v 1.26 2005/03/31 18:57:12 sella Exp $
** Copyright (c) 2001-2005 Digital Genesis Software, LLC. All Rights Reserved.
** Released under the GPL Version 2 License.
** http://www.digitalgenesis.com
*/

#ifdef HAVE_CONFIG_H
	#include "config.h"
#endif

#include "event.h"
#include "debug.h"
#include "storagedata.h"
#include "storage/storage.h"
#include <stdio.h>
#include <sys/stat.h>
#include <stl/slist>
#include <stl/string>
#include <stl/vector>

Event::Event() {
	this->name.append(" event");

	has_execute = true;
	has_scheduler = false;
}

Event::~Event() {
}

RESULT Event::execute() {
	RESULT result = SUCCESS;

	slist<string>::iterator status;

	if (trace.thread) {
		LMSG("Event::execute() starting");
	}

	/* Register allows GUI to collect for this status. */
	for (status = statusList.begin(); status != statusList.end(); ++status) {
		registerStatus(*status);
	}

	/* Process Information. */
	for (status = statusList.begin(); status != statusList.end() && !interrupt; ++status) {
		polOutput.clear();
		polInput.clear();

		if (trace.internal) {
			LMSG("Event::execute(): Calling GuiQueueNext for status %s.", status->c_str());
		}

		while (GuiQueueNext(*status, polOutput) == SUCCESS && !interrupt) {
			/* XXX: Not sure if we should even have an input policy?! */
			if (!inputPolicy.empty()) {
				polInput = polOutput;

				/* Run policy on input data. */
				if (inputPolicy.evaluate(polOutput, polInput) == ACTION_FAILURE) {
					LMSG("NOTICE: Failed to apply input policy.");

					return FAILURE;
				}
			}

			if (!outputPolicy.empty()) {
				polInput = polOutput;

				/* Set our source. */
				polInput["source"] = "event";
				
				/* Run policy on input data. */
				if (outputPolicy.evaluate(polOutput, polInput) == ACTION_FAILURE) {
					LMSG("NOTICE: Failed to apply output policy.");

					return FAILURE;
				}
			}

			/* Run data thru output policy. */
			processPolicyOutput(polOutput);
		}
	}

	/* Run contents of gui_event thru output policy. */
	processStorageContent();

	if (trace.thread) {
		LMSG("Event::execute() exiting");
	}

	return result;
}

RESULT Event::readConfig() {
	RESULT result = SUCCESS;

	vector<string> v;

	/* Add and compile input policy. */
	if (config->assignValues("policy input", v)) {
		LMSG("NOTICE: Input policies are not implemented yet.");
		if (inputPolicy.addPolicies(config, v) == FAILURE) {
			LMSG("NOTICE: Failed to add/compile input policies.");
			result = FAILURE;
		}
	}

	/* Add and compile output policy. */
	if (config->assignValues("policy output", v)) {
		if (outputPolicy.addPolicies(config, v) == FAILURE) {
			LMSG("NOTICE: Failed to add/compile output policies.");
			result = FAILURE;
		}
	}

	/* Verify we have required policies. */
	if (outputPolicy.empty()) {
		LMSG("NOTICE: A output policy is required for this module.");
		result = FAILURE;
	}

#if 0
	/* FIXME: Should use getReferencedVariables to set this... not hard code it. */
	statusList.push_front("warning");
	statusList.push_front("failure");
	statusList.push_front("recovery");
	statusList.push_front("information");
	statusList.push_front("infofailure");
	statusList.push_front("inforecovery");
#else
	if (getPolicyStatusList(statusList) != SUCCESS) {
		LMSG("NOTICE: Failed to locate any status within output policy.");

		result = FAILURE;
	}
#endif

	/* Configure Storage */
	storage.setConfig(*config);

	return result;
}

RESULT Event::printConfig() {
	string str = "status: [ ";
	for (slist<string>::iterator it = statusList.begin(); it != statusList.end(); ++it) {
		str.append(*it + " ");
	}
	str.append("]");
	LMSG(str.c_str());

	return Gui::printConfig();
}

RESULT Event::processStorageContent() {
	RESULT result = SUCCESS;

	string sql;
	char buf[128];
	unsigned long id;
	time_t now, timestamp;
	variable_binding_t policy_input;
	variable_binding_t policy_output;
	StorageData reader, reader2, eraser;

	//FIXME: This could be written to combine deletes togeather.

//#define LIBDBI065_HACK
#ifndef LIBDBI065_HACK //libdbi 065 segfaults on reading "text" fields such as detail from the db.
	if ((result = storage.query(reader, "SELECT CONCAT(UNIX_TIMESTAMP() - UNIX_TIMESTAMP(timestamp)) AS age, element, interface, address, protocol, channel, status, brief, detail FROM gui_event")) == SUCCESS) {
#else
	if ((result = storage.query(reader, "SELECT CONCAT(UNIX_TIMESTAMP() - UNIX_TIMESTAMP(timestamp)) AS age, element, interface, address, protocol, channel, status, brief, 'libdbi v065 hack - event.cxx +138' AS detail FROM gui_event")) == SUCCESS) {
#endif
		while (reader.nextRow()) {
			reader.getString(policy_input["age"], "age");

			reader.getULong(policy_input["element-id"], "element");

			reader.getULong(policy_input["interface-id"], "interface");

			reader.getULong(policy_input["address-id"], "address");

			reader.getString(policy_input["protocol"], "protocol");

			reader.getString(policy_input["channel"], "channel");

			reader.getString(policy_input["status"], "status");

			reader.getString(policy_input["brief"], "brief");

#ifndef LIBDBI065_HACK
			reader.getBinary(policy_input["detail"], "detail");
#else
			reader.getString(policy_input["detail"], "detail");
#endif

			policy_input["source"] = "storage";

			if (trace.internal) {
				LMSG("Checking: %s (age %s)", policy_input["status"].c_str(), policy_input["age"].c_str());
				LMSG("element: %s, interface: %s, address: %s, brief: %s, detail: %s", policy_input["element-id"].c_str(), policy_input["interface-id"].c_str(), policy_input["address-id"].c_str(), policy_input["brief"].c_str(), policy_input["detail"].c_str());
			}

			/* Run policy on input reader. */
			if ((outputPolicy.evaluate(policy_output, policy_input)) == ACTION_FAILURE) {
				LMSG("NOTICE: Failed to apply output policy.");

				result = FAILURE;
				break;
			}

			for (slist<string>::iterator status = statusList.begin(); status != statusList.end(); ++status) {
				if (policy_output.find(*status) != policy_output.end()) {
					if (!policy_output[*status].compare("clear")) {
						if (trace.debug) {
							LMSG("DEBUG: Clearing gui_event content for status '%s'. Age was %s.", status->c_str(), policy_output["age"].c_str());
						}

						sql = "DELETE FROM gui_event WHERE status = " + eraser.quoteString(*status);

						if (policy_output["element-id"].empty()) {
							sql.append(" AND element IS NULL");
						} else {
							sql.append(" AND element = " + policy_output["element-id"]);
						}

						if (policy_output["interface-id"].empty()) {
							sql.append(" AND interface IS NULL");
						} else {
							sql.append(" AND interface = " + policy_output["interface-id"]);
						}

						if (policy_output["address-id"].empty()) {
							sql.append(" AND address IS NULL");
						} else {
							sql.append(" AND address = " + policy_output["address-id"]);
						}

						if (!policy_output["channel"].empty()) {
							sql.append(" AND channel = " + eraser.quoteString(policy_output["channel"]));
						}

						if ((result = storage.query(eraser, sql.c_str())) != SUCCESS) {
							LMSG("NOTICE: Failed to delete in Information::processPolicyOutput().");

							break;
						}

						/* Clear for next iteration. */
						eraser.clear();
					}
				}
			}

			/* Clear for next iteration. */
			policy_output.clear();
		}
	} else {
		LMSG("NOTICE: Failed to read gui_event data from storage.");
		result = FAILURE;
	}

	return result;
}

RESULT Event::processPolicyOutput(variable_binding_t &policy_output) {
	RESULT result = SUCCESS;
	unsigned long element = 0, interface = 0, address = 0;

	string str, sql_ins, sql_sel, sql_del;
	StorageData reader, writer, eraser;

	sql_ins = "INSERT INTO gui_event SET ";

	if (policy_output.find("timestamp") != policy_output.end()) {
		sql_ins.append("gui_event.timestamp = " + policy_output["timestamp"] + ", ");
	}

	if (policy_output.find("element-id") != policy_output.end()) {
		if (!policy_output["element-id"].empty()) {
			sql_ins.append("gui_event.element = " + policy_output["element-id"] + ", ");
		}
	}

	if (policy_output.find("interface-id") != policy_output.end()) {
		if (!policy_output["interface-id"].empty()) {
			sql_ins.append("gui_event.interface = " + policy_output["interface-id"] + ", ");
		}
	}

	if (policy_output.find("address-id") != policy_output.end()) {
		if (!policy_output["address-id"].empty()) {
			sql_ins.append("gui_event.address = " + policy_output["address-id"] + ", ");
		}
	}

	if (policy_output.find("protocol") != policy_output.end()) {
		sql_ins.append("gui_event.protocol = " + writer.quoteString(policy_output["protocol"]) + ", ");
	}

	if (policy_output.find("channel") != policy_output.end()) {
		sql_ins.append("gui_event.channel = " + writer.quoteString(policy_output["channel"]) + ", ");
	}

	if (policy_output.find("status") != policy_output.end()) {
		sql_ins.append("gui_event.status = " + writer.quoteString(policy_output["status"]) + ", ");
	}

	if (policy_output.find("brief") != policy_output.end()) {
		sql_ins.append("gui_event.brief = " + writer.quoteString(policy_output["brief"]) + ", ");
	}

	if (policy_output.find("detail") != policy_output.end()) {
		sql_ins.append("gui_event.detail = " + writer.quoteString(policy_output["detail"]) + ", ");
	}
	sql_ins.erase(sql_ins.size() - 2);

	for (slist<string>::iterator status = statusList.begin(); status != statusList.end(); ++status) {
		if (policy_output.find(*status) != policy_output.end()) {
			if (!policy_output[*status].compare("clear")) {
				if (trace.debug) {
					LMSG("Clearing %s/%s/%s/%s/%s", (policy_output["element-id"].empty()) ? "0" : policy_output["element-id"].c_str(), (policy_output["interface-id"].empty()) ? "0" : policy_output["interface-id"].c_str(), (policy_output["address-id"].empty()) ? "0" : policy_output["address-id"].c_str(), status->c_str(), policy_output["channel"].c_str());
				}

				sql_del = "DELETE FROM gui_event WHERE status = " + eraser.quoteString(*status);

				if (policy_output["element-id"].empty()) {
					sql_del.append(" AND element IS NULL");
				} else {
					sql_del.append(" AND element = " + policy_output["element-id"]);
				}

				if (policy_output["interface-id"].empty()) {
					sql_del.append(" AND interface IS NULL");
				} else {
					sql_del.append(" AND interface = " + policy_output["interface-id"]);
				}

				if (policy_output["address-id"].empty()) {
					sql_del.append(" AND address IS NULL");
				} else {
					sql_del.append(" AND address = " + policy_output["address-id"]);
				}

				if (!policy_output["channel"].empty()) {
					sql_del.append(" AND channel = " + eraser.quoteString(policy_output["channel"]));
				}

				if ((result = storage.query(eraser, sql_del.c_str())) != SUCCESS) {
					LMSG("NOTICE: Failed to delete in Information::processPolicyOutput().");

					break;
				}
			} else if (!policy_output[*status].compare("trim")) {
				if (trace.debug) {
					LMSG("DEBUG: Trimming %s/%s/%s/%s/%s", (policy_output["element-id"].empty()) ? "0" : policy_output["element-id"].c_str(), (policy_output["interface-id"].empty()) ? "0" : policy_output["interface-id"].c_str(), (policy_output["address-id"].empty()) ? "0" : policy_output["address-id"].c_str(), status->c_str(), policy_output["channel"].c_str());
				}

				sql_del = "DELETE FROM gui_event WHERE status = " + eraser.quoteString(*status);
				sql_sel = "SELECT id FROM gui_event WHERE status = " + eraser.quoteString(*status);

				if (policy_output["element-id"].empty()) {
					str = " AND element IS NULL";
					sql_del.append(str);
					sql_sel.append(str);
				} else {
					str = " AND element = " + policy_output["element-id"];
					sql_del.append(str);
					sql_sel.append(str);
				}

				if (policy_output["interface-id"].empty()) {
					str = " AND interface IS NULL";
					sql_del.append(str);
					sql_sel.append(str);
				} else {
					str = " AND interface = " + policy_output["interface-id"];
					sql_sel.append(str);
					sql_del.append(str);
				}

				if (policy_output["address-id"].empty()) {
					str = " AND address IS NULL";
					sql_del.append(str);
					sql_sel.append(str);
				} else {
					str = " AND address = " + policy_output["address-id"];
					sql_del.append(str);
					sql_sel.append(str);
				}

				if (!policy_output["channel"].empty()) {
					str = " AND channel = " + eraser.quoteString(policy_output["channel"]);
					sql_del.append(str);
					sql_sel.append(str);
				}

				sql_sel.append(" ORDER BY timestamp LIMIT 1");

				if ((result = storage.query(reader, sql_sel.c_str())) != SUCCESS) {
					LMSG("NOTICE: Failed to select in Information::processPolicyOutput().");

					break;
				} else if (reader.nextRow()) {
					/* Clear new record, since we are keeping the old one only. */
					sql_ins.erase();

					reader.getULong(str, "id");

					sql_del.append(" AND id != " + str);

					if ((result = storage.query(eraser, sql_del.c_str())) != SUCCESS) {
						LMSG("NOTICE: Failed to delete in Information::processPolicyOutput().");

						break;
					}

					/* Clear for next iteration. */
					reader.clear();
				}
			}
		}
	}

	/* Write new content. */
	if (!sql_ins.empty() && result == SUCCESS) {
		if ((result = storage.query(writer, sql_ins.c_str())) != SUCCESS) {
			LMSG("NOTICE: Failed to write in Information::processPolicyOutput().");
		}
	}

	return result;
}

RESULT Event::getPolicyStatusList(slist<string> &statusList) {
	RESULT result = SUCCESS;

	slist<unsigned int>::iterator it;
	variable_reference_t referenced;

	if (this->outputPolicy.getReferencedVariables(referenced) != SUCCESS) {
		LMSG("ERROR: unable to get referenced variables from output policies.");

		result = FAILURE;
	} else {
		/* Walk policy terms. */
		for (variable_reference_t::iterator term = referenced.begin(); term != referenced.end(); ++term) {
			for (slist<variable_t>::iterator variable = term->begin(); variable != term->end(); ++variable) {
				if (variable->name.compare("status") == 0) {
					for (slist<string>::iterator event = variable->values.begin(); event != variable->values.end(); ++event) {
						statusList.push_front(*event);
					}
				}
			}
		}

		/* Return a sorted unique list. */
		statusList.sort();
		statusList.unique();
	}

	if (trace.debug && statusList.empty()) {
		LMSG("NOTICE: No policy intervals found in ICMP::getPolicyIntervals.");
	}

	return result;
}

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