
package network;

import eventbase.*;
import eventbase.event.*;
import eventbase.link.*;
import eventbase.routing.*;

import java.lang.*;
import java.net.*;
import java.util.*;


public class PacketServiceThread extends Thread {

    final int JOIN = 0;
    final int DEAD = 1;
    final int ADD_NBR_RESPONSE = 2;
    final int DELAY = 3;
    final int INTENT_TO_CANCEL = 4;
    final int CANCEL_NEIGHBOR = 5;
    final int VRTENTRY = 6;
    final int SPANNINGTREE = 7;

    final int VRTENTRY_SIZE = 13;

    // TODO:  can we put this value somewhere better than here?
    public static final double UPDATE_INTERVAL = 35000;
    DatagramPacket packet = null;
    EventBaseAgent dbAgent = null;
    TransmissionTable table = null;
    BufferQueue sendBuffer = null;
    Hashtable nodesTable = null;
    SpanningTreeQueue sQueue = null;
    VRTUpdateManager vrtUpdateManager = null;
    NetworkStatDataModel dataModel = null;
    int nodeIndex;


    public PacketServiceThread(int index, DatagramPacket p, EventBaseAgent db, SpanningTreeQueue q,
                               VRTUpdateManager v, TransmissionTable t, BufferQueue buffer, Hashtable tb, 
			       NetworkStatDataModel netData) {
        nodeIndex = index;
        packet = p;
        dbAgent = db;
        sQueue = q;
        table = t;
        sendBuffer = buffer;
        nodesTable = tb;
        vrtUpdateManager = v;
	dataModel = netData;
	
    }

    private DatagramPacket createAckPacket(int recvSeq) {
        byte[] buf = new byte[2];
        buf[0] = (new Integer(((NetworkManager.VERSION & 0xF) << 4) |
                              (NetworkManager.ACK & 0xF))).byteValue();
        buf[1] = (new Integer(recvSeq)).byteValue();
        DatagramPacket pac = new DatagramPacket(buf, buf.length,
                                                packet.getAddress(), packet.getPort());
	dataModel.editSendRecord(nodeIndex, buf.length);
        return pac;
    }
    
    private DatagramPacket createInitPacket() {
        byte[] buf = null;
        buf = new byte[2];
	
        buf[0] = (new Integer(((NetworkManager.VERSION & 0xF) << 4) |
                              (NetworkManager.INIT & 0xF))).byteValue();

        DatagramPacket pac = new DatagramPacket(buf, buf.length,
                                                packet.getAddress(), packet.getPort());
	dataModel.editSendRecord(nodeIndex, buf.length);
        return pac;
    }
    
    private double bytesToDouble(byte[] buf, int startPos) {
        long value = 0;
        for (int i = 0; i < 8; i++)
           value = ((((long)(buf[i + startPos])) & 0xFF) << (56 - i * 8)) | value;

        return Double.longBitsToDouble(value);
    }

    private int bytesToInteger(byte[] buf, int startPos) {
        int value = 0;
        for (int i = 0; i < 4; i++)
            value = ((((int)(buf[i + startPos])) & 0xFF) << (24 - i * 8)) | value;
        return value;
    }

    private byte[] integerToBytes(int value) {
        byte[] buf = new byte[4];
        for (int i = 0; i < 4; i++)
            buf[i] = (new Integer((value >>> (24 - i * 8)) & 0xFF)).byteValue();
	
        return buf;
    }

