/*
** SellaNMS
** $Id: topology.cxx,v 1.24 2008/01/31 22:31:08 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: topology.cxx,v 1.24 2008/01/31 22:31:08 sella Exp $";

#include "daemon.h"
#include "main.h"
#include "topology.h"
#include "storage/storagedata.h"
#include <arpa/inet.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/in.h>
#include <stl/algorithm>

extern int errno;
extern bool interrupt;
extern ConfigNode* config;

Topology::Topology():mergePercent(0), duplicatePercent(0), maxEntryDelete(0) {
	this->matrix.added_len = 0;
	this->matrix.existing_len = 0;
	this->matrix.matrix = NULL;
}

Topology::~Topology() {
	for (unsigned long k = 0; k < this->matrix.existing_len; k++) {
		SAFE_DELETE_ARRAY(this->matrix.matrix[k]);
	}
	SAFE_DELETE_ARRAY(this->matrix.matrix);
}

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

	unsigned long tmp;
	vector<string> v;
	string str;

   ConfigNode node;

	if (!config->assignNode(getConfigPath(), node)) {
		LMSG("No '%s' section found within the config file.", getConfigPath());

		return FAILURE;
	}
	
	/* Percentage of IP interfaces that must match between elements to be merged (assumed to be the same element). */
	node.assignValue("merge-percent", tmp, DISCOVERY_TOPOLOGY_MERGEPERCENT_DEF, DISCOVERY_TOPOLOGY_MERGEPERCENT_MIN, DISCOVERY_TOPOLOGY_MERGEPERCENT_MAX);
	this->mergePercent = tmp;

	/* Percentage of IP interfaces that must match between elements to be considered a duplicate (assumed to be the same element). */
	node.assignValue("duplicate-percent", tmp, DISCOVERY_TOPOLOGY_DUPLICATEPERCENT_DEF, DISCOVERY_TOPOLOGY_DUPLICATEPERCENT_MIN, DISCOVERY_TOPOLOGY_DUPLICATEPERCENT_MAX);
	this->duplicatePercent = tmp;

	/* Maximum number of entries to delete at once from element_data. :*/
	node.assignValue("maximum-entry-delete", this->maxEntryDelete, DISCOVERY_TOPOLOGY_MAXENTRYDELETE_DEF, DISCOVERY_TOPOLOGY_MAXENTRYDELETE_MIN, DISCOVERY_TOPOLOGY_MAXENTRYDELETE_MAX);

	if (node.assignValue("duplicate-algo", str)) {
		if (!strcasecmp(str.c_str(), "a-and-b")) {
			duplicateAlgo = DUPLICATEALGO_A_AND_B_GE_MATCH_PERCENTAGE;
		} else if (!strcasecmp(str.c_str(), "a-or-b")) {
			duplicateAlgo = DUPLICATEALGO_A_OR_B_GE_MATCH_PERCENTAGE;
		} else {
			duplicateAlgo = DISCOVERY_TOPOLOGY_DUPLICATEALGO_DEF;
		}
	} else {
		duplicateAlgo = DISCOVERY_TOPOLOGY_DUPLICATEALGO_DEF;
	}

	if (node.assignValue("merge-algo", str)) {
		if (!strcasecmp(str.c_str(), "hostname-or-merge-percentage")) {
			mergeAlgo = MERGEALGO_HOSTNAME_OR_MERGE_PERCENTAGE;
		} else if (!strcasecmp(str.c_str(), "hostname-and-merge-percentage")) {
			mergeAlgo = MERGEALGO_HOSTNAME_AND_MERGE_PERCENTAGE;
		} else if (!strcasecmp(str.c_str(), "hostname-only")) {
			mergeAlgo = MERGEALGO_HOSTNAME_ONLY;
		} else if (!strcasecmp(str.c_str(), "merge-percentage-only")) {
			mergeAlgo = MERGEALGO_MERGE_PERCENTAGE_ONLY;
		} else {
			mergeAlgo = DISCOVERY_TOPOLOGY_MERGEALGO_DEF;
		}
	} else {
		mergeAlgo = DISCOVERY_TOPOLOGY_MERGEALGO_DEF;
	}

	/* Clear any existing policies. */
	inputPolicy.clear();
	outputPolicy.clear();

	/* Add and compile input policy. */
	if (node.assignValues("policy input", v)) {
		if (inputPolicy.addPolicies(config, v) == FAILURE) {
			LMSG("Failed to add/compile input policies.");
			result = 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.");
			result = FAILURE;
		}
	}

	/* Configure Storage */
	if (storage.setConfig(*config) != SUCCESS) {
		result = FAILURE;
	}

	printConfig();

	return result;
}

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

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

	TRACE(TRACE_CONFIG, "[Configuration]");

	switch (mergeAlgo) {
		case MERGEALGO_HOSTNAME_OR_MERGE_PERCENTAGE:
			TRACE(TRACE_CONFIG, "merge-algo: hostname-or-merge-percentage");
			break;

		case MERGEALGO_HOSTNAME_AND_MERGE_PERCENTAGE:
			TRACE(TRACE_CONFIG, "merge-algo: hostname-and-merge-percentage");
			break;

		case MERGEALGO_HOSTNAME_ONLY:
			TRACE(TRACE_CONFIG, "merge-algo: hostname-only");
			break;

		case MERGEALGO_MERGE_PERCENTAGE_ONLY:
			TRACE(TRACE_CONFIG, "merge-algo: merge-percentage-only");
			break;
	}

	TRACE(TRACE_CONFIG, "merge-percent: %0.0f", this->mergePercent);

	switch (duplicateAlgo) {
		case DUPLICATEALGO_A_AND_B_GE_MATCH_PERCENTAGE:
			TRACE(TRACE_CONFIG, "duplicate-algo: a-and-b");
			break;

		case DUPLICATEALGO_A_OR_B_GE_MATCH_PERCENTAGE:
			TRACE(TRACE_CONFIG, "duplicate-algo: a-or-b");
			break;
	}

	TRACE(TRACE_CONFIG, "duplicate-percent: %0.0f", this->duplicatePercent);
	TRACE(TRACE_CONFIG, "maximum-entry-delete: %lu", this->maxEntryDelete);

	if (config->assignValues("discovery topology 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("discovery topology 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 Topology::execute() {
	RESULT result = SUCCESS;

	TRACE(TRACE_THREAD, "starting execute.");
	if (!interrupt && (result = correlateIpToElement()) != SUCCESS) {
		LMSG("correlateIpToElement failed");
		return result;
	}

	if (!interrupt && (result = compareNewToExistingDiscovery()) != SUCCESS) {
		LMSG("compareNewToExistingDiscovery failed");
		return result;
	}

	if (!interrupt && (result = processDiscoveryResults()) != SUCCESS) {
		LMSG("processDiscoveryResults failed");
		return result;
	}

	if (!interrupt && (result = processLinks()) != SUCCESS) {
		LMSG("processLinks failed");
		return result;
	}

	if (!interrupt && (result = optimizeDatabase()) != SUCCESS) {
		LMSG("optimizeDatabase failed");
		return result;
	}
	TRACE(TRACE_THREAD, "finished execute.");

	return result;
}

RESULT Topology::correlateIpToElement() {
	RESULT result = SUCCESS;

	TRACE(TRACE_DEBUG, "mapping IP addresses to elements.");
	if ((result = loadNewDiscovery()) != SUCCESS) {
		LMSG("Topology::loadNewDiscovery() failed");
	} else {
		if ((result = loadExistingDiscovery()) != SUCCESS) {
			LMSG("Topology::loadExistingDiscovery() failed");
		}
	}

	if (TRACE_ISSET(TRACE_DUMP)) {
		LMSG("Printing New Discovery Layer3 Hash");
		printLayer3Hash(new_discovery);

		LMSG("Printing Existing Discovery Layer3 Hash");
		printLayer3Hash(existing_discovery);
	}

	return result;
}

RESULT Topology::compareNewToExistingDiscovery() {
	RESULT result = SUCCESS;

	unsigned long i = 0;
	unsigned long j = 0;

	this->matrix.added_len = new_discovery.size();
	this->matrix.existing_len = existing_discovery.size();

	TRACE(TRACE_DEBUG, "merging network discovery with previous network topology.");

	this->matrix.matrix = new int*[this->matrix.existing_len];
	if (!(this->matrix.matrix)) {
		LMSG("Failed to allocate the matrix");

		result = FAILURE;
	} else {
		for (unsigned long k = 0; k < this->matrix.existing_len; k++) {
			this->matrix.matrix[k] = new int[this->matrix.added_len];
			if (!(this->matrix.matrix[k])) {
				LMSG("Failed to allocate the matrix");

				result = FAILURE;
			} else {
				memset(this->matrix.matrix[k], 0, this->matrix.added_len * sizeof(int));
			}
		}
	}

	if (result == SUCCESS) {
		i = 0;
		for (layer3_hash_iter_t it = existing_discovery.begin(); it != existing_discovery.end(); ++it) {
			j = 0;
			for (layer3_hash_iter_t jt = new_discovery.begin(); jt != new_discovery.end(); ++jt) {
	 			switch (mergeAlgo) {
					/* Merge if hostname matches or mergePercentage matches.
						(Most Reliable) [Use high mergePercent for best results]
					*/
					case MERGEALGO_HOSTNAME_OR_MERGE_PERCENTAGE:
						if (compareSetsHostname(it->first, jt->first) == true \
							|| compareSetsIP(it->second, jt->second, mergePercent) == true) {
							this->matrix.matrix[i][j] = (int) 1;
						}

						break;

					/* Merge if hostname matches and mergePercentage matches.
						(Second Most Reliable) [Use low mergePercent for best results]
					*/
					case MERGEALGO_HOSTNAME_AND_MERGE_PERCENTAGE:
						if (compareSetsHostname(it->first, jt->first) == true \
							&& compareSetsIP(it->second, jt->second, mergePercent) == true) {
							this->matrix.matrix[i][j] = (int) 1;
						}
						break;

					/* Merge if hostname matches.
						(Usually Reliable)
					*/
					case MERGEALGO_HOSTNAME_ONLY:
						if (compareSetsHostname(it->first, jt->first) == true) {
							this->matrix.matrix[i][j] = (int) 1;
						}
						break;

					/* Merge if mergePercent matches
						(Least Reliable) [Use ~50% mergePercent for best results]
					*/
					case MERGEALGO_MERGE_PERCENTAGE_ONLY:
						if (compareSetsIP(it->second, jt->second, mergePercent) == true) {
							this->matrix.matrix[i][j] = (int) 1;
						}
						break;
				}
				j++;
			}
			i++;
		}
	}
	
	return result;
}

RESULT Topology::processDiscoveryResults() {
	RESULT result = SUCCESS;

	list<unsigned long> added_elements;
	list<unsigned long> existing_elements;

	TRACE(TRACE_DEBUG, "processing discovery results.");

	if ((result = classifyElements(added_elements, existing_elements)) != SUCCESS) {
		LMSG("Topology::classifyElements() failed");
	} else {
		if ((result = processAddedElements(added_elements)) != SUCCESS) {
			LMSG("Topology::processAddedElements() failed");
		}
		if ((result = processExistingElements(existing_elements)) != SUCCESS) {
			LMSG("Topology::processExistingElements() failed");
		}
	}

	return result;
}

RESULT Topology::classifyElements(list<unsigned long> &added_elements, list<unsigned long> &existing_elements) {
	RESULT result = SUCCESS;
	
	unsigned long i = 0;
	unsigned long j = 0;

	int is_added[this->matrix.added_len];
	memset(is_added, 1, this->matrix.added_len * sizeof(int));

	for (i = 0; i < this->matrix.existing_len; i++) {
		for (j = 0; j < this->matrix.added_len; j++) {
			if (this->matrix.matrix[i][j]) {
				is_added[j] = 0;
			}
		}
	}

	i = 0;
	for (layer3_hash_iter_t it = new_discovery.begin(); it != new_discovery.end(); ++it) {
		if (is_added[i++]) {
			added_elements.push_back(it->first);
		} else {
			existing_elements.push_back(it->first);
		}
	}

	return result;
}

RESULT Topology::processLinks() {
	RESULT result = SUCCESS;

	link_hash_t new_link_hash;
	link_hash_t old_link_hash;

	TRACE(TRACE_DEBUG, "processing element links.");

	/* Build links from the discovery that was just commited. */
	if (!interrupt && (result = buildLinks(new_link_hash)) != SUCCESS) {
		LMSG("WARNING: Failed to build links from discovery information.");
		return result;
	}

	/* Read in pre-existing (old) links. */
	if (!interrupt && (result = loadExistingLinks(old_link_hash)) != SUCCESS) {
		LMSG("WARNING: Failed load existing links.");
		return result;
	}

	/* Seperate out the new links from the existing (old). */
	if (!interrupt && (result = isolateNewLinks(new_link_hash, old_link_hash)) != SUCCESS) {
		LMSG("WARNING: Failed to isolate new links from previous links.");
		return result;
	}

	/* Write our newly found links. */
	if (!interrupt && (result = insertNewLinks(new_link_hash)) != SUCCESS) {
		LMSG("WARNING: Failed to insert new links.");
		return result;
	}

	/* Remove Orphaned Link Points */
	if (!interrupt && (result = removeOrphanLinkPoints()) != SUCCESS) {
		LMSG("WARNING: Failed to remove orphaned link points.");
		return result;
	}

	/* Remove unused/unreferenced Links. */
	if (!interrupt && (result = removeUnusedLinks()) != SUCCESS) {
		LMSG("WARNING: Failed to remove unused links.");
		return result;
	}

	return result;
}

RESULT Topology::buildLinks(link_hash_t &link_hash) {
	RESULT result = SUCCESS;

	Netblock n;
	string str, sql;
	char buf[132];
	unsigned long element, ip, network, netmask;

	set<string> used_address;
	link_builder_t link_builder;
	hash_map<unsigned long, link_builder_t> builder_hash;

	variable_binding_t policy_input;
	variable_binding_t policy_output;

	StorageData reader;

	if (storage.queryf(reader, "SELECT element.id AS element, ip.id AS ip, ip_data.variable AS variable, ip_data.value AS value FROM element, interface, ip, ip_data WHERE element.id = interface.element AND interface.id = ip.interface AND ip.id = ip_data.ip AND ((ip_data.variable = '%s') OR (ip_data.variable = '%s')) ORDER BY element.id, ip.id", IP_IPADDRTABLE_IPADDRENTRY_IPADENTADDR, IP_IPADDRTABLE_IPADDRENTRY_IPADENTNETMASK) != SUCCESS) {
		LMSG("ERROR: Unable to read existing ip addresses.");
		
		result = FAILURE;
	} else {
		/* Set policy event */
		policy_input["protocol"] = "topology";
		policy_input["event"] = "link";

		/* Build up address/netmask sets, in preperation for building links. */
		while (reader.nextRow()) {
			reader.getULong(element, "element");
			reader.getULong(ip, "ip");

			if (builder_hash.find(ip) == builder_hash.end()) {
				link_builder.element = element;
				link_builder.network.erase();
				link_builder.netmask.erase();
				builder_hash[ip] = link_builder;
			} else {
				link_builder = builder_hash[ip];
				if (element != link_builder.element) {
					LMSG("An address or netmask was returned from storage with the same ip id, but different element ids. This should never happen.");
					continue;
				}
			}

			reader.getString(str, "variable");
			if (str.compare(IP_IPADDRTABLE_IPADDRENTRY_IPADENTADDR) == 0) {
				reader.getString(str, "value");
				link_builder.network = str;
			} else if (str.compare(IP_IPADDRTABLE_IPADDRENTRY_IPADENTNETMASK) == 0) {
				reader.getString(str, "value");
				link_builder.netmask = str;
			} else {
				/* Samity Check.. can be removed. Even the continue.. since this never happens. */
				LMSG("Either str was NULL, or I got an variable that I shouldn't have.");
				continue;
			}

			builder_hash[ip] = link_builder;
		}

		reader.clear();

		/* Build links from builder_hash. */
		for (hash_map<unsigned long, link_builder_t>::iterator it = builder_hash.begin(); it != builder_hash.end(); ++it) {
			if (it->second.network.empty() || it->second.netmask.empty()) {
				if (it->second.network.empty() && it->second.netmask.empty()) {
					LMSG("Network and Netmask were not set in builder_hash. Element: %d", it->second.element);
				} else if (it->second.network.empty()) {
					LMSG("Network was not set in builder_hash. Element: %d, Netmask: %s", it->second.element, it->second.netmask.c_str());
				} else {
					LMSG("Netmask was not set in builder_hash. Element: %d, Network: %s", it->second.element, it->second.network.c_str());
				}
			} else {
				TRACE(TRACE_INTERNAL, "existing: %s/%s (point %d [x_interface_ip.id]).", it->second.network.c_str(), it->second.netmask.c_str(), it->first);
				n.setNetblock(it->second.network.c_str(), it->second.netmask.c_str());

				/* Skip /32s. */
				n.getNetmask(netmask);
				if (netmask == 0xffffffff) {
					TRACE(TRACE_INTERNAL, "skipping a /32. Element: %d, Network: %s, Netmask: %s", it->second.element, it->second.network.c_str(), it->second.netmask.c_str());
					continue;
				}
				
				/* Prevent duplicate IP's from building a link. */
				if (used_address.find(it->second.network) != used_address.end()) {
					TRACE(TRACE_INTERNAL, "skipping duplicate IP address. Element: %d, Network: %s, Netmask: %s", it->second.element, it->second.network.c_str(), it->second.netmask.c_str());
					continue;
				}

				/* Store IP address for duplicate detection. */
				used_address.insert(it->second.network);

				/* Netblock */
				n.getNetworkCidrAddress(buf);
				policy_input["ip"] = buf;

				/* Execute policy */
				if (inputPolicy.evaluate(policy_output, policy_input) != ACTION_REJECT) {
					n.getNetworkAddress(network);

					/* Create a hash entry for new links under this network. */
					if (link_hash.find(network) == link_hash.end()) {
						link_hash[network];
					}
					link_hash[network].link = 0; /* No assigned link id yet. */
					link_hash[network].points.push_front(it->first);
				}
			}
		}
	}

	return result;
}

RESULT Topology::loadExistingLinks(link_hash_t &link_hash) {
	RESULT result = SUCCESS;

	Netblock n;
	string str;
	unsigned long element, ip, network, point, link;
	link_builder_t link_builder;
	hash_map<unsigned long, link_builder_t> builder_hash;

	StorageData reader;

	if (storage.queryf(reader, "SELECT link_point.point AS point, link_point.link AS link, element.id AS element, ip.id AS ip, ip_data.variable AS variable, ip_data.value AS value FROM link, link_point, element, interface, ip, ip_data WHERE (link.id = link_point.link AND link_point.point = ip.id AND element.id = interface.element AND interface.id = ip.interface AND ip.id = ip_data.ip) AND link.layer = 'ip' AND ((ip_data.variable = '%s') OR (ip_data.variable = '%s')) ORDER BY ip.id", IP_IPADDRTABLE_IPADDRENTRY_IPADENTADDR, IP_IPADDRTABLE_IPADDRENTRY_IPADENTNETMASK) != SUCCESS) {
		LMSG("ERROR: Unable to read existing ip addresses.");
		
		result = FAILURE;
	} else {
		/* Build up address/netmask,a_loc/z_loc sets, in preperation for building links. */
		while (reader.nextRow()) {
			reader.getULong(point, "point");
			reader.getULong(link, "link");
			reader.getULong(element, "element");
			reader.getULong(ip, "ip");

			if (builder_hash.find(ip) == builder_hash.end()) {
				link_builder.element = element;
				link_builder.link = link;
				link_builder.point = point;
				link_builder.network.erase();
				link_builder.netmask.erase();
				builder_hash[ip] = link_builder;
			} else {
				link_builder = builder_hash[ip];
				if (element != link_builder.element) {
					LMSG("An address or netmask was returned from storage with the same ip id, but different element ids. This should never happen.");
					continue;
				}
			}

			reader.getString(str, "variable");
			if (str.compare(IP_IPADDRTABLE_IPADDRENTRY_IPADENTADDR) == 0) {
				reader.getString(str, "value");
				link_builder.network = str;
			} else if (str.compare(IP_IPADDRTABLE_IPADDRENTRY_IPADENTNETMASK) == 0) {
				reader.getString(str, "value");
				link_builder.netmask = str;
			} else {
				LMSG("Either str was NULL, or I got an variable that I shouldn't have.");
				continue;
			}

			builder_hash[ip] = link_builder;
		}

		reader.clear();

		/* Build links from builder_hash. */
		for (hash_map<unsigned long, link_builder_t>::iterator it = builder_hash.begin(); it != builder_hash.end(); ++it) {
			if (it->second.network.size() > 0 && it->second.netmask.size() > 0) {
				n.setNetblock(it->second.network.c_str(), it->second.netmask.c_str());
				TRACE(TRACE_INTERNAL, "existing: %s/%s (point %d)", it->second.network.c_str(), it->second.netmask.c_str(), it->second.point);

				if (n.isValid()) { /* Just a sanity check (it wouldn't be in the DB, if it wasn't valid. Can be removed. */
					n.getNetworkAddress(network);
					if (link_hash.find(network) == link_hash.end()) {
						link_hash[network];
					}
					link_hash[network].link = it->second.link;
					link_hash[network].points.push_front(it->second.point);
				} else {
					LMSG("The netblock (%s) /netmask (%s) returned from storage was invalid. This shouldn't happen.", it->second.network.c_str(), it->second.netmask.c_str());
				}
			} else {
				LMSG("Network or Netmask wasn't set in last loop. Network.size() == %d,  Netmask.size() == %d", it->second.network.size(), it->second.netmask.size());
			}
		}
	}

	return result;
}

RESULT Topology::isolateNewLinks(link_hash_t &new_link_hash, link_hash_t &old_link_hash) {
	slist<unsigned long> difference;
	link_hash_t::iterator it, jt;
	unsigned long quick_comp = 0, list_comp = 0;

	/* Sort new_link lists, for the compare (set_difference requires ascending order). */
	for (it = new_link_hash.begin(); it != new_link_hash.end(); ++it) {
		it->second.points.sort();
	}

	/* Eliminate links, without at least 2 end points. Far less compares needed then. */
	it = new_link_hash.begin();
	while (it != new_link_hash.end()) {
		/* Edge interfaces have only 1 point. */
		if (it->second.points.size() < 2) {
			new_link_hash.erase(it);
			it = new_link_hash.begin();
		} else if (!new_link_hash.empty()) {
			++it;
		}
	}

	/* Sort existing_link lists, for the compare (set_difference requres ascending order). */
	for (jt = old_link_hash.begin(); jt != old_link_hash.end(); ++jt) {
		jt->second.points.sort();
	}

	if (TRACE_ISSET(TRACE_DUMP)) {
		LMSG("New Discovery links (new_link_hash: %d):", new_link_hash.size());
		printLinkHash(new_link_hash);

		LMSG("Current Links (old_link_hash: %d):", old_link_hash.size());
		printLinkHash(old_link_hash);

	}

	for (it = new_link_hash.begin(); it != new_link_hash.end(); ++it) {
		for (jt = old_link_hash.begin(); jt != old_link_hash.end(); ++jt) {
			quick_comp++;

			/* Netblocks must match. */
			if (it->first == jt->first) {
				/* Does at least one point match a pre-existing link. */
				set_difference(it->second.points.begin(), it->second.points.end(), jt->second.points.begin(), jt->second.points.end(), inserter(difference, difference.begin()));

				TRACE(TRACE_INTERNAL, "NEW: %d, OLD: %d, DIFF: %d", it->second.points.size(), jt->second.points.size(), difference.size());

				if (difference.empty()) { /* Perfect Match. No need to add any points, its Prego. (Its already in there.) */
					TRACE(TRACE_INTERNAL, "exact Match - All points already in place.");

					/* Clear the list out. We don't want to add any points. */
					it->second.points.clear();
					break;
				} else if (difference.size() < it->second.points.size()) { /* Partial Match. Add points in the difference. They are new. */
					TRACE(TRACE_INTERNAL, "partial Match - %d points need to be added.", difference.size());

					/* Set the link to the existing link, since we are going to attach these new points to it. */
					it->second.link = jt->second.link;
					/* Set new_hash.points to the difference. Otherwise, we would duplicate existing points. */
					it->second.points = difference;
					difference.clear();
					break;
				} else { /* No Match. Might be a new link, but needs to be checked against other existing points before we know. */
					difference.clear();
				}
			}
		}
	}
	TRACE(TRACE_INTERNAL, "quick Compares: %d, List Compares: %d", quick_comp, list_comp);

	/* Final sweep to remove entires with no points remaining after compare. */
	it = new_link_hash.begin();
	while (it != new_link_hash.end()) {
		if (it->second.points.empty()) {
			new_link_hash.erase(it);
			it = new_link_hash.begin();
		} else if (!new_link_hash.empty()) {
			++it;
		}
	}

	return SUCCESS;
}

	
RESULT Topology::insertNewLinks(link_hash_t &link_hash) {
	RESULT result = SUCCESS;

	string sql;
	char buf[132];
	StorageData data;

	link_hash_iter_t it;
	slist<unsigned long>::iterator jt;

	TRACE(TRACE_INTERNAL, " - Link size %d", link_hash.size());

	for (it = link_hash.begin(); it != link_hash.end(); ++it) {
		/* Is there a known existing link? 0 is a no. */
		if (it->second.link == 0) {
			TRACE(TRACE_INTERNAL, "adding a new link with %d points.", it->second.points.size());

			/* Create a new link to bind the points to. */
			if (storage.query(data, "INSERT INTO link SET layer = 'ip'") != SUCCESS) {
				LMSG("ERROR: Failed to write link. Will skip adding points for this link.");
				continue;
			}

			/* Update the link, with the newly created one. */
			it->second.link = data.getLastInsertId("link");
		}

		for (jt = it->second.points.begin(); jt != it->second.points.end(); ++jt) {
			snprintf(buf, 132, "(%lu,%lu),", it->second.link, *jt);
			sql += buf;
		}
	}

	if (sql.empty()) {
		LMSG("No new links to add.");
	} else {
		sql.erase(sql.size() - 1, 1);
		sql.insert(0, "INSERT INTO link_point (link, point) VALUES ");
		if (storage.query(data, sql.c_str()) != SUCCESS) {
			LMSG("ERROR: Failed to write link points.");

			result = FAILURE;
		}
	}

	return result;
}

bool Topology::compareSetsHostname(unsigned long existing_host, unsigned long new_host) {
	bool result = false;
	struct in_addr e_addr, n_addr;
	char buf[512], e_str[132], n_str[132];
	string e_host, n_host;
	StorageData data;

	if (existing_host == 0 || new_host == 0) {
		return false;
	}

	/* Setup the addresses. */
	e_addr.s_addr = existing_host;
	n_addr.s_addr = new_host;

	if (inet_ntop(AF_INET, &e_addr, e_str, sizeof(e_str)) == NULL) {
		return false;
	}

	if (inet_ntop(AF_INET, &n_addr, n_str, sizeof(n_str)) == NULL) {
		return false;
	}

	/* Select hostname for existing host. */
	snprintf(buf, sizeof(buf), "SELECT element_data.value AS hostname FROM element LEFT JOIN element_data ON (element.id = element_data.element) WHERE element_data.variable = '%s' AND element.netid = '%s' LIMIT 1", ".1.3.6.1.2.1.1.5.0", e_str);
	if (storage.query(data, buf) != SUCCESS || data.getNumRow() < 1) {
		LMSG("ERROR: Failed to find hostname for %s (existing host).", e_str);

		return false;
	}
	data.nextRow();
	data.getString(e_host, "hostname");

	if (e_host.size() < 1) {
		LMSG("WARNING: Hostname for %s is blank. Set the hostname on this device.", e_str);
		return false;
	}

	/* Select hostname for new host. */
	snprintf(buf, sizeof(buf), "SELECT value AS hostname FROM discovery_snmp WHERE variable = '%s' AND ip = '%s' LIMIT 1", ".1.3.6.1.2.1.1.5.0", n_str);
	if (storage.query(data, buf) != SUCCESS || data.getNumRow() < 1) {
		LMSG("ERROR: Failed to find hostname for %s (new host).", n_str);

		return false;
	}
	data.nextRow();
	data.getString(n_host, "hostname");

	if (n_host.size() < 1) {
		LMSG("WARNING: Hostname for %s is blank. Set the hostname on this device.", n_str);
		return false;
	}

	/* Compare hostnames. */
	if (!e_host.compare(n_host)) {
		result = true;
	}

	TRACE(TRACE_INTERNAL, "existing Host: %s (%s), New Host: %s (%s), Result: %s", e_host.c_str(), e_str, n_host.c_str(), n_str, result ? "true" : "false");
	return result;
}

bool Topology::compareSetsIP(set<unsigned long> &a, set<unsigned long> &b, float &match_percent) {
	bool result = false;

	float a_percent, b_percent;
	set<unsigned long> intersection;
	insert_iterator<set<unsigned long> > intersection_iter(intersection, intersection.begin());

	if (a.empty() || b.empty()) {
		return false;
	}

	//Get the intersection of a and b.
	set_intersection(a.begin(), a.end(), b.begin(), b.end(), intersection_iter);

	//No intersection. No match.
	if (intersection.size() < 1) {
		return result;
	} else {
		a_percent = (float) intersection.size()/(float) a.size() * 100;
		b_percent = (float) intersection.size()/(float) b.size() * 100;

		switch (duplicateAlgo) {
			case DUPLICATEALGO_A_AND_B_GE_MATCH_PERCENTAGE:
				if (a_percent >= match_percent && b_percent >= match_percent) {
					result = true;
				}

				break;

			case DUPLICATEALGO_A_OR_B_GE_MATCH_PERCENTAGE:
				//Larger device match % must be >= match_percent to be merged.
				if (a.size() > b.size() && a_percent > match_percent) {
					result = true;
				} else if (b_percent > match_percent) {
					result = true;
				}

				break;
		}

		TRACE(TRACE_INTERNAL, "a.size(): %d, b.size(): %d, a_perc: %f, b_perc: %f, match_percent: %f, result: %d", a.size(), b.size(), a_percent, b_percent, match_percent, result);
	}

	return result;
}

RESULT Topology::processAddedElements(list<unsigned long> &added_elements) {
	RESULT result = SUCCESS;
	list<unsigned long>::iterator l_it;
	struct in_addr a;

	char inet_buffer[16];

	for (l_it = added_elements.begin(); !interrupt && l_it != added_elements.end(); ++l_it) {
		TRACE(TRACE_INTERNAL, "adding element: %s", inet_ntop(AF_INET, &a, inet_buffer, 16));

		result = insertElement(*l_it);
	}

	return result;
}

RESULT Topology::updateElement(unsigned long key) {
	RESULT result = SUCCESS;

	unsigned long element[2];
	char	inet_buffer[16];

	in_addr network_id;

	slist<pair<string, string> > element_data;
	hash_map<string, hash_map<string, string> > interface_data;
	hash_map<string, hash_map<string, string> > ipaddress_data;

	hash_map<string, unsigned long> interface_map;
	hash_map<string, unsigned long> ipaddress_map;

	hash_map<string, unsigned long> ifIndex;

	slist<unsigned long> read_discovery_indexes;
	
	network_id.s_addr = key;

	if (getExistingInterfaces(element[0], interface_map, key) != SUCCESS) {
		LMSG("ERROR: unable to retrieve existing interfaces for element.");

		result = FAILURE;
	}
	if (getExistingIPAddresses(element[1], ipaddress_map, key) != SUCCESS) {
		LMSG("ERROR: unable to retrieve existing IP addresses for element.");

		result = FAILURE;
	}
	if (element[0] != element[1]) {
		LMSG("ERROR: more than one network element found with a netid of (%d).", inet_ntop(AF_INET, &network_id, inet_buffer, 16));

		result = FAILURE;
	}
	if (result == SUCCESS) {
		if (getDiscoveryInformation(read_discovery_indexes, element_data, interface_data, ipaddress_data, key) != SUCCESS) {
			LMSG("ERROR: unable to retrieve discovery information.");

			result = FAILURE;
		} else {
			if (updateElementData(element[0], element_data) != SUCCESS) {
				LMSG("ERROR: unable to update element data.");

				result = FAILURE;
			}
			if (updateInterfaceData(element[0], interface_data, interface_map) != SUCCESS) {
				LMSG("ERROR: unable to update interface data.");

				result = FAILURE;
			}
			if (updateIPAddressData(element[0], ipaddress_data, ipaddress_map) != SUCCESS) {
				LMSG("ERROR: unable to update IP data.");

				result = FAILURE;
			}
			if (deleteDiscoveryInformation(read_discovery_indexes) != SUCCESS) {
				LMSG("WARNING: unable to delete discovery information.");
			}
		}
	}
	return result;
}

RESULT Topology::insertElement(const unsigned long key) {
	RESULT result = SUCCESS;

	unsigned long element;	

	slist<pair<string, string> > element_data;
	hash_map<string, hash_map<string, string> > interface_data;
	hash_map<string, hash_map<string, string> > ipaddress_data;

	hash_map<string, unsigned long> interface_map;
	hash_map<string, unsigned long> ipaddress_map;

	hash_map<string, unsigned long> ifIndex;

	slist<unsigned long> read_discovery_indexes;
	
	if (insertNewElement(element, key) != SUCCESS) {
		LMSG("ERROR: unable to create new element.");

		result = FAILURE;
	} else {
		if (getDiscoveryInformation(read_discovery_indexes, element_data, interface_data, ipaddress_data, key) != SUCCESS) {
			LMSG("ERROR: unable to retrieve discovery information.");

			result = FAILURE;
		} else {
			if (insertElementData(element, element_data) != SUCCESS) {
				LMSG("ERROR: unable to insert new element data.");

				result = FAILURE;
			}
			if (updateInterfaceData(element, interface_data, interface_map) != SUCCESS) {
				LMSG("ERROR: unable to insert new interface data.");

				result = FAILURE;
			}
			if (updateIPAddressData(element, ipaddress_data, ipaddress_map) != SUCCESS) {
				LMSG("ERROR: unable to insert new IP data.");

				result = FAILURE;
			}
			if (deleteDiscoveryInformation(read_discovery_indexes) != SUCCESS) {
				LMSG("WARNING: unable to delete discovery information.");
			}
		}	
	}
	return result;
}

RESULT Topology::processRemovedElements(list<unsigned long> &removed_elements) {
	RESULT result = SUCCESS;

	return result;
}

RESULT Topology::processExistingElements(list<unsigned long> &existing_elements) {
	RESULT result = SUCCESS;

	char inet_buf[16];

	for (list<unsigned long>::iterator it = existing_elements.begin(); !interrupt && it != existing_elements.end(); ++it) {
		if (TRACE_ISSET(TRACE_INTERNAL)) {
			struct in_addr a;
			a.s_addr = *it;
			TRACE(TRACE_INTERNAL, "Updating element: %s", inet_ntop(AF_INET, &a, inet_buf, sizeof(inet_buf)));
		}

		result = updateElement(*it);
	}
	
	return result;
}

RESULT Topology::getExistingInterfaces(unsigned long &element, hash_map<string, unsigned long> &interfaces, unsigned long key) {
	RESULT result = SUCCESS;

	char	inet_buffer[16];

	TRACE(TRACE_INTERNAL, " - Existing Interfaces:");

	string sql;
	char buf[132];
	StorageData reader;
	struct in_addr netid;

	for (set<unsigned long>::iterator s_it = new_discovery[key].begin(); s_it != new_discovery[key].end(); ++s_it) {
		netid.s_addr = *s_it;
		snprintf(buf, 132, "(element.netid = '%s') OR ", inet_ntop(AF_INET, &netid, inet_buffer, 16));
		sql += buf;
	}

	if (sql.empty()) {
		result = FAILURE;
	} else {
		sql.erase(sql.size() - 4);
		sql.insert(0, "SELECT element.id AS element, interface.id AS interface, interface_data.value AS value FROM element, interface, interface_data, ip WHERE (interface_data.interface = interface.id AND interface.id = ip.interface AND element.id=interface.element) AND interface_data.variable = '%s' AND (");
		sql.append(")");

		if (storage.queryf(reader, sql.c_str(), INTERFACES_IFTABLE_IFENTRY_IFDESCR) != SUCCESS) {
			LMSG("ERROR: unable to retrieve existing interface information.");

			result = FAILURE;
		} else {
			string interface_name;

			if (!reader.nextRow()) {
				/* No data. Clear. */
				element = 0;
				interfaces.clear();
			} else {
				/* Read available data. */
				reader.getULong(element, "element");

				do {
					reader.getString(interface_name, "value");
					reader.getULong(interfaces[interface_name], "interface");

					TRACE(TRACE_INTERNAL, "    - %s", interface_name.c_str());
				} while (reader.nextRow());
			}
		}
	}
	
	return result;
}

RESULT Topology::getExistingIPAddresses(unsigned long &element, hash_map<string, unsigned long> &ip_addresses, unsigned long key) {
	RESULT result = SUCCESS;

	TRACE(TRACE_INTERNAL, " - Existing IP Addresses:");
	
	string sql;
	char buf[132];
	char inet_buffer[16];
	StorageData reader;
	struct in_addr netid;

	for (set<unsigned long>::iterator s_it = new_discovery[key].begin(); s_it != new_discovery[key].end(); ++s_it) {
		netid.s_addr = *s_it;
		snprintf(buf, 132, "(element.netid = '%s') OR ", inet_ntop(AF_INET, &netid, inet_buffer, 16));
		sql += buf;
	}

	if (sql.empty()) {
		result = FAILURE;
	} else {
		sql.erase(sql.size() - 4);
		sql.insert(0, "SELECT element.id AS element, ip.id AS ip, ip_data.value AS value FROM element, interface, ip, ip_data WHERE (ip_data.ip = ip.id AND interface.id = ip.interface AND element.id = interface.element) AND ip_data.variable = '%s' AND (");
		sql.append(")");

		if (storage.queryf(reader, sql.c_str(), IP_IPADDRTABLE_IPADDRENTRY_IPADENTADDR) != SUCCESS) {
			LMSG("ERROR: unable to retrieve existing IP address information.");

			result = FAILURE;
		} else {
			string ip_address;
 
			if (!reader.nextRow()) {
				/* No data. Clear. */
				element = 0;
				ip_addresses.clear();
			} else {
				/* Read available data. */
				reader.getULong(element, "element");

				do {
					reader.getString(ip_address, "value");
					reader.getULong(ip_addresses[ip_address], "ip");

					TRACE(TRACE_INTERNAL, "    - %s [%lu]", ip_address.c_str(), ip_addresses[ip_address]);
				} while (reader.nextRow());
			}
		}
	}
	
	return result;
}

RESULT Topology::getDiscoveryInformation(slist<unsigned long> &discovery_id, slist<pair<string, string> > &element_data, hash_map<string, hash_map<string, string> > &interface_data, hash_map<string, hash_map<string, string> > &ipaddress_data, unsigned long key) {
	RESULT result = SUCCESS;

	StorageData reader;

	bool found;
	char buf[132];
	char inet_buffer[16];
	string str, sql, if_index;
	struct in_addr netid;

	slist<string> interfaceInformationOIDs;
	slist<string> ipaddressInformationOIDs;

	pair<string, string> element_information;

	for (set<unsigned long>::iterator s_it = new_discovery[key].begin(); s_it != new_discovery[key].end(); ++s_it) {
		netid.s_addr = *s_it;
		snprintf(buf, 132, "(ip = '%s') OR ", inet_ntop(AF_INET, &netid, inet_buffer, 16));
		sql += buf;
	}

	if (!sql.empty()) {
		sql.erase(sql.size() - 4);
		sql.insert(0, "SELECT id, variable, value FROM discovery_snmp WHERE ");

		if (storage.query(reader, sql.c_str()) != SUCCESS) {
			LMSG("ERROR: unable to retrieve SNMP discovery data.");

			result = FAILURE;
		} else {
			bool oid_match = false;
			unsigned long pivot;
			
			string comparison_oid;
			
			unsigned long data_id;
			string data_oid;
			string data_value;

			interfaceInformationOIDs.push_front(".1.3.6.1.2.1.2.2.1"); //Interface Names Mostly
			interfaceInformationOIDs.push_front(".1.3.6.1.2.1.31.1.1.1"); //Interface Descriptions

			ipaddressInformationOIDs.push_front(".1.3.6.1.2.1.4.20.1"); //IP Addresses attached to ifIndex (SNMP Bug related to this)

			while (reader.nextRow()) {
				reader.getULong(data_id, "id");
				reader.getString(data_oid, "variable");
				reader.getString(data_value, "value");

				oid_match = false;

				if (oid_match == false) {
					for (slist<string>::iterator it = interfaceInformationOIDs.begin(); it != interfaceInformationOIDs.end(); ++it) {
						comparison_oid = (*it) + ".";

						if (data_oid.size() >= comparison_oid.size()) {
							if (data_oid.substr(0, comparison_oid.size()).compare(comparison_oid) == 0) {
								for (pivot = comparison_oid.size(); data_oid.at(pivot) != '.'; pivot++) {
									comparison_oid.append(data_oid.substr(pivot, 1));
								}
								data_oid.erase(0, pivot + 1);
		
								if (interface_data.find(data_oid) == interface_data.end()) {
									interface_data[data_oid];
								}
								interface_data[data_oid][comparison_oid] = data_value;
		
								oid_match = true;
							}
						}
					}
				}
				if (oid_match == false) {
					for (slist<string>::iterator it = ipaddressInformationOIDs.begin(); it != ipaddressInformationOIDs.end(); ++it) {
						comparison_oid = (*it) + ".";
		
						if (data_oid.size() >= comparison_oid.size()) {
							if (data_oid.substr(0, comparison_oid.size()).compare(comparison_oid) == 0) {
								for (pivot = comparison_oid.size(); data_oid.at(pivot) != '.'; pivot++) {
									comparison_oid.append(data_oid.substr(pivot, 1));
								}
								data_oid.erase(0, pivot + 1);
		
								if (ipaddress_data.find(data_oid) == ipaddress_data.end()) {
									ipaddress_data[data_oid];
								}
								ipaddress_data[data_oid][comparison_oid] = data_value;
		
								oid_match = true;
							}
						}
					}
				}
				if (oid_match == false) {
					element_information.first = data_oid;
					element_information.second = data_value;

					element_data.push_front(element_information);
				}
				discovery_id.push_front(data_id);
			}
		}
	}

	/* Insert interface data if an IP address was availble (Cisco SNMP bug causes this) */
	/* Insert ipaddress data for physical and unconfigured logical interfaces (0.0.0.0/32) */
	
	/* Run thru ipaddress_data and insert interface data if its missing. */
	for (hash_map<string, hash_map<string, string> >::iterator ip_it = ipaddress_data.begin(); ip_it != ipaddress_data.end(); ++ip_it) {
		/* Locate the ifIndex */
		str = IP_IPADDRTABLE_IPADDRENTRY_IPADENTIFINDEX;
		if (ip_it->second.find(str) != ip_it->second.end()) {
			if_index = ip_it->second[str];
			if (interface_data.find(if_index) == interface_data.end()) { /* Insert generated interface name if its missing for this ifIndex */
				/* Insert ifIndex */
				interface_data[if_index];
				interface_data[if_index][".1.3.6.1.2.1.2.2.1.1"] = if_index;
				/* Insert Generated Interface Name */
				interface_data[if_index];
				interface_data[if_index][".1.3.6.1.2.1.2.2.1.2"] = "Unknown-";
				interface_data[if_index][".1.3.6.1.2.1.2.2.1.2"].append(if_index);
				/* Insert Empty Description */
				interface_data[if_index];
				interface_data[if_index][".1.3.6.1.2.1.31.1.1.1.18"] = "";
				/* Insert AdminStatus */
				interface_data[if_index];
				interface_data[if_index][".1.3.6.1.2.1.2.2.1.7"] = "2";
				/* Insert OperStatus */
				interface_data[if_index];
				interface_data[if_index][".1.3.6.1.2.1.2.2.1.8"] = "2";
			}
		}
	}

	/* Run thru interface_data and insert a bogus ip address in ipaddress_data, if its missing */
	for (hash_map<string, hash_map<string, string> >::iterator i_it = interface_data.begin(); i_it != interface_data.end(); ++i_it) {
		found = true;
		for (hash_map<string, hash_map<string, string> >::iterator ip_it = ipaddress_data.begin(); ip_it != ipaddress_data.end(); ++ip_it) {
			found = false;
			str = IP_IPADDRTABLE_IPADDRENTRY_IPADENTIFINDEX;
			if (ip_it->second.find(str) != ip_it->second.end()) {
				if_index = ip_it->second[str];
				if (!if_index.compare(i_it->first)) { /* If the ifIndex matches, it has an IP.. or at least an entry. */
					found = true;
					break;
				}
			}
		}

		/* Insert bogus/non IP for unconfigured, or physical interfaces. */
		/* NOTE: The hash requires uniq addresses within a device. Trailing space acts as a terminator for Netblock(). */
		if (!found) {
			str = "0.0.0.0 "; /* Trailing space is required. */
			str.append(i_it->first); /* str == "0.0.0.0 ifIndex" (ie: "0.0.0.0 204") */
			ipaddress_data[str];
			ipaddress_data[str][IP_IPADDRTABLE_IPADDRENTRY_IPADENTADDR] = str;
			ipaddress_data[str][IP_IPADDRTABLE_IPADDRENTRY_IPADENTIFINDEX] = i_it->first;
			ipaddress_data[str][IP_IPADDRTABLE_IPADDRENTRY_IPADENTNETMASK] = "255.255.255.255";
		}
	}

#if 0
	LMSG("Dump of interface_data");
	for (hash_map<string, hash_map<string, string> >::iterator it_x = interface_data.begin(); it_x != interface_data.end(); ++it_x) {
		LMSG("first: %s", it_x->first.c_str());
		for (hash_map<string, string>::iterator it_y = it_x->second.begin(); it_y != it_x->second.end(); ++it_y) {
			LMSG("- fst: %s", it_y->first.c_str());
			LMSG("- sec: %s", it_y->second.c_str());
		}
	}
	
	LMSG("Dump of ipaddress_data");
	for (hash_map<string, hash_map<string, string> >::iterator it_x = ipaddress_data.begin(); it_x != ipaddress_data.end(); ++it_x) {
		LMSG("first: %s", it_x->first.c_str());
		for (hash_map<string, string>::iterator it_y = it_x->second.begin(); it_y != it_x->second.end(); ++it_y) {
			LMSG("- fst: %s", it_y->first.c_str());
			LMSG("- sec: %s", it_y->second.c_str());
		}
	}
#endif
	
	return result;
}

RESULT Topology::deleteDiscoveryInformation(slist<unsigned long> &information_ids) {
	RESULT result = SUCCESS;

	string sql;
	char buf[132];
	StorageData eraser;
	unsigned int count = 0, slist_size = information_ids.size();

	for (slist<unsigned long>::iterator it = information_ids.begin(); it != information_ids.end(); ++it) {
		snprintf(buf, 132, "id = %lu OR ", *it);
		sql.append(buf);

		if (++count % maxEntryDelete == 0 || count >= slist_size) {
			sql.erase(sql.size() - 4);
			sql.insert(0, "DELETE FROM discovery_snmp WHERE ");

			if (storage.query(eraser, sql.c_str()) != SUCCESS) {
				LMSG("ERROR: unable to delete SNMP discovery data.");

				result = FAILURE;
			} else {
				sql.erase();
				eraser.clear();
			}
		}
	}

	return result;
}

RESULT Topology::updateElementData(unsigned long element, slist<pair<string, string> > &element_data) {
	RESULT result = SUCCESS;

	StorageData eraser;

	if (storage.queryf(eraser, "DELETE FROM element_data WHERE element = '%lu'", element) != SUCCESS) {
		LMSG("ERROR: unable to delete old element data.");

		result = FAILURE;
	} else {
		if (insertElementData(element, element_data) != SUCCESS) {
			LMSG("ERROR: unable to insert element data.");

			result = FAILURE;
		}
	}
	
	return result;
}

RESULT Topology::insertElementData(unsigned long element, slist<pair<string, string> > &element_data) {
	RESULT result = SUCCESS;

	StorageData writer;

	TRACE(TRACE_INTERNAL, " - Inserting Element Data:");

	/*
		Without the unique call, it is possible for multiple sets of element_data to be inserted into
		the database.  This occurs when the configured set of netblocks to scan for SNMP discovery contains more
		than a single IP address assigned to a device.

		This leads to database bloat as well as some asthetic bugs when viewing links from other devices to
		the device affected by this bug.
	*/
	element_data.sort();
	element_data.unique();

	string sql;
	char buf[132];

	for (slist<pair<string, string> >::iterator it = element_data.begin(); it != element_data.end(); ++it) {
		snprintf(buf, 132, "(%lu,", element);
		sql.append(buf + writer.quoteString(it->first) + "," + writer.quoteString(it->second) + "),");

		TRACE(TRACE_INTERNAL, "    - Adding: [%s] = %s", it->first.c_str(), it->second.c_str());
	}

	if (!sql.empty()) {
		sql.erase(sql.size() - 1);
		sql.insert(0, "INSERT INTO element_data (element, variable, value) VALUES ");

		if (storage.query(writer, sql.c_str()) != SUCCESS) {
			LMSG("ERROR: unable to write element data.");

			result = FAILURE;
		}
	}

	return result;
}

RESULT Topology::updateInterfaceData(unsigned long element, hash_map<string, hash_map<string, string> > &interface_data, hash_map<string, unsigned long> &interface_map) {
	RESULT result = SUCCESS;

	TRACE(TRACE_INTERNAL, " - Updating Interface Data:");

	string sql;
	char buf[132];
	StorageData eraser;

	pair<string, unsigned long> interface_id;
	slist<pair<string, unsigned long> > interface_list;

	for (hash_map<string, hash_map<string, string> >::iterator it = interface_data.begin(); it != interface_data.end(); ++it) {
		if (it->second.find(".1.3.6.1.2.1.2.2.1.2") != it->second.end()) {
			if (interface_map.find(it->second[".1.3.6.1.2.1.2.2.1.2"]) != interface_map.end()) {
				interface_id.first = it->first;
				interface_id.second = interface_map[it->second[".1.3.6.1.2.1.2.2.1.2"]];

				interface_list.push_front(interface_id);

				snprintf(buf, 132, "interface = %lu OR ", interface_id.second);
				sql.append(buf);

				TRACE(TRACE_INTERNAL, "    - Erasing old data: %s", it->second[".1.3.6.1.2.1.2.2.1.2"].c_str());
			} else {
				TRACE(TRACE_INTERNAL, "    - Inserting new interface: %s", it->second[".1.3.6.1.2.1.2.2.1.2"].c_str());

				if (insertNewInterface(element, interface_id.second) != SUCCESS) {
					LMSG("ERROR: unable to insert new interface.");

					result = FAILURE;
				} else {
					interface_id.first = it->first;

					interface_list.push_front(interface_id);
				}
			}
		}
	}

	if (storage.query(eraser, "LOCK TABLE interface_data WRITE") != SUCCESS) {
		LMSG("WARNING: Failed lock table interface_data for write.");

		result = FAILURE;
	} else {
		if (!sql.empty()) {
			sql.erase(sql.size() - 4);
			sql.insert(0, "DELETE FROM interface_data WHERE ");

			if (storage.query(eraser, sql.c_str()) != SUCCESS) {
				LMSG("ERROR: unable to delete old interface data.");

				result = FAILURE;
			}
		}
		if (result != FAILURE) {
			if (insertInterfaceData(interface_list, interface_data) != SUCCESS) {
				LMSG("ERROR: unable to insert interface data.");

				result = FAILURE;
			}
		}
		if (storage.query(eraser, "UNLOCK TABLES") != SUCCESS) {
			LMSG("WARNING: Failed unlock table interface_data.");

			result = FAILURE;
		}
	}

	return result;
}

RESULT Topology::insertNewElement(unsigned long &element, unsigned long key) {
	RESULT result = SUCCESS;

	StorageData writer;
	
	in_addr netid;
	netid.s_addr = key;

	char inet_buffer[16];

	if (storage.queryf(writer, "INSERT INTO element (netid) VALUES ('%s')", inet_ntop(AF_INET, &netid, inet_buffer, 16)) != SUCCESS) {
		LMSG("ERROR: unable to write new element.");

		result = FAILURE;
	} else {
		if ((element = writer.getLastInsertId("element")) == 0) {
			LMSG("ERROR: unable to get ID for new element.");

			result = FAILURE;
		}
	}
	return result;
}

RESULT Topology::insertNewInterface(unsigned long element, unsigned long &interface_id) {
	RESULT result = SUCCESS;

	StorageData writer;

	if (storage.queryf(writer, "INSERT INTO interface (element) VALUES (%lu)", element) != SUCCESS) {
		LMSG("ERROR: unable to write new interface.");

		result = FAILURE;
	} else {
		if ((interface_id = writer.getLastInsertId("interface")) == 0) {
			LMSG("ERROR: unable to get ID for new interface.");

			result = FAILURE;
		}
	}
	return result;
}

RESULT Topology::insertNewIP(unsigned long interface, unsigned long &ipaddress_id) {
	RESULT result = SUCCESS;

	StorageData writer;

	if (storage.queryf(writer, "INSERT INTO ip (interface) VALUES (%lu)", interface) != SUCCESS) {
		LMSG("ERROR: unable to write new IP.");

		result = FAILURE;
	} else {
		if ((ipaddress_id = writer.getLastInsertId("ip")) == 0) {
			LMSG("ERROR: unable to get ID for new IP.");

			result = FAILURE;
		}
	}
	return result;
}

RESULT Topology::insertInterfaceData(slist<pair<string, unsigned long> > &interface_list, hash_map<string, hash_map<string, string> > &interface_data) {
	RESULT result = SUCCESS;
	slist<pair<string, unsigned long> >::iterator it;
	hash_map<string, string>::iterator jt;
	StorageData writer;
	char buf[132];
	string sql, str;

	TRACE(TRACE_INTERNAL, " - Inserting Interface Data:");

	for (it = interface_list.begin(); it != interface_list.end(); ++it) {
		if (interface_data.find(it->first) != interface_data.end()) {
			/* Add data for interface it->first to writer */
			for (jt = interface_data[it->first].begin(); jt != interface_data[it->first].end(); ++jt) {
				snprintf(buf, 132, "(%lu,", it->second);
				str = jt->first;
				sql.append(buf + writer.quoteString(str) + "," + writer.quoteString(jt->second) + "),");

				TRACE(TRACE_INTERNAL, "    - Inserting: [%s] = %s", jt->first.c_str(), jt->second.c_str());
			}
		} else {
			LMSG("ERROR: missing interface information.");

			result = FAILURE;
		}
	}

	if (!sql.empty()) {
		sql.erase(sql.size() - 1);
		sql.insert(0, "INSERT INTO interface_data (interface, variable, value) VALUES ");

		if (storage.query(writer, sql.c_str()) != SUCCESS) {
			LMSG("ERROR: unable to commit interface data.");

			result = FAILURE;
		}
	}

	return result;
}

RESULT Topology::mapIfIndexToInterfaceId(unsigned long element, hash_map<string, unsigned long> &ifidx_map) {
	RESULT result = SUCCESS;

	TRACE(TRACE_INTERNAL, " - Mapping ifIndexes to Interface Ids:");
	
	StorageData reader;

	string if_index;

	//Match up all interfaces to specified element. Most elements have many interfaces.
	if (storage.queryf(reader, "SELECT interface_data.value AS value, interface_data.interface AS interface FROM interface, interface_data WHERE interface_data.interface = interface.id AND interface.element = '%lu' AND interface_data.variable = '%s'", element, ".1.3.6.1.2.1.2.2.1.1") != SUCCESS) {
		LMSG("ERROR: unable to read interface data.");

		result = FAILURE;
	} else {
		while (reader.nextRow()) {
			reader.getString(if_index, "value");
			reader.getULong(ifidx_map[if_index], "interface");

			TRACE(TRACE_INTERNAL, "    - Mapping: [%s] = %lu", if_index.c_str(), ifidx_map[if_index]);
		}
	}
	return result;
}

#if 0
RESULT Topology::getIpIdList(unsigned long element, unsigned long interface, slist<unsigned long> &ip_id_list) {
	RESULT result = SUCCESS;

#ifdef TRACE
	LMSG(" - Getting IP id list for interface %lu on element %lu:", interface, element);
#endif
	
	StorageData reader;

	unsigned long id;

	//Collect all IP's associated with the specified interface on a specific element. Most interfaces only have one IP.
	if (storage.queryf(reader, "SELECT ip_data.ip AS ip FROM interface, ip, ip_data WHERE ( ip.interface = interface.id AND ip_data.ip = ip.id) AND interface.element = %lu AND interface.id = %lu AND ip_data.variable = '%s'", element, interface, ".1.3.6.1.2.1.4.20.1.2") != SUCCESS) {
		LMSG("ERROR: unable to read ip data.");

		result = FAILURE;
	} else {
		while (reader.nextRow()) {
			reader.getULong(id, "ip");
			ip_id_list.push_front(id);

#ifdef TRACE
			LMSG("    - Recieved: %lu", id);
#endif
		}
	}
	return result;
}
#endif

RESULT Topology::updateIPAddressData(unsigned long element, hash_map<string, hash_map<string, string> > &ipaddress_data, hash_map<string, unsigned long> &ipaddress_map) {
	RESULT result = SUCCESS;

	StorageData reader, eraser;

	char buf[132];
	unsigned long interface_id;
	string sql, if_index;
	
	pair<string, unsigned long> ipaddress_dbid_pair;
	hash_map<string, unsigned long> ifindex_to_interface_id;
	slist<pair<string, unsigned long> > ipaddress_list;

	TRACE(TRACE_INTERNAL, " - Updating IP Address Data:");

	//Map ifIndex to Interface Ids
	mapIfIndexToInterfaceId(element, ifindex_to_interface_id);

	//Loop through existing ip entries, deleting their data.
	for (hash_map<string, unsigned long>::iterator it = ipaddress_map.begin(); it != ipaddress_map.end(); ++it) {
		snprintf(buf, 132, "ip = %lu OR ", it->second);
		sql.append(buf);

		TRACE(TRACE_INTERNAL, "    - Erasing old data for: %s [id: %lu]", it->first.c_str(), it->second);
	}

	//Loop through new data, adding any new ip entries.
	for (hash_map<string, hash_map<string, string> >::iterator it = ipaddress_data.begin(); it != ipaddress_data.end(); ++it) {
		//Skip entries without an ifIndex. They are missing data.
		if (it->second.find(IP_IPADDRTABLE_IPADDRENTRY_IPADENTIFINDEX) == it->second.end()) {
			continue;
		}
		if_index = it->second[IP_IPADDRTABLE_IPADDRENTRY_IPADENTIFINDEX];

		//Is this a new IP on this element? If so, add a new ip entry.
		if (ipaddress_map.find(it->first) == ipaddress_map.end()) {
			TRACE(TRACE_INTERNAL, "    - Inserting new IP for: %s", it->first.c_str());

			interface_id = ifindex_to_interface_id[if_index];
			if (insertNewIP(interface_id, ipaddress_dbid_pair.second) != SUCCESS) {
				LMSG("ERROR: unable to insert new interface.");

				continue;
			}
		} else {
			//This is an existing IP in the database.
			ipaddress_dbid_pair.second = ipaddress_map[it->first];
		}
		//Add to the ipaddress_list, for ip data insertion.
		ipaddress_dbid_pair.first = it->first;
		ipaddress_list.push_front(ipaddress_dbid_pair);
	}

	if (storage.query(eraser, "LOCK TABLE ip_data WRITE") != SUCCESS) {
		LMSG("WARNING: Failed lock table ip_data for write.");

		result = FAILURE;
	} else {
		if (!sql.empty()) {
			sql.erase(sql.size() - 4);
			sql.insert(0, "DELETE FROM ip_data WHERE ");

			if (storage.query(eraser, sql.c_str()) != SUCCESS) {
				LMSG("ERROR: unable to delete old interface IP data.");

				result = FAILURE;
			}
		}
		if (result != FAILURE) {
			if (insertIPAddressData(ipaddress_list, ipaddress_data) != SUCCESS) {
				LMSG("ERROR: unable to insert interface IP data.");

				result = FAILURE;
			}
		}
		if (storage.query(eraser, "UNLOCK TABLES") != SUCCESS) {
			LMSG("WARNING: Failed to unlock table ip_data.");

			result = FAILURE;
		}
	}

	removeUnusedIPs();

	return result;
}

RESULT Topology::insertIPAddressData(slist<pair<string, unsigned long> > &ipaddress_list, hash_map<string, hash_map<string, string> > &ipaddress_data) {
	RESULT result = SUCCESS;

	TRACE(TRACE_INTERNAL, " - Inserting IP Address Data:");

	string sql, str;
	char buf[132];
	StorageData writer;

	for (slist<pair<string, unsigned long> >::iterator it = ipaddress_list.begin(); it != ipaddress_list.end(); ++it) {
		if (ipaddress_data.find(it->first) != ipaddress_data.end()) {
			for (hash_map<string, string>::iterator jt = ipaddress_data[it->first].begin(); jt != ipaddress_data[it->first].end(); ++jt) {
				snprintf(buf, 132, "(%lu,", it->second);
				str = jt->first;
				sql.append(buf + writer.quoteString(str) + "," + writer.quoteString(jt->second) + "),");
				TRACE(TRACE_INTERNAL, "    - Inserting: [%s] = %s", jt->first.c_str(), jt->second.c_str());
			}
		} else {
			LMSG("ERROR: missing IP information.");

			result = FAILURE;
		}
	}

	if (!sql.empty()) {
		sql.erase(sql.size() - 1);
		sql.insert(0, "INSERT INTO ip_data (ip, variable, value) VALUES ");

		if (storage.query(writer, sql.c_str()) != SUCCESS) {
			LMSG("ERROR: unable to commit IP data.");

			result = FAILURE;
		}
	}

	return result;
}

RESULT Topology::loadNewDiscovery() {
	RESULT result = FAILURE;

	string str;
	StorageData data;
	struct in_addr source, interface, prev_source;

	if (storage.queryf(data, "SELECT ip, value FROM discovery_snmp WHERE variable LIKE '%s' ORDER BY ip", IP_IPADDRTABLE_IPADDRENTRY_IPADENTADDR_LIKE) != SUCCESS) {
		LMSG("Topology::loadNewDiscovery - Failed to read from discovery_snmp.");

		result = FAILURE;
	} else {
		TRACE(TRACE_INTERNAL, "New Discovery %d record count.", data.getNumRow());

		prev_source.s_addr = 0;
		while (data.nextRow()) {
			data.getString(str, "ip");
			if (!inet_pton(AF_INET, str.c_str(), &source)) {
				continue;
			}

			/* Insert source as an interface. Protects against packet loss, and broken SNMP implementations. */
			if (source.s_addr != prev_source.s_addr) {
				new_discovery[source.s_addr].insert(source.s_addr);
				prev_source.s_addr = source.s_addr;
			}

			data.getString(str, "value");
			if (!inet_pton(AF_INET, str.c_str(), &interface)) {
				continue;
			}

			/* Insert interface */
			new_discovery[source.s_addr].insert(interface.s_addr);
		}
	}

	if ((result = removeDuplicateElement(new_discovery)) != SUCCESS) {
		LMSG("Topology::removeDuplicateElement() - Failed");
	}

	return result;
}

RESULT Topology::removeDuplicateElement(layer3_hash_t &layer3_hash) {
	RESULT result = SUCCESS;
	set<unsigned long>::iterator set_it;
	layer3_hash_t::iterator l3_it_1, l3_it_2;
	set<unsigned long> result_set;

	/* FIXME: I believe merge doesn't work right if there are only 2 elements. You end up with 1 full set and 1 empty set. */

	/* Merge duplicates */
	for (l3_it_1 = layer3_hash.begin(); l3_it_1 != layer3_hash.end(); ++l3_it_1) {
		for (l3_it_2 = l3_it_1, ++l3_it_2; l3_it_2 != layer3_hash.end(); ++l3_it_2) {
			assert(l3_it_1 != l3_it_2);

			/* Two elements (routers) have the same interfaces, merge them. */
			if (compareSetsIP(l3_it_1->second, l3_it_2->second, duplicatePercent) == true) {
				set_union(l3_it_1->second.begin(), l3_it_1->second.end(), l3_it_2->second.begin(), l3_it_2->second.end(), inserter(result_set, result_set.begin()));

				l3_it_1->second = result_set; /* Replace original set with union of the two */
				l3_it_2->second.clear(); /* Clear the old set */
				result_set.clear(); /* This is required */
			}
		}
	}

	/* Erase empty sets */
	for (l3_it_1 = layer3_hash.begin(); l3_it_1 != layer3_hash.end(); ++l3_it_1) {
		if (l3_it_1->second.empty()) {
			layer3_hash.erase(l3_it_1);
			l3_it_1 = layer3_hash.begin();
		}
	}

	return result;
}

RESULT Topology::loadExistingDiscovery() {
	RESULT result = SUCCESS;

	struct in_addr ifid, netid, cur_netid;

	StorageData reader;

	string id, ipaddress;

	if (storage.queryf(reader, "SELECT element.netid AS netid, ip_data.value AS value FROM element, interface, ip, ip_data WHERE (element.id = interface.element AND interface.id = ip.interface AND ip.id = ip_data.ip) AND ip_data.variable = '%s' ORDER BY element.id", IP_IPADDRTABLE_IPADDRENTRY_IPADENTADDR) != SUCCESS) {
		LMSG("ERROR: unable to retrieve existing network layout.");
		
		result = FAILURE;
	} else {
		/* Initialize cur_netid. */
		if (inet_pton(AF_INET, "0.0.0.0", &cur_netid) == 0) {
			LMSG("DEBUG: Failed to initialize Topology::loadExistingDiscovery() cur_netid.");
		}

		TRACE(TRACE_INTERNAL, "Existing Discovery %d record count.", reader.getNumRow());

		while (reader.nextRow()) {
			reader.getString(id, "netid");
			reader.getString(ipaddress, "value");

			if (inet_pton(AF_INET, id.c_str(), &netid)) {
				if (inet_pton(AF_INET, ipaddress.c_str(), &ifid)) {
					if (cur_netid.s_addr != netid.s_addr) {
						cur_netid = netid;
					
						this->existing_discovery[cur_netid.s_addr];
					}
					this->existing_discovery[cur_netid.s_addr].insert(ifid.s_addr);
				}
			}
		}
	}
	
	return result;
}

RESULT Topology::removeOrphanLinkPoints() {
	RESULT result = SUCCESS;

	char buf[132];
	string sql;
	unsigned long id;
	StorageData reader, eraser;

	TRACE(TRACE_INTERNAL, " - Locating unused link_point entries:");

	//Locate orphan link_point entries. (ie: The ip entry it refered to has been removed.)
	if (storage.query(reader, "SELECT link_point.id AS id FROM link_point LEFT JOIN ip ON ( link_point.point = ip.id) WHERE ip.id IS NULL") != SUCCESS) {
		LMSG("ERROR: unable to read link_point/ip data.");

		result = FAILURE;
	} else {
		sql.erase();
		while (reader.nextRow()) {
			reader.getULong(id, "id");
			snprintf(buf, 132, "link_point.id = %lu OR ", id);
			sql.append(buf);

			TRACE(TRACE_INTERNAL, "    - Unused link point: %lu", id);
		}
		reader.clear();
	}

	if (!sql.empty()) {
		if (storage.query(eraser, "LOCK TABLE link_point WRITE") != SUCCESS) {
			result = FAILURE;

			LMSG("ERROR: Failed to lock table link_point for write.");
		} else {
			//Remove orphaned link_point entries.
			sql.erase(sql.size() - 4);
			sql.insert(0, "DELETE FROM link_point WHERE ");

			if (storage.query(eraser, sql.c_str()) != SUCCESS) {
				LMSG("ERROR: unable to delete old interface IP data.");

				result = FAILURE;
			}
			if (storage.query(eraser, "UNLOCK TABLES") != SUCCESS) {
				result = FAILURE;

				LMSG("ERROR: Failed to unlock table link_point");
			}
		}
	}

	TRACE(TRACE_INTERNAL, " - Locating links with only 1 link_point entrie:");

	//Locate link_point entries with only 1 point.
	if (storage.query(reader, "SELECT id FROM link_point GROUP BY link HAVING count(point) = 1") != SUCCESS) {
		LMSG("ERROR: unable to read link/link_point data.");

		result = FAILURE;
	} else {
		sql.erase();
		while (reader.nextRow()) {
			reader.getULong(id, "id");
			snprintf(buf, 132, "link_point.id = %lu OR ", id);
			sql.append(buf);

			TRACE(TRACE_INTERNAL, "    - Singlular link point: %lu", id);
		}
		reader.clear();
	}

	if (!sql.empty()) {
		if (storage.query(eraser, "LOCK TABLE link_point WRITE") != SUCCESS) {
			result = FAILURE;

			LMSG("ERROR: Failed to lock table link_point for write.");
		} else {
			//Remove orphaned link_point entries.
			sql.erase(sql.size() - 4);
			sql.insert(0, "DELETE FROM link_point WHERE ");

			if (storage.query(eraser, sql.c_str()) != SUCCESS) {
				LMSG("ERROR: unable to delete old interface IP data.");

				result = FAILURE;
			}
			if (storage.query(eraser, "UNLOCK TABLES") != SUCCESS) {
				result = FAILURE;

				LMSG("ERROR: Failed to unlock table link_point");
			}
		}
	}

	return result;
}

RESULT Topology::removeUnusedIPs() {
	RESULT result = SUCCESS;

	char buf[132];
	string sql;
	unsigned long id;
	StorageData reader, eraser;

	TRACE(TRACE_INTERNAL, " - Locating unused IP entries:");

	//Locate ip entries that have no data bound to them, for removal below.
	if (storage.query(reader, "SELECT ip.id AS id FROM ip LEFT JOIN ip_data ON (ip.id = ip_data.ip) WHERE ip_data.id IS NULL") != SUCCESS) {
		LMSG("ERROR: unable to read ip data.");

		result = FAILURE;
	} else {
		sql.erase();
		while (reader.nextRow()) {
			reader.getULong(id, "id");
			snprintf(buf, 132, "ip.id = %lu OR ", id);
			sql.append(buf);

			TRACE(TRACE_INTERNAL, "    - Unused Entry: %lu", id);
		}
		reader.clear();
	}

	if (!sql.empty()) {
		if (storage.query(eraser, "LOCK TABLE ip WRITE") != SUCCESS) {
			result = FAILURE;

			LMSG("ERROR: Failed to lock table ip for write.");
		} else {
			//Remove ip entries that have no data bound to them.
			sql.erase(sql.size() - 4);
			sql.insert(0, "DELETE FROM ip WHERE ");

			if (storage.query(eraser, sql.c_str()) != SUCCESS) {
				LMSG("ERROR: unable to delete old interface IP data.");

				result = FAILURE;
			}
			if (storage.query(eraser, "UNLOCK TABLES") != SUCCESS) {
				result = FAILURE;

				LMSG("ERROR: Failed to unlock table ip");
			}
		}
	}

	return result;
}

RESULT Topology::removeUnusedLinks() {
	RESULT result = SUCCESS;

	char buf[132];
	string sql;
	unsigned long id;
	StorageData reader, eraser;

	TRACE(TRACE_INTERNAL, " - Locating unused Link entries:");

	//Locate unused/unreferenced link entries. (ie: The link_point entry it was referenced by has been removed.)
	if (storage.query(reader, "SELECT link.id AS id FROM link LEFT JOIN link_point ON ( link.id = link_point.link) WHERE link_point.id IS NULL") != SUCCESS) {
		LMSG("ERROR: unable to read link/link_point data.");

		result = FAILURE;
	} else {
		sql.erase();
		while (reader.nextRow()) {
			reader.getULong(id, "id");
			snprintf(buf, 132, "link.id = %lu OR ", id);
			sql.append(buf);

			TRACE(TRACE_INTERNAL, "    - Unused Link: %lu", id);
		}
		reader.clear();
	}

	if (!sql.empty()) {
		if (storage.query(eraser, "LOCK TABLE link WRITE") != SUCCESS) {
			result = FAILURE;

			LMSG("ERROR: Failed to lock table link for write.");
		} else {
			//Remove link entries that have no link_points bound to them.
			sql.erase(sql.size() - 4);
			sql.insert(0, "DELETE FROM link WHERE ");

			if (storage.query(eraser, sql.c_str()) != SUCCESS) {
				LMSG("ERROR: unable to delete old interface IP data.");

				result = FAILURE;
			}
			if (storage.query(eraser, "UNLOCK TABLES") != SUCCESS) {
				result = FAILURE;

				LMSG("ERROR: Failed to unlock table link");
			}
		}
	}

	return result;
}

RESULT Topology::optimizeDatabase() {
	RESULT result = SUCCESS;
	string sql;
	StorageData data;

	TRACE(TRACE_DEBUG, "Optimizing the database.");

	sql = "OPTIMIZE TABLES element, interface, ip, element_data, interface_data, ip_data, element_attrib, interface_attrib, ip_attrib, link, link_point";
	if (storage.query(data, sql.c_str()) != SUCCESS) {
		LMSG("ERROR: Unable to optimize database.");

		result = FAILURE;
	}

	return result;
}


void Topology::printLayer3Hash(layer3_hash_t &layer3_hash, long n) {
	layer3_hash_t::iterator l3_it;
	set<unsigned long>::iterator set_it;
	struct in_addr addr;

	char inet_buffer[16];

	long i = 0;
	for (l3_it = layer3_hash.begin(); l3_it != layer3_hash.end(); ++l3_it) {
		if (i++ == n || n == -1) {
			addr.s_addr = l3_it->first;
			LMSG("Source %s:\n", inet_ntop(AF_INET, &addr, inet_buffer, 16));
			for (set_it = l3_it->second.begin(); set_it != l3_it->second.end(); ++set_it) {
				addr.s_addr = *set_it;
				LMSG(" %s ", inet_ntop(AF_INET, &addr, inet_buffer, 16));
			}
 			LMSG("\n\n");
		}
	}
}

void Topology::printLinkHash(link_hash_t &link_hash, long n) {
	link_hash_t::iterator it;
	slist<unsigned long>::iterator jt;

	long i = 0;
	for (it = link_hash.begin(); it != link_hash.end(); ++it) {
		if (i++ == n || n == -1) {
			printf("Hash: %lu - Link: %lu - Points Size: %u\n ", it->first, it->second.link, it->second.points.size());
			for (jt = it->second.points.begin(); jt != it->second.points.end(); ++jt) {
				printf("%lu -> ", *jt);
			}
			printf("end\n"); fflush(0);
		}
	}
}

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