/* Cyclops
 * FER - SPVP
 * Fakultet elektrotehnike i racunarstva (http://www.fer.hr/)
 * Unska 3, 10000 Zagreb, Hrvatska
 * (c) 2001 FER, Zagreb.
 */


package hr.fer.zesoi.cyclops;

import java.util.*;


/**
 * Routing table. Based on somewhat simplified RIP protocol.
 * @author Tomislav Petkoviæ 
 */
public class RIPTable
{

    /** RIP table. */
    Vector table = new Vector();

    /** Owner node. */
    Node owner_node;

    /**
     * Default constructor.
     * @param owner Reference of owner node.
     */
    public RIPTable (Node owner)
    {
	this.owner_node = owner;
    }
    /* RIPTable */

    /**
     * Adds element to the table.
     * @param destination_node Reference of destination node or network.
     * @param next_node Reference of next node along a path to destination.
     * @param cost Cost of a route.
     */
    public void addElement (Node destination, Node next_node, float cost)
    {
	RIPTableElement new_element = new RIPTableElement(destination, next_node, cost);
	table.addElement(new_element);
    }
    /* addElement */

    /**
     * Finds next node along a path to destination.
     * @param destination_node Reference of destination node or network.
     * @return Returns null if no apropriate route is found and next node
     *         along a path is appropriate route is found.
     */
    public Node findNextNodeForRouteTo (Node destination_node)
    {
	int i;
	RIPTableElement element;
	Node next_node;

	next_node = null;
	for (i=0; i<table.size(); i++)
	    {
		element = (RIPTableElement)table.elementAt(i);
		/* Route is valid only if cost is less than infinity (16). */
		if (destination_node == element.destination_node)
		    {
			next_node = element.next_node;
			if (element.cost < 16)
			    {
				return next_node;
			    }
		    }
		/* if */
	    }
	/* for */
	return next_node;
    }
    /* findNextNodeForRouteTo */

    /**
     * Updates the table. Instead of sending updates as
     * packets through the network, every node will update
     * table directly (for simplicity).
     * @param node Node from whom to take the RIP table.
     */
    public void updateTable (Node node)
    {
	int i;
	int j;
	RIPTable another_table = node.getRIPTable();
	RIPTableElement old_element;
	RIPTableElement new_element;

	/* Metric update for node. */
	j = findEntry(node);
	if (j >= 0)
	    {
		old_element = (RIPTableElement)table.elementAt(j);
		old_element.cost = 1;
		old_element.next_node = node;
		old_element.route_change_flag = true;
		old_element.route_timeout = 5;
		old_element.garbage_timeout = 5;
	    }
	/* Metric update. */
	for (i=0; i<another_table.size(); i++)
	    {
		new_element = another_table.elementAt(i);
		j = findEntry(new_element.destination_node);
		if ((j >= 0) &&
		    (new_element.destination_node != owner_node)
		    )
		    {
			old_element = (RIPTableElement)table.elementAt(j);
			if (new_element.next_node == owner_node)
			    {
				/* Split horizon with poisoned reverse. Cost is 16. */
				if (old_element.cost >= 16)
				    {
					/* Better route exists. */
					old_element.cost = 16;
					old_element.next_node = node;
					old_element.route_change_flag = true;
					old_element.route_timeout = 5;
					old_element.garbage_timeout = 5;
				    }
				else if (old_element.next_node == node)
				    {
					/* Update is required. */
					old_element.cost = 16;
					old_element.route_change_flag = true;
					old_element.route_timeout = 5;
					old_element.garbage_timeout = 5;
				    }
				else
				    {
					old_element.route_change_flag = false;
				    }

			    }
			else
			    {
				/* Normal update. */
				if (old_element.cost >= new_element.cost + 1)
				    {
					/* Better route exists. */
					old_element.cost = new_element.cost + 1;
					old_element.next_node = node;
					old_element.route_change_flag = true;
					old_element.route_timeout = 5;
					old_element.garbage_timeout = 5;
				    }
				else if (old_element.next_node == node)
				    {
					/* Update is required. */
					old_element.cost = new_element.cost + 1;
					old_element.route_change_flag = true;
					old_element.route_timeout = 5;
					old_element.garbage_timeout = 5;
				    }
				else
				    {
					old_element.route_change_flag = false;
				    }
			    }
			/* else */
		    }
		else if(new_element.destination_node != owner_node)
		    {
			/* Create new table element. */
			addElement(new_element.destination_node, node,
				   new_element.cost + 1
				   );
		    }
		/* else */
	    }
	/* for */ /* Process all elements. */
    }
    /* updateTable */