    private void handleDataPacket() {
        byte[] data = packet.getData();

        int dataType = (int)(data[2]);

        switch (dataType) {
            case JOIN:
                {
                    double time = bytesToDouble(data, 3);
                    dbAgent.setLatestEventTime(time);

                    InetAddress curhost = ((InetObject)nodesTable.get(
                                                new Integer(bytesToInteger(data, 11)))).address;
                    System.out.println("JOIN: " + time + " " + curhost.getHostName());


                    if (dbAgent != null){
                        OverlayHost host =
                            new OverlayHost(dbAgent.getHostID(curhost.getHostName().toLowerCase()),
                                            curhost.getHostName().toLowerCase());

                        JoinGroupEvent join_event = new JoinGroupEvent(host);
                        dbAgent.InsertEventIntoTables(time, join_event);
                    }
                }
                break;
            case DEAD:
            case ADD_NBR_RESPONSE:
            case DELAY:
            case INTENT_TO_CANCEL:
            case CANCEL_NEIGHBOR:
                {
                    double time = bytesToDouble(data, 3);
                    dbAgent.setLatestEventTime(time);
                    InetAddress curhost = ((InetObject)nodesTable.get(
                                                new Integer(bytesToInteger(data, 11)))).address;
                    InetAddress targethost = ((InetObject)nodesTable.get(
						new Integer(bytesToInteger(data, 15)))).address;

                    System.out.println("Type: " + dataType + " Packet from: " + packet.getAddress().getHostName());
		    
                    int delay = -1;
                    if (dataType == DELAY)
                        delay = bytesToInteger(data, 19);
		    
                    System.out.println("Type: " + dataType + " " + time + " " +
				       curhost.getHostName() + " " + targethost.getHostName() + " " + delay);
		    
                     if (dbAgent != null) {
			 OverlayHost host =
			     new OverlayHost(dbAgent.getHostID(curhost.getHostName().toLowerCase()),
					     curhost.getHostName().toLowerCase());
			 OverlayHost targetNode =
			     new OverlayHost (dbAgent.getHostID(targethost.getHostName().toLowerCase()),
					      targethost.getHostName().toLowerCase());
			 
			 OverlayEvent event = null;
			 switch (dataType) {
			    case DEAD:
                                event = new DetectDeadHostEvent(host, targetNode);
                                break;
                            case ADD_NBR_RESPONSE:
                                event = new AddLinkEvent(host, targetNode);
                                break;
                            case DELAY:
                                dbAgent.InsertLinkInfoIntoTable(time, host.getID(),
                                                                targetNode.getID(),
                                                                LinkInfoType.DELAY,
                                                                delay);
                                break;
                            case INTENT_TO_CANCEL:
                                event = new IntentToLeaveGroupEvent(targetNode);
                                break;
                            case CANCEL_NEIGHBOR:
                                event = new DropLinkEvent(host, targetNode);
                                break;
                        }
                        if (event != null)
                            dbAgent.InsertEventIntoTables(time, event);


                    }
                }
                break;
            case VRTENTRY:
                {
                    double time = bytesToDouble(data, 3);
                    dbAgent.setLatestEventTime(time);
                    InetAddress curhost = ((InetObject)nodesTable.get(
                                                new Integer(bytesToInteger(data, 11)))).address;
                    int size = (int)data[15];
                    // size of vrt table
                    int vrt = (int)data[16];
		    //                    System.out.println("VRT Count: " + size);
                    //System.out.println("VRT Size: " + vrt);
                    // TODO: keep old copy of table some where
                    //       update table when new entry comes
                    //       dump whole table when needed
                    for (int i = 0; i < size; i++) {
                        int startPos = 17 + i * VRTENTRY_SIZE;
                        String dest = (((InetObject)nodesTable.get(new Integer(bytesToInteger(data, startPos))))).address.getHostName();
                        int nextHopVal = bytesToInteger(data, startPos + 4);
                        String nextHop;
                        if (nextHopVal != -1)
                            nextHop = (((InetObject)nodesTable.get(new Integer(nextHopVal)))).address.getHostName();
                        else
                            nextHop = new String("255.255.255.255");

                        int cost = bytesToInteger(data, startPos + 8);
                        boolean deadFlag = false;
                        if ((int)data[startPos + 12] != 0)
                            deadFlag = true;
			// System.out.println("$$$$$$ network VRT curhost:" +  curhost.getHostName()  + " dest:" + dest + " nextHop:" + nextHop + " cost:" + cost + " " + deadFlag);
			

                        // insert VRT info to VRT table
                        // TODO -- check whether neighbor is equal to 255.255.255.255 which means
                        // no route, just replace with -1 instead of going thru dbAgent.
                        // need to change that with the version that has the bug fix
                        if (dbAgent != null) {

                            int nextHopID = -1;

                            if (!(nextHop.toLowerCase().equals("255.255.255.255") ||
                                nextHop.toLowerCase().equals("-1.255.255.255")))
                                  nextHopID = dbAgent.getHostID(nextHop.toLowerCase());
                            vrtUpdateManager.updateVRTEntry(
                              dbAgent.getHostID(curhost.getHostName().toLowerCase()),
                              dbAgent.getHostID(dest.toLowerCase()),
                              nextHopID, cost, (deadFlag)?1:0);
                        }
                    }
                }
                break;
            case SPANNINGTREE:
                {
                    double time = bytesToDouble(data, 3);
                    dbAgent.setLatestEventTime(time);
                    InetAddress curhost = ((InetObject)nodesTable.get(
                                                new Integer(bytesToInteger(data, 11)))).address;
                    // num entry actually sent in packet
                    int size = (int)data[15];
                    // size of vrt table
                    int vrt = (int)data[16];
		    //                    System.out.println("SpanningTree Count: " + size);
                    //System.out.println("SpanningTree Size: " + vrt);
                    int startPos = 17;

		    //                    System.out.println("******** in PacketServiceTherad: curHost :" + curhost.getHostName());
		    String forwardingHost = curhost.getHostName();
		    int numEntries = (vrt > dbAgent.getNumOfOverlayHosts())?vrt:dbAgent.getNumOfOverlayHosts();
		    SpanningTreeStoringObject obj = new SpanningTreeStoringObject(time, numEntries, forwardingHost);
                    for (int i = 0; i < size; i++) {
                        String source = (((InetObject)nodesTable.get(new Integer(bytesToInteger(data, startPos))))).address.getHostName();
                        int numChildren = (int)data[startPos + 4];

                        String[] children = new String[numChildren];
			int[] childIDList = new int[numChildren];

                        int childStartPos = startPos + 5;
                        for (int j = 0; j < numChildren; j++) {
                            children[j] = (((InetObject)nodesTable.get(new Integer(bytesToInteger(data, childStartPos + j * 4))))).
				address.getHostName();
			    childIDList[j] = dbAgent.getHostID(children[j].toLowerCase());
			}
                        startPos += 5 + numChildren * 4;


                        // Print out for debug
			/*
			System.out.println("ForwardSource: " + source);
                        System.out.print("NumChildren: " + numChildren);
                        for (int k = 0; k < numChildren; k++) {
			    System.out.print(children[k] + " " + childIDList[k]);
			}
			System.out.println();
			*/
			SpanningTreeStoringEntries sEntry = new SpanningTreeStoringEntries(source, numChildren, children);
			obj.insertEntry(sEntry);
			// VRT Table - add ChildList
			vrtUpdateManager.addChildEntries(dbAgent.getHostID(forwardingHost.toLowerCase()), 
							 dbAgent.getHostID(source.toLowerCase()), 
							 numChildren, children, childIDList);
			
		    }
		    // dbAgent.printMappingTable();
		    // vrtUpdateManager.print();
                    vrtUpdateManager.dumpToDB(dbAgent,
					      dbAgent.getHostID(curhost.getHostName().toLowerCase()),
					      time);
		    if (size > 0) {
			sQueue.insertPacket(obj);
			//			sQueue.print();
		    }
                }
                break;
        }

    }

