/*
** SellaNMS
** $Id: state.cxx,v 1.9 2005/08/18 05:54:26 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.9 2005/08/18 05:54:26 sella Exp $";

#include "state.h"
#include "common/util.h"
#include "common/netblock.h"
#include "common/storagedata.h"
#include "policy_framework/policy_framework.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/list>
#include <stl/queue>
#include <stl/list>
#include <stl/string>

extern ConfigNode* config;

extern bool interrupt;

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

State::~State() {
}

RESULT State::execute() {
	while (!interrupt) {
		this->update("network_element_interface_ip");	

		sleep(this->interval);
	}
	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, 30, 1);

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

			return FAILURE;
		}
	}

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

		return FAILURE;
	}

	/* Configure Storage */
	LMSG("Setting storage config...");
	storage.setConfig(*config);

	printConfig();

	return result;
}

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

	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 SUCCESS;
}

void State::update(const char *table) {
	char			sql[1024];
	time_t		new_timestamp;
	long			index;

	string		variable;
	string		value;

	StorageData	indexes;
	StorageData	data;

	variable_binding_t	input;
	variable_binding_t	output;

	time(&new_timestamp);

	TRACE(TRACE_INTERNAL, "START");

	/* FIXME: This is an extremely long lock. */

	snprintf(sql, 1024, "LOCK TABLES %s_attrib WRITE", table);
	if (storage.query(data, sql) == FAILURE) {
		TRACE(TRACE_INTERNAL, "SQL failed: %s", sql);
		return;
	}
	TRACE(TRACE_INTERNAL, "LOCKED");
	TRACE(TRACE_INTERNAL, "CHECKING NEWER THAN: %d", this->timestamp);

	snprintf(sql, 1024, "SELECT DISTINCT network_element_interface_ip FROM %s_attrib WHERE timestamp > FROM_UNIXTIME('%d') ORDER BY network_element_interface_ip", table, this->timestamp);
	if (storage.query(indexes, sql) == FAILURE) {
		TRACE(TRACE_INTERNAL, "SQL failed: %s", sql);
		return;
	}
	TRACE(TRACE_INTERNAL, "LOOPING");
	while (indexes.nextRow() && !interrupt) {
		if (indexes.getLong(index, "network_element_interface_ip")) {
			snprintf(sql, 1024, "SELECT variable, value FROM %s_attrib WHERE network_element_interface_ip = '%d' AND (variable LIKE '%%-state' OR variable LIKE '%%-state-changes')", table, index);

			if (storage.query(data, sql) == FAILURE) {
				TRACE(TRACE_INTERNAL, "SQL failed: %s", sql);
				return;
			}
			output.clear();
			input.clear();

			/* Setup generic data. */
			input["protocol"] = "state";
			input["origin"] = "attrib";

			TRACE(TRACE_INTERNAL, "UPDATE: %d", index);
			while (data.nextRow() && !interrupt) {
				if (data.getString(variable, "variable")) {
					if (data.getString(value, "value")) {
						TRACE(TRACE_INTERNAL, "  input[%s] = %s;", variable.c_str(), value.c_str());
						input[variable] = value;	
					}
				}
			}
			this->outputPolicy.evaluate(output, input);

			for (variable_binding_t::iterator i = output.begin(); i != output.end(); ++i) {
				if (input.find(i->first) != input.end()) {
					if ((i->second).compare(input[i->first])) {
						TRACE(TRACE_INTERNAL, "  CHANGING FROM %s TO %s", input[i->first].c_str(), i->second.c_str());
						snprintf(sql, 1024, "UPDATE %s_attrib SET value = '%s' WHERE variable = '%s' AND network_element_interface_ip='%d'", table, i->second.c_str(), i->first.c_str(), index);
						TRACE(TRACE_INTERNAL, "%s", sql);

						if (storage.query(data, sql) == FAILURE) {
							TRACE(TRACE_INTERNAL, "SQL failed: %s", sql);
							return;
						}
					} else {
						TRACE(TRACE_INTERNAL, "  NO CHANGE: %s", i->first.c_str());
					}
				} else {
					TRACE(TRACE_INTERNAL, "  SETTING TO %s", i->first.c_str());
					snprintf(sql, 1024, "INSERT INTO %s_attrib SET variable='%s', value='%s', network_element_interface_ip='%d'", table, i->first.c_str(), i->second.c_str(), index);

					if (storage.query(data, sql) == FAILURE) {
						TRACE(TRACE_INTERNAL, "SQL failed: %s", sql);
						return;
					}
				}
			}
			TRACE(TRACE_INTERNAL, "  UPDATING TIMESTAMP TO: %d", new_timestamp);
			snprintf(sql, 1024, "UPDATE %s_attrib SET timestamp=FROM_UNIXTIME('%d') WHERE network_element_interface_ip='%d'", table, new_timestamp, index);
			if (storage.query(data, sql) == FAILURE) {
				TRACE(TRACE_INTERNAL, "SQL failed: %s", sql);
				return;
			}
		}
	}
	snprintf(sql, 1024, "UNLOCK TABLES");
	if (storage.query(data, sql) == FAILURE) {
		TRACE(TRACE_INTERNAL, "SQL failed: %s", sql);
		return;
	}
	TRACE(TRACE_INTERNAL, "DONE");

	this->timestamp = new_timestamp;
}

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