/*
** SellaNMS
** $Id: state.cxx,v 1.12 2005/11/14 07:22:58 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 char rcsid[] = "$Id: state.cxx,v 1.12 2005/11/14 07:22:58 sella Exp $";

#include "state.h"
#include "common/util.h"
#include "common/netblock.h"
#include "common/storagedata.h"
#include "policy_framework/policy_framework.h"
#include "state/state.h"
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include <arpa/inet.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/ip.h>
#include <netinet/ip_icmp.h>
#include <stl/hash_map>
#include <stl/string>
#include <stl/vector>

#define POLICY_TABLE_TRACKING

extern ConfigNode* config;

extern bool interrupt;

State::State():timestamp(0) {
}

State::~State() {
}

RESULT State::execute() {
	time_t next;

	while (!interrupt) {
		/* Update interval. */
		next = time(NULL) + this->interval;

		/* Remove stale entries */
		this->expire();

		/* Update states */
		this->update();	

		while (time(NULL) < next && !interrupt) {
			usleep(USLEEP_LONGLOOP_TIME);
		}
	}
	return SUCCESS;
}

RESULT State::readConfig() {
	RESULT result = SUCCESS;
	string str;
	vector<string> v;
	vector<string>::iterator v_it;
	ConfigNode node;
	struct in_addr ip;
	unsigned long tmp = 0;

	config->assignNode("monitor state", node);

	/* How often to start a polling inteval. */
	node.assignValue("interval", interval, MODULE_MONITOR_STATE_INTERVAL_DEF, MODULE_MONITOR_STATE_INTERVAL_MIN, MODULE_MONITOR_STATE_INTERVAL_MAX);

	/* Add and compile input policy. */
	if (node.assignValues("policy input", v)) {
		if (inputPolicy.addPolicies(config, v) == FAILURE) {
			LMSG("failed to add/compile input policies");

			return FAILURE;
		}
	}

	/* Add and compile output policy. */
	if (node.assignValues("policy output", v)) {
		if (outputPolicy.addPolicies(config, v) == FAILURE) {
			LMSG("failed to add/compile output policies");

			return FAILURE;
		}
	}

	/* Verify we have required policies. */
	if (outputPolicy.empty()) {
		LMSG("an output policy is required for this module");

		return FAILURE;
	}

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

	printConfig();

	return result;
}

void State::printConfig() {
	string str;
	vector<string> v;
	vector<string>::iterator v_it;

	if (!TRACE_ISSET(TRACE_CONFIG)) {
		return;
	}

	TRACE(TRACE_CONFIG, "interval: %lu (sec)", this->interval);

	if (config->assignValues("monitor state policy input", v)) {
		str.erase();
		for (v_it = v.begin(); v_it != v.end(); ++v_it) {
			str.append(*v_it + " ");
		}
		TRACE(TRACE_CONFIG, "policy input: %s", str.c_str());
	} else {
		TRACE(TRACE_CONFIG, "policy input: none");
	}

	if (config->assignValues("monitor state policy output", v)) {
		str.erase();
		for (v_it = v.begin(); v_it != v.end(); ++v_it) {
			str.append(*v_it + " ");
		}
		TRACE(TRACE_CONFIG, "policy output: %s", str.c_str());
	} else {
		TRACE(TRACE_CONFIG, "policy output: none");
	}

	return;
}

RESULT State::expire() {
	variable_binding_t policy_input;

	policy_input["origin"] = "attr";
	policy_input["protocol"] = "state";
	policy_input["event"] = "expire";

	return SUCCESS;
}

