/*
** SellaNMS
** $Id: history.cxx,v 1.12 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 "history.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>

History::History() {
	this->name.append(" history");

	has_execute = true;
	has_scheduler = false;
}

History::~History() {
}

RESULT History::execute() {
	slist<string>::iterator status;

	if (trace.thread) {
		LMSG("History::execute() entering");
	}


	/* 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) {
		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"] = "history";
				
				/* 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. */
			if (processPolicyOutput(polOutput) != SUCCESS) {
				return FAILURE;
			}
		}
	}

	/* Disabled, since its way to heavy weight. Need to find another way to handle storage content. */
#if 0
	/* Run contents of gui_history thru output policy. */
	if (processStorageContent() != SUCCESS) {
		return FAILURE;
	}
#endif

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

	return SUCCESS;
}

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

	vector<string> v;

	config->assignValue("duplicate-timeout", duplicateTimeout, DEFAULT_MODULE_OUTPUT_GUI_HISTORY_DUPLICATE_TIMEOUT);

	/* 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;
		}
	}

	/* Insert History. */
	statusList.push_front("history");

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

	return result;
}

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

	LMSG("duplicate-timeout: %lus", this->duplicateTimeout);

	return Gui::printConfig();
}

RESULT History::processStorageContent() {
	RESULT result = SUCCESS;
	ACTION action;

	string sql;
	variable_binding_t policy_input;
	variable_binding_t policy_output;
	StorageData reader, eraser;

	if (storage.query(reader, "SELECT id, CONCAT(UNIX_TIMESTAMP() - UNIX_TIMESTAMP(timestamp)) AS age, element, interface, address, protocol, channel, status, brief, detail FROM gui_history") == SUCCESS) {
		while (reader.nextRow()) {
			reader.getULong(policy_input["id"], "id");

			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");

			reader.getBinary(policy_input["detail"], "detail");

			policy_input["source"] = "storage";

			/* Run policy on input reader. */
			for (slist<string>::iterator status = statusList.begin(); status != statusList.end(); ++status) {
				if ((action = outputPolicy.evaluate(policy_output, policy_input)) == ACTION_FAILURE) {
					return FAILURE;
				} else if (action != ACTION_REJECT) {
					if (policy_output.find(*status) != policy_output.end()) {
						if (!policy_output[*status].compare("clear")) {
							if (trace.debug) {
								LMSG("DEBUG: Clearing old history content for status '%s'. Age was %s.", policy_output["status"].c_str(), policy_output["age"].c_str());
							}

							sql = "DELETE FROM gui_history WHERE id = " + policy_output["id"];

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

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

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

	return result;
}

/* FIXME: This is *way* too slow when the history grows to a larger size. We should implements an in-memory duplicate detection algo. The SELECT is what kills performance. */
RESULT History::processPolicyDuplicateOutput(bool &duplicate_found, variable_binding_t &policy_output) {
	RESULT result = SUCCESS;

	unsigned long id;
	string sql;
	StorageData data;

	/* Assume no duplicate. */
	duplicate_found = false;

	if (policy_output.find("element-id") != policy_output.end()) {
		if (!policy_output["element-id"].empty()) {
			sql.append("gui_history.element = " + policy_output["element-id"] + " AND ");
		}
	}

	if (policy_output.find("interface-id") != policy_output.end()) {
		if (!policy_output["interface-id"].empty()) {
			sql.append("gui_history.interface = " + policy_output["interface-id"] + " AND ");
		}
	}

	if (policy_output.find("address-id") != policy_output.end()) {
		if (!policy_output["address-id"].empty()) {
			sql.append("gui_history.address = " + policy_output["address-id"] + " AND ");
		}
	}

	/* Detect duplicates. */
	if (!sql.empty()) {
		sql.erase(sql.size() - 5);
		sql = "SELECT id, protocol, channel, status, brief FROM gui_history WHERE (" + sql;
		sql.append(") AND UNIX_TIMESTAMP(timestamp) > (UNIX_TIMESTAMP() - %lu) ORDER BY timestamp DESC LIMIT 1");
		if ((result = storage.queryf(data, sql.c_str(), duplicateTimeout)) != SUCCESS) {
			LMSG("NOTICE: Failed to read from database in History::processPolicyDuplicateOutput().");
		}

		/* If duplicate, update count. */
		while (data.nextRow()) {
			if (policy_output.find("protocol") != policy_output.end()) {
				data.getString(sql, "protocol");

				/* Is this a duplicate? */
				if (sql.compare(policy_output["protocol"])) {
					break;
				}
			}

			if (policy_output.find("channel") != policy_output.end()) {
				data.getString(sql, "channel");

				/* Is this a duplicate? */
				if (sql.compare(policy_output["channel"])) {
					break;
				}
			}

			if (policy_output.find("status") != policy_output.end()) {
				data.getString(sql, "status");

				/* Is this a duplicate? */
				if (sql.compare(policy_output["status"])) {
					break;
				}
			}

			if (policy_output.find("brief") != policy_output.end()) {
				data.getString(sql, "brief");

				/* Is this a duplicate? */
				if (sql.compare(policy_output["brief"])) {
					break;
				}
			}

			data.getULong(id, "id");

			/* Clear data */
			data.clear();
			/* Update count, while keeping the timestamp the same. */

			sql = "UPDATE gui_history SET timestamp = timestamp, count = count + 1 WHERE id = %lu";
			if ((result = storage.queryf(data, sql.c_str(), id)) != SUCCESS) {
				LMSG("NOTICE: Failed to update the database in History::processPolicyDuplicateOutput().");
			} else {
				duplicate_found = true;
			}

			/* There is only one entry. */
			break;
		}
	}

	return result;
}


RESULT History::processPolicyOutput(variable_binding_t &policy_output) {
	RESULT result = SUCCESS;

	bool duplicate_found;

	string sql;
	StorageData writer;

	/* Handle duplicate if needed. */
	if (duplicateTimeout > 0) {
		if ((result = processPolicyDuplicateOutput(duplicate_found, policy_output)) != SUCCESS) {
			LMSG("WARNING: Failure within History::processPolicyDuplicateOutput()");
		}

		/* Duplicate processed, return. */
		if (duplicate_found) {
			if (trace.debug) {
				LMSG("Duplicate found. Brief: %s", policy_output["brief"].c_str());
			}
			return result;
		}
	}

	/* Insert new data into gui_history. */
	if (policy_output.find("timestamp") != policy_output.end()) {
		sql.append("gui_history.timestamp = " + policy_output["timestamp"] + ", ");
	}

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

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

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

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

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

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

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

	if (policy_output.find("detail") != policy_output.end()) {
		sql.append("gui_history.detail = " + writer.quoteString(policy_output["detail"]) + ", ");
	}

	/* Write new content. */
	if (!sql.empty()) {
		sql.erase(sql.size() - 2);
		sql = "INSERT INTO gui_history SET " + sql;

		if ((result = storage.query(writer, sql.c_str())) != SUCCESS) {
			LMSG("NOTICE: Failed to write to database in History::processPolicyOutput().");
		}
	}

	return result;
}

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