    public void run() {
        // TODO: Decode the packet and perform database records insertion
        // Constraint: ALIVE interval > timeout

        byte[] data = packet.getData();
        int type = (int)(data[0] & 0xF);

	dataModel.editRecvRecord(nodeIndex, packet.getLength());
        switch (type) {
            case NetworkManager.INIT:
                {
                    sendBuffer.enqueue(createInitPacket());
                }
                break;
            case NetworkManager.ACK:
                {
                    int in_seq = (int)(data[1]);
                        // current seqNum in transmission
                    int expect_seq = table.getSeqNum(nodeIndex) - 1;

                    //System.out.println("Ack received: " + in_seq);
                    //System.out.println("   waiting: " + expect_seq);

                    if (in_seq == expect_seq) {
                        table.setTimerValue(nodeIndex, TransmissionTable.ACK_RECEIVED);
                        table.resetRetransmitCount(nodeIndex);
                    } // else do nothing
                }
                break;
            case NetworkManager.DATA:
                {
                    int in_seq = (int)(data[1]);
                    int expect_seq = table.getRecvSeqNum(nodeIndex);

                    //System.out.println("Data received: " + in_seq);
                    //System.out.println("  expecting: " + expect_seq);
		    handleDataPacket();
                    if (in_seq == expect_seq) {
                        // Send back ack
                        sendBuffer.enqueue(createAckPacket(in_seq));
                        table.setRecvSeqNum(nodeIndex, (in_seq + 1) % TransmissionTable.MAX_SEQ);

                        // Parse in data for EventBaseAgent
                    }
                    else if ((in_seq + 1) % TransmissionTable.MAX_SEQ == expect_seq) {
                        // Old msg, possible losing ack
                        sendBuffer.enqueue(createAckPacket(in_seq));
                    }
                    else { // Network Disconnection
                        // TODO: Call up to eventbase? - tungfai

                        // Set the expecting seq num to current one
                        table.setRecvSeqNum(nodeIndex, (in_seq + 1) % TransmissionTable.MAX_SEQ);
                        sendBuffer.enqueue(createAckPacket(in_seq));

                    }
                    //handleDataPacket();
                }
                break;
	case NetworkManager.TERMINATE:
	    {
		int hashcode = packet.getAddress().hashCode();
		InetObject obj = (InetObject)nodesTable.get(new Integer(hashcode));
		obj.simulationAlive = false;

	    }
	    break;
        }

	// disable explicit gc call -- yhchu
	// the profiler says it's spending more than 50% of cycle gc
	// System.gc();
    }

}