RESULT State::update() {
	bool running;
	time_t now;

	unsigned long element = 0, interface = 0, ip = 0;
	unsigned long last_element = 0, last_interface = 0, last_ip = 0;

	string sql, variable, value;

	size_t buf_size = 132;
	char buf[buf_size];

	StorageData	data;

	variable_binding_t policy_input;
	hash_map<string, string> tableMap;

	/* Capture start time */
	time(&now);

//	sql = "SELECT DISTINCT element.id, interface.id, ip.id FROM element LEFT JOIN element_attr ON (element_attr.element = element.id AND element_attr.variable LIKE '%%-state') LEFT JOIN interface ON (interface.element = element.id) LEFT JOIN interface_attr ON (interface_attr.interface = interface.id AND interface_attr.variable LIKE '%%-state') LEFT JOIN ip ON (ip.interface = interface.id) LEFT JOIN ip_attr ON (ip_attr.ip = ip.id AND ip_attr.variable LIKE '%%-state')  WHERE element_attr.timestamp > FROM_UNIXTIME('%lu') OR interface_attr.timestamp > FROM_UNIXTIME('%lu') OR ip_attr.timestamp > FROM_UNIXTIME('%lu')";

	/* Collect all state changes since last check. */
	sql = "SELECT element.id element_id, interface.id interface_id, ip.id ip_id, element_attr.variable element_attr_variable, element_attr.value element_attr_value, interface_attr.variable interface_attr_variable, interface_attr.value interface_attr_value, ip_attr.variable ip_attr_variable, ip_attr.value ip_attr_value FROM element LEFT JOIN element_attr ON (element_attr.element = element.id AND element_attr.variable LIKE '%%-state') LEFT JOIN interface ON (interface.element = element.id) LEFT JOIN interface_attr ON (interface_attr.interface = interface.id AND interface_attr.variable LIKE '%%-state') LEFT JOIN ip ON (ip.interface = interface.id) LEFT JOIN ip_attr ON (ip_attr.ip = ip.id AND ip_attr.variable LIKE '%%-state')  WHERE element_attr.timestamp > FROM_UNIXTIME(%lu) OR interface_attr.timestamp > FROM_UNIXTIME(%lu) OR ip_attr.timestamp > FROM_UNIXTIME(%lu) ORDER BY element_id, interface_id, ip_id";

	if (storage.queryf(data, sql.c_str(), this->timestamp, this->timestamp, this->timestamp) == FAILURE) {
		LMSG("Failed to query new state changes. SQL: %s", sql.c_str());

		return FAILURE;
	}

	/* Initialize loop. */
	if (running = data.nextRow()) {
		/* Get first ids. */
		data.getULong(element, "element_id");
		data.getULong(interface, "interface_id");
		data.getULong(ip, "ip_id");

		policy_input["origin"] = "attr";
		policy_input["protocol"] = "state";
		policy_input["event"] = "update";
	}

	while (running && !interrupt) {
		/* Update last ids. */
		last_element = element;
		last_interface = interface;
		last_ip = ip;

		/* Insert element related data. */
		snprintf(buf, buf_size, "%lu", element);
		policy_input["element-id"] = buf;

		if (element != 0) {
			data.getString(variable, "element_attr_variable");

			if (!variable.empty()) {
				data.getString(value, "element_attr_value");
				policy_input[variable] = value;
				tableMap[variable] = "element";

#ifdef POLICY_TABLE_TRACKING
				snprintf(buf, buf_size, "element-%s", variable.c_str());
				policy_input[buf] = value;
				tableMap[buf] = "element";
#endif
			}
		}

		/* Insert interface related data. */
		snprintf(buf, buf_size, "%lu", interface);
		policy_input["interface-id"] = buf;

		if (interface != 0) {
			data.getString(variable, "interface_attr_variable");

			if (!variable.empty()) {
				data.getString(value, "interface_attr_value");
				policy_input[variable] = value;
				tableMap[variable] = "interface";

#ifdef POLICY_TABLE_TRACKING
				snprintf(buf, buf_size, "interface-%s", variable.c_str());
				policy_input[buf] = value;
				tableMap[buf] = "interface";
#endif
			}
		}

		/* Insert ip related data. */
		snprintf(buf, buf_size, "%lu", ip);
		policy_input["ip-id"] = buf;

		if (ip != 0) {
			data.getString(variable, "ip_attr_variable");

			if (!variable.empty()) {
				data.getString(value, "ip_attr_value");
				policy_input[variable] = value;
				tableMap[variable] = "ip";

#ifdef POLICY_TABLE_TRACKING
				snprintf(buf, buf_size, "ip-%s", variable.c_str());
				policy_input[buf] = value;
				tableMap[buf] = "ip";
#endif
			}
		}

		/* Grab next row. */
		if (running = data.nextRow()) {
			/* Get next set of ids. */
			data.getULong(element, "element_id");
			data.getULong(interface, "interface_id");
			data.getULong(ip, "ip_id");
		} else {
			/* Force last entry to be processed. */
			element = interface = ip = 0;
		}

		/* If this is a new data set (ids don't match), then run data though policy. */
		if (element != last_element || interface != last_interface || ip != last_ip) {
			processPolicy(policy_input, tableMap);

			/* Reinitialize structure for next run. */
			tableMap.clear();
			policy_input.clear();
			policy_input["origin"] = "attr";
			policy_input["protocol"] = "state";
			policy_input["event"] = "update";
		} else {
			TRACE(TRACE_DUMP, "Same! %lu == %lu, %lu == %lu, %lu == %lu", element, last_element, interface, last_interface, ip, last_ip);
		}
	}

	/* Update timestamp */
	this->timestamp = now;
}

