/*
 * ShortestGeographicalPathRouting.java
 *
 * Created on April 15, 2008, 11:43 AM
 *
 * To change this template, choose Tools | Template Manager
 * and open the template in the editor.
 */

package sidnet.stack.routing;

import java.util.LinkedList;
import jist.runtime.JistAPI;
import jist.swans.Constants;
import sidnet.core.interfaces.AppInterface;
import jist.swans.mac.MacAddress;
import jist.swans.misc.Message;
import jist.swans.misc.MessageAnno;
import jist.swans.net.NetAddress;
import jist.swans.net.NetInterface;
import jist.swans.net.NetMessage;
import sidnet.jist.swans.RouteInterface;
import sidnet.colorprofiles.ColorProfileAggregate;
import sidnet.colorprofiles.ColorProfileGeneric;
import sidnet.messages.MessageDataP2P;
import sidnet.messages.NZMessage;
import sidnet.core.misc.Location2D;
import sidnet.core.misc.Node;
import sidnet.core.misc.NodeEntry;
import sidnet.core.misc.Query;
import sidnet.core.misc.Region;

/**
 *
 * @author Oliviu C. Ghica, Northwestern University
 */
public class ShortestGeographicalPathRouting implements RouteInterface
{
    public static final byte ERROR = -1;
    public static final byte SUCCESS = 0;

    private final Node myNode;          // The SIDnet handle to the node representation 
    
    // entity hookup (network stack)
    /** Network entity. */
    private NetInterface netEntity;
   
    /** Self-referencing proxy entity. */
    private RouteInterface self; 
    
    /** The proxy-entity for this application interface */
    private AppInterface appInterface;
    
    /** A placeholder for the query information, when submitted by the user through the terminal */
    private Query query;
    
    /** Creates a new instance of ShortestGeographicalPathRouting
     *
     * @param Node    the SIDnet node handle to access its GUI-primitives and shared environment
     */
    public ShortestGeographicalPathRouting(Node myNode)
    {
        this.myNode = myNode;
        
        /** Create a proxy for the application layer of this node */
        self = (RouteInterface)JistAPI.proxy(this, RouteInterface.class);
    }
    
    /** SWANS legacy. We no longer enable this */
    public void peek(NetMessage msg, MacAddress lastHopMac, MessageAnno anno) {
       // no peeking
    }
    
    /** 
     *  Receive a message from the network layer
     *  This method is typically called when this node is the ultimate destination of an incoming data-message (the sink) 
     * 
     * @param Message   the incomming message
     * @param NetAddress the original source of the message
     * @param MacAddress the MAC address 1-hop neighbor from which this nodes received this message
     * @param macId     the macId interface through which this message was received
     * @param NetAddress the IP address of the ultimate node destination (this)
     * @param priority  the priority of the incoming message
     * @param ttl    Time To Leave
     */
    public void receive(Message msg, NetAddress src, MacAddress lastHop, byte macId, NetAddress dst, byte priority, byte ttl, MessageAnno anno) 
    {  
        /* Provide a basic visual feedback on the fact that this node has received a message */
        myNode.getNodeGUI().colorCode.mark(ColorProfileAggregate.RECEIVE, 200);  
        myNode.getNodeGUI().repaint();
        
        /* 
         * A message may come in a format that you define based on your implementation needs
         * You must extract that format and act upon
         */
         if (msg instanceof NZMessage)
         {
            NZMessage msgNZ = (NZMessage)msg;
            sendToAppLayer(msgNZ.getPayload(), null);
         }
    }
    