    /**
     * Updates the table.
     * @param another_table Received RIP table.
     */
    public void updateTable (RIPTable another_table)
    {
	int i;
	int j;
	RIPTableElement old_element;
	RIPTableElement new_element;

	for (i=0; i<another_table.size(); i++)
	    {
		new_element = another_table.elementAt(i);
		j = findEntry(new_element.destination_node);
		if (j != -1)
		    {
			old_element = (RIPTableElement)table.elementAt(j);
			if (old_element.cost > new_element.cost + 1)
			    {
				/* Better route exists. */
				old_element.cost = new_element.cost + 1;
				old_element.next_node = new_element.next_node;
				old_element.route_change_flag = true;
				old_element.route_timeout = 5;
				old_element.garbage_timeout = 5;
			    }
			else if (old_element.next_node == new_element.next_node)
			    {
				/* Update if required. */
				old_element.cost = new_element.cost + 1;
				old_element.route_change_flag = true;
				old_element.route_timeout = 5;
				old_element.garbage_timeout = 5;
			    }
			else
			    {
				old_element.route_change_flag = false;
			    }
		    }
	    
		else
		    {
			/* Create new table element. */
			addElement(new_element.destination_node, new_element.next_node,
				   new_element.cost + 1
				   );
		    }
		/* else */
	    }
	/* for */ /* Process all elements. */
    }
    /* updateTable */

    /**
     * Decreases all timers and removes expired entries.
     */
    public void garbageCollect ()
    {
	int i;
	RIPTableElement element;

	for (i=0; i<table.size(); i++)
	    {
		element = (RIPTableElement)table.elementAt(i);
		if (!element.route_change_flag)
		    {
			if (element.route_timeout>0)
			    {
				element.route_timeout -= 1;
			    }
			else if (element.garbage_timeout>0)
			    {
				element.cost = 16;
				element.garbage_timeout -= 1;
			    }
			else
			    {
				/* Entry expired, will be terminated. */
				table.removeElementAt(i);
				i--;
			    }
		    }
		/* if */
	    }
	/* for */
    }
    /* garbageCollect */

    /**
     * Sets destination cost to infinity.
     * Used when line or node is erased/disconnected.
     * @param node Unreachable node. Used to find table entry.
     */
    public void setCostToInfinity (Node node)
    {
	int i;
	RIPTableElement element;

	for (i=0; i<table.size(); i++)
	    {
		element = (RIPTableElement)table.elementAt(i);
		if ((element.next_node == node) ||
		    (element.destination_node == node)
		    )
		    {
			element.cost = 16;
			element.route_change_flag = true;
			element.route_timeout = 5;
			element.garbage_timeout = 5;
		    }
		/* if */
	    }
	/* for */
    }
    /* setCostToInfinity */

    /**
     * Sets cost to given value.
     * @param destination Destination node. Used to find table entry.
     * @param cost Path cost.
     */
    public void setCost (Node destination, float cost)
    {
	int i;
	RIPTableElement element;

	for (i=0; i<table.size(); i++)
	    {
		element = (RIPTableElement)table.elementAt(i);
		if (element.destination_node == destination)
		    {
			element.cost = cost;
			element.route_change_flag = true;
			element.route_timeout = 5;
			element.garbage_timeout = 5;
		    }
		/* if */
	    }
	/* for */
    }
    /* setCost */

    /**
     * Gets element at index i.
     * @param index Index of element.
     * @return Returns element reference.
     */
    public RIPTableElement elementAt (int index)
    {
	return (RIPTableElement)table.elementAt(index);
    }
    /* elementAt */

    /**
     * Finds table entry with given destination node or network.
     * @param destination_node Reference of destination node or network.
     * @return Returns index of table entry if one exists, -1 otherwise.
     */
    public int findEntry (Node destination_node)
    {
	int i;
	RIPTableElement element;

	for (i=0; i<table.size(); i++)
	    {
		element = (RIPTableElement)table.elementAt(i);
		if (element.destination_node == destination_node)
		    {
			return i;
		    }
	    }
	return -1;
    }
    /* findEntry */

    /**
     * Gets table size.
     * @return Returns number of elements in a table.
     */
    public int size ()
    {
	return table.size();
    }
    /* size */

}
/* RIPTable */