RESULT State::processPolicy(variable_binding_t& policy_input, hash_map<string, string>& tableMap) {
	RESULT result = SUCCESS;
	ACTION action;

	time_t t;
	string protocol;

	StorageData	data;

	variable_binding_t policy_output, org_policy_input;

	/* Store an unmodified policy_input for comparison. */
	org_policy_input = policy_input;

	/* Run input policy over policy_input. */
	t = time(NULL);
	action = this->inputPolicy.evaluate(policy_output, policy_input);
	TRACE(TRACE_TIMER, "inputPolicy.evaluate() ran %lu sec for:  element %s, interface %s, ip %s", time(NULL) - t, policy_input["element-id"].c_str(), policy_input["interface-id"].c_str(), policy_input["ip-id"].c_str());

	if (action == ACTION_REJECT) {
		TRACE(TRACE_DEBUG, "Input policy rejected. Proceeding to next message. element %s, interface %s, ip %s", policy_input["element-id"].c_str(), policy_input["interface-id"].c_str(), policy_input["ip-id"].c_str());

		return result;
	} else if (action == ACTION_FAILURE) {
		LMSG("Input policy evaluation failed. Shutting down. element %s, interface %s, ip %s", policy_input["element-id"].c_str(), policy_input["interface-id"].c_str(), policy_input["ip-id"].c_str());

		interrupt = true;
		result = FAILURE;

		return result;
	}

	/* Copy data over for output policy. */
	policy_input = policy_output;
	policy_output.clear();

	/* Run output policy over policy_input. */
	t = time(NULL);
	action = this->outputPolicy.evaluate(policy_output, policy_input);
	TRACE(TRACE_TIMER, "outputPolicy.evaluate() ran %lu sec for:  element %s, interface %s, ip %s", time(NULL) - t, policy_input["element-id"].c_str(), policy_input["interface-id"].c_str(), policy_input["ip-id"].c_str());

	if (action == ACTION_REJECT) {
		TRACE(TRACE_DEBUG, "Output policy rejected. Proceeding to next message. element %s, interface %s, ip %s", policy_input["element-id"].c_str(), policy_input["interface-id"].c_str(), policy_input["ip-id"].c_str());

		return result;
	} else if (action == ACTION_FAILURE) {
		LMSG("Output policy evaluation failed. Shutting down. element %s, interface %s, ip %s", policy_input["element-id"].c_str(), policy_input["interface-id"].c_str(), policy_input["ip-id"].c_str());

		interrupt = true;
		result = FAILURE;

		return result;
	}

	/* Clear tmp policy_input. */
	policy_input.clear();

	/* Deal with state changes. */
	result = processChange(org_policy_input, policy_output, tableMap);

	return result;
}