     /**
      * Send a message
      * This method is being called when a message, comming from either the application layer or the mac layer, needs to be forwarded
      *
      * @param  NetMessage  the 'NetMessage' wrapped Message
      */
     public void send(NetMessage msg, MessageAnno anno) 
     {                     
        NetAddress nextHopIP;
        
        /* Provide a visual indication that this particular node has received a message that needs to be forwarded */
        myNode.getNodeGUI().colorCode.mark(ColorProfileGeneric.RECEIVE, 500);
      
        /* If this message comes from App Layer */
        if (((NetMessage.Ip)msg).getPayload() instanceof MessageDataP2P ) // We know the type of message format the application layer produces
        {
            MessageDataP2P msgP2P = (MessageDataP2P)((NetMessage.Ip)msg).getPayload();            
              
            /* Is this a query initialization message ? If so, it will contain Query information */
            if (msgP2P.query != null)
            {
                myNode.getNodeGUI().colorCode.mark(ColorProfileAggregate.RECEIVE, 2000);
                myNode.getNodeGUI().repaint();
                
                this.query = msgP2P.query;
                                
                /* Find the closest vertex. A query may contain a region of interest. Since this is P2P routing, we look for the closest vertex of that region */
                Region region = msgP2P.query.getRegion();
                region.resetIterator();
                
                Location2D nextLoc = region.getNext();
                Location2D closestVertex = nextLoc;
                
                double distMin = nextLoc.distanceTo(myNode.getNodeGUI().getPanelLocation2D());
                
                while (region.hasNext())
                {
                    nextLoc = region.getNext();
                    if (nextLoc.distanceTo(myNode.getNodeGUI().getPanelLocation2D()) < distMin)
                    {
                        distMin = nextLoc.distanceTo(myNode.getNodeGUI().getPanelLocation2D());
                        closestVertex = nextLoc;
                    }
                }
                
                /* Retrieve the IP address of the 1-hop neighbor closest to the area of interest */
                nextHopIP = getThroughShortestPath(closestVertex);
     
                /* If there is no node closer to the area of interest than this node, then this node will be the one to satisfiy the query */
                if (nextHopIP.toString().compareTo(myNode.getIP().toString()) == 0)
                     sendToAppLayer(msgP2P, null);
                 else /* keep forwarding the query */
                     sendToLinkLayer((NetMessage.Ip)msg, nextHopIP);
  
                 return;
            }
            else /* it is a (first) data message, which means, this node is the source of data-messages. It needs to
                    convert from the app-layer message format to the routing-layer message format 
                  */
            {
                // Find the next-forwarding node
                NZMessage msgNZ = new NZMessage(msgP2P.queryId, msgP2P.sequenceNumber, JistAPI.getTime(), msgP2P);
                msgNZ.setSourceLocation(query.getSinkNCSLocation2D().fromNCS(myNode.getNodeGUI().getLocationContext())); /* Will use this placeholder to actually put the location of the sink */
                NetMessage.Ip copyOfMsg = new NetMessage.Ip(msgNZ, /* The payload here is changed, however */ 
                                                          ((NetMessage.Ip)msg).getSrc(),
                                                          ((NetMessage.Ip)msg).getDst(),
                                                          ((NetMessage.Ip)msg).getProtocol(),
                                                          ((NetMessage.Ip)msg).getPriority(), 
                                                          ((NetMessage.Ip)msg).getTTL(),
                                                          ((NetMessage.Ip)msg).getId(),
                                                          ((NetMessage.Ip)msg).getFragOffset());
                
                nextHopIP = getThroughShortestPath(query.getSinkNCSLocation2D().fromNCS(myNode.getNodeGUI().getLocationContext()));

                /* if unable to find a better neighbor (local minima), drop the packet (the naive solution) */
                if (nextHopIP.equals(myNode.getIP()))
                    return; // drop packet
                else /* keep forwarding */
                    sendToLinkLayer(copyOfMsg, nextHopIP);
                return;
            }
        }
        else /* This is a (forwarding) node on the path in between the source and the sink */
            if (((NetMessage.Ip)msg).getPayload() instanceof NZMessage)
        {
            NZMessage msgNZ = (NZMessage)((NetMessage.Ip)msg).getPayload();                
            nextHopIP = getThroughShortestPath(msgNZ.getSourceLocation());
            
            // is the nextHopIP different than this node? 
            if (!myNode.getIP().equals(nextHopIP))
            {
                 NetMessage.Ip copyOfMsg = new NetMessage.Ip(msgNZ, /* The payload here is changed, however */ 
                                                          ((NetMessage.Ip)msg).getSrc(),
                                                          ((NetMessage.Ip)msg).getDst(),
                                                          ((NetMessage.Ip)msg).getProtocol(),
                                                          ((NetMessage.Ip)msg).getPriority(), 
                                                          ((NetMessage.Ip)msg).getTTL(),
                                                          ((NetMessage.Ip)msg).getId(),
                                                          ((NetMessage.Ip)msg).getFragOffset());
                /* keep forwarding */
                sendToLinkLayer(copyOfMsg, nextHopIP);
                return;
            }
            else
            {    
                /* I am the final destination, so shut the message up to the app layer */
                if (nextHopIP.equals(((NetMessage.Ip)msg).getDst()))
                    sendToAppLayer((MessageDataP2P)(msgNZ.getPayload()), null);
                return;
            }
        }
     }
    
     
    public void sendToAppLayer(Message msg, NetAddress src)
    {
        appInterface.receive(msg, src, null, (byte)-1, NetAddress.LOCAL, (byte)-1, (byte)-1);
    }
    
    public byte sendToLinkLayer(NetMessage.Ip ipMsg, NetAddress nextHopDestIP)
    {
        if (nextHopDestIP == null)
            System.err.println("NULL nextHopDestIP");
        if (nextHopDestIP == NetAddress.ANY)
            netEntity.send(ipMsg, Constants.NET_INTERFACE_DEFAULT, MacAddress.ANY, null);
        else
        {
            NodeEntry nodeEntry = myNode.neighboursList.get(nextHopDestIP);
            if (nodeEntry == null)
            {
                 System.err.println("Node #" + myNode.getID() + ": Destination IP (" + nextHopDestIP + ") not in my neighborhood. Please re-route! Are you sending the packet to yourself?");
                 System.err.println("Node #" + myNode.getID() + "has + " + myNode.neighboursList.size() + " neighbors");
                 new Exception().printStackTrace();
                 return ERROR; 
            }
            MacAddress macAddress = nodeEntry.mac;
            if (macAddress == null)
            {
                 System.err.println("Node #" + myNode.getID() + ": Destination IP (" + nextHopDestIP + ") not in my neighborhood. Please re-route! Are you sending the packet to yourself?");
                 System.err.println("Node #" + myNode.getID() + "has + " + myNode.neighboursList.size() + " neighbors");
                 return ERROR;
            }
            myNode.getNodeGUI().colorCode.mark(ColorProfileAggregate.TRANSMIT, 2);
            myNode.getNodeGUI().repaint();
            netEntity.send(ipMsg, Constants.NET_INTERFACE_DEFAULT, macAddress, null);
        }
        
        return SUCCESS;
    }
   
       
     public NetAddress getThroughShortestPath(Location2D destLocation)
    {
        NodeEntry nextNodeEntry;
         
        double closestDist = myNode.getNodeGUI().getPanelLocation2D().distanceTo(destLocation);
        NetAddress closestNode = myNode.getIP();
        LinkedList<NodeEntry> neighboursLinkedList = myNode.neighboursList.getAsLinkedList();
        
        for(NodeEntry nodeEntry: neighboursLinkedList)
        {
             /* Get the location coordinates of the neighbour 'i' */
            nextNodeEntry = nodeEntry;
            double neighbourDistance = nodeEntry.getNCS_Location2D().fromNCS(myNode.getNodeGUI().getLocationContext()).distanceTo(destLocation);
            if ( neighbourDistance < closestDist )
            {
                closestDist = neighbourDistance;
                closestNode = nodeEntry.ip;
            }
        }
        
        return closestNode;
    }   
       
     
    public void netQueueFull(Message msg, MacAddress nextHopMac)
    {
        //System.out.println("WARNING: Net Queue full");
    }

       
       
       
   /* **************************************** *
    * SWANS network's stack hook-up interfaces *
    * **************************************** */
    
    public RouteInterface getProxy()
    {
        return self;
    }
   
    
    public void setNetEntity(NetInterface netEntity)
    {
        if(!JistAPI.isEntity(netEntity)) throw new IllegalArgumentException("expected entity");
        if(this.netEntity!=null) throw new IllegalStateException("net entity already set");
        
        this.netEntity = netEntity;
    }

    
    public void setAppInterface(AppInterface appInterface)
    {
        this.appInterface = appInterface;
    }
        
   public void start()
   {
        //nothing
   }
    
}