RESULT State::processChange(variable_binding_t& policy_input, variable_binding_t& policy_output, hash_map<string, string>& tableMap) {
	RESULT result = SUCCESS;

	const short TABLE_UNKNOWN = 0, TABLE_ELEMENT = 1, TABLE_INTERFACE = 2, TABLE_IP = 3;
	short table;
	unsigned long id;

	size_t buf_size = 132;
	char buf[buf_size];
	bool duplicate;

	IPv4State *ipv4_state;
	InterfaceState *interface_state;
	ElementState *element_state;

	/* Syncronize table specific and non-table specific entries. */
	for (variable_binding_t::iterator it = policy_output.begin(); it != policy_output.end(); ++it) {
		TRACE(TRACE_INTERNAL, "considering %s", it->first.c_str());

		/* Don't check non-state entries. */
		if (it->first.size() <= 6 || it->first.substr(it->first.size() - 6, 6).compare("-state")) {
			TRACE(TRACE_INTERNAL, "non-state");
			continue;
		}

		/* Syncronize data only if policy_output state differs from the policy_input state. */
		if (policy_input.find(it->first) == policy_input.end() || !policy_input[it->first].compare(policy_output[it->first])) {
			TRACE(TRACE_INTERNAL, "didn't change %s == %s", policy_input[it->first].c_str(), policy_output[it->first].c_str());
			continue;
		}

		TRACE(TRACE_INTERNAL, "checking %s", it->first.c_str());

		/* If table specific changed, then set non-table specific to same state. */
		if (!it->first.substr(0, 3).compare("ip-")) {
			policy_output[it->first.substr(3)] = policy_output[it->first];

			TRACE(TRACE_INTERNAL, "syncing  %s over %s -> value == %s", it->first.c_str(), it->first.substr(3, it->first.size()).c_str(), policy_input[it->first].c_str());
		} else if (!it->first.substr(0, 10).compare("interface-")) {
			policy_output[it->first.substr(10)] = policy_output[it->first];

			TRACE(TRACE_INTERNAL, "syncing  %s over %s -> value == %s", it->first.c_str(), it->first.substr(10, it->first.size()).c_str(), policy_input[it->first].c_str());
		} else if (!it->first.substr(0, 8).compare("element-")) {
			policy_output[it->first.substr(8)] = policy_output[it->first];

			TRACE(TRACE_INTERNAL, "syncing  %s over %s -> value == %s", it->first.c_str(), it->first.substr(8, it->first.size()).c_str(), policy_input[it->first].c_str());
		} else if (tableMap.find(it->first) != tableMap.end()) { /* Handle non-table specific case. */
			snprintf(buf, buf_size, "%s-%s", tableMap[it->first].c_str(), it->first.c_str());
			policy_output[buf] = policy_output[it->first];

			TRACE(TRACE_INTERNAL, "syncing  %s over %s -> value == %s", it->first.c_str(), buf, policy_input[it->first].c_str());
		}
	}

	/* Update table for each state change. */
	for (variable_binding_t::iterator it = policy_output.begin(); it != policy_output.end(); ++it) {
		TRACE(TRACE_INTERNAL, "--CONSIDERING %s", it->first.c_str());
		/* Don't check non-state entries. */
		if (it->first.size() <= 6 || it->first.substr(it->first.size() - 6, 6).compare("-state")) {
			TRACE(TRACE_INTERNAL, "non-state");
			continue;
		}

		TRACE(TRACE_INTERNAL, "comparing %s => %s to %s", it->first.c_str(), policy_input[it->first].c_str(), policy_output[it->first].c_str());

		/* Update only if policy_output state differs from the policy_input state. */
		if (policy_input.find(it->first) != policy_input.end() && !policy_input[it->first].compare(policy_output[it->first])) {
			TRACE(TRACE_INTERNAL, "didn't differ %s -> %s vs %s", it->first.c_str(), policy_input[it->first].c_str(), policy_output[it->first].c_str());
			continue;
		}

		/* Update only if this entry doesn't have a specific table entry. */
		if (tableMap.find(it->first) != tableMap.end()) {
			snprintf(buf, buf_size, "%s-%s", tableMap[it->first].c_str(), it->first.c_str());
			if (tableMap.find(buf) != tableMap.end() && !tableMap[it->first].compare(tableMap[buf])) {
				continue;
			}
		}

		if (tableMap.find(it->first) != tableMap.end()) { /* Update existing state entry. */
			if (!tableMap[it->first].compare("ip")) {
				table = TABLE_IP;
			} else if (!tableMap[it->first].compare("interface")) {
				table = TABLE_INTERFACE;
			} else if (!tableMap[it->first].compare("element")) {
				table = TABLE_ELEMENT;
			} else {
				table = TABLE_UNKNOWN;
			}
		} else if (!it->first.substr(0, 3).compare("ip-")) {
			table = TABLE_IP;
		} else if (!it->first.substr(0, 10).compare("interface-")) {
			table = TABLE_INTERFACE;
		} else if (!it->first.substr(0, 8).compare("element-")) {
			table = TABLE_ELEMENT;
		} else {
			if (policy_input.find("ip-id") != policy_input.end()) {
				table = TABLE_IP;
			} else if (policy_input.find("interface-id") != policy_input.end()) {
				table = TABLE_INTERFACE;
			} else if (policy_input.find("element-id") != policy_input.end()) {
				table = TABLE_ELEMENT;
			} else {
				table = TABLE_UNKNOWN;
			}
		}

		string protocol = it->first.substr(0, it->first.size() - 6);
		TRACE(TRACE_INTERNAL, "proto: %s", protocol.c_str());

		switch (table) {
			case TABLE_ELEMENT:
				id = strtoul(policy_input["element-id"].c_str(), NULL, 10);

				TRACE(TRACE_DEBUG, "updating element_attr %s id %lu to state %s", it->first.c_str(), id, policy_output[it->first].c_str());

				if (!it->first.substr(0, 8).compare("element-")) {
					element_state = new ElementState(&(this->storage), id, it->first.substr(8, it->first.size() - (6 + 8)).c_str());
				} else {
					element_state = new ElementState(&(this->storage), id, it->first.substr(0, it->first.size() - 6).c_str());
				}
				element_state->updateTimestamp(false);
				element_state->set(policy_output[it->first].c_str());

				SAFE_DELETE(element_state);
				break;
			case TABLE_INTERFACE:
				id = strtoul(policy_input["interface-id"].c_str(), NULL, 10);

				TRACE(TRACE_DEBUG, "updating interface_attr %s id %lu to state %s", it->first.c_str(), id, policy_output[it->first].c_str());

				if (!it->first.substr(0, 10).compare("interface-")) {
					interface_state = new InterfaceState(&(this->storage), id, it->first.substr(10, it->first.size() - (6 + 10)).c_str());
				} else {
					interface_state = new InterfaceState(&(this->storage), id, it->first.substr(0, it->first.size() - 6).c_str());
				}
				interface_state->updateTimestamp(false);
				interface_state->set(policy_output[it->first].c_str());

				SAFE_DELETE(interface_state);
				break;
			case TABLE_IP:
				id = strtoul(policy_input["ip-id"].c_str(), NULL, 10);

				TRACE(TRACE_DEBUG, "updating ip_attr %s id %lu to state %s", it->first.c_str(), id, policy_output[it->first].c_str());

				if (!it->first.substr(0, 3).compare("ip-")) {
					ipv4_state = new IPv4State(&(this->storage), id, it->first.substr(3, it->first.size() - (6 + 3)).c_str());
				} else {
					ipv4_state = new IPv4State(&(this->storage), id, it->first.substr(0, it->first.size() - 6).c_str());
				}
				ipv4_state->updateTimestamp(false);
				ipv4_state->set(policy_output[it->first].c_str());

				SAFE_DELETE(ipv4_state);
				break;
			default:
				TRACE(TRACE_DEBUG, "unable to determine table for %s state change", it->first.c_str());
		}
   }

	return result;
}

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