/*
 * AppSampleP2P.java
 *
 * Created on April 15, 2008, 11:14 AM
 */

/**
 *
 * @author  Oliviu Ghica
 */
package sidnet.stack.app;

import java.util.LinkedList;
import java.util.List;
import jist.swans.misc.Message; 
import jist.swans.net.NetInterface; 
import jist.swans.net.NetAddress; 
import jist.swans.mac.MacAddress;
import jist.swans.Constants; 
import jist.runtime.JistAPI; 
import sidnet.core.interfaces.ColorProfile;
import sidnet.colorprofiles.ColorProfileBezier;
import sidnet.colorprofiles.ColorProfileGeneric;
import sidnet.core.interfaces.AppInterface;
import sidnet.core.interfaces.CallbackInterface;
import sidnet.messages.MessageDataP2P;
import sidnet.messages.MessageHeartbeat;
import sidnet.core.misc.Node;
import sidnet.core.misc.Query;
import sidnet.utilityviews.statscollector.StatsCollector;
import sidnet.core.simcontrol.SimManager;

public class AppSampleP2P implements AppInterface, CallbackInterface {
    private final Node myNode; // The SIDnet handle to the node representation 
    
    /** network entity. */ 
    private NetInterface netEntity;
    
    /** self-referencing proxy entity. */
    private Object self;
    
    /** flag to mark if a heartbean protocol has been initialized */
    private boolean heartbeatInitiated = false;
    
    private boolean flag = false;
    
    private boolean signaledUserRequest = false;
    
    private final short routingProtocolIndex;
    
    private StatsCollector stats = null;
    
    private boolean startedSensing = false;
    
    /** Creates a new instance of the AppP2P */
    public AppSampleP2P(Node myNode, short routingProtocolIndex, StatsCollector stats)
    {
        this.self = JistAPI.proxyMany(this, new Class[] { AppInterface.class });
        this.myNode = myNode;
        
        /* To allow the upper layer (user's terminal) to signal any updates to this node */
        this.myNode.setAppCallback(this);
  
        this.routingProtocolIndex = routingProtocolIndex;
        
        this.stats = stats;
    }
    
    
    
    /* 
     * This is your main execution loop at the Application Level. Here you design the application functionality. It is simulation-time driven
     * The first call to this function is made automatically upon starting the simulation, from the Driver
     */
    public void run(String[] args) 
    {
         /* At time 0, set the simulation speed to x1000 to get over the heartbeat node identification phase fast */
           if (JistAPI.getTime() == 0)  // this is how to get the simulation time, by the way
               myNode.getSimControl().setSpeed(SimManager.X1000);
     
          //if (myNode.getID() != 2) return;  // ???
          /* This is a one-time phase. We'll allow a one-hour warm-up in which each node identifies its neighbors (The Heartbeat Protocol) */
          if (JistAPI.getTime() > 0 && !heartbeatInitiated)
          {
                //System.out.println("["+(myNode.getID() * 5 * Constants.MINUTE) +"] Node " + myNode.getID() + " broadcasts a heartbeat message");
               
                myNode.getNodeGUI().colorCode.mark(ColorProfileBezier.TRANSMIT, 500);
                myNode.getNodeGUI().repaint(); 
               
                /* To avoid all nodes to transmit in the same time */
                JistAPI.sleepBlock(myNode.getID() * 5 * Constants.SECOND); 
                
                MessageHeartbeat msg = new MessageHeartbeat();
                msg.setNCS_Location(myNode.getNCS_Location2D());
                               
                myNode.getNodeGUI().colorCode.mark(ColorProfileBezier.TRANSMIT, 500); 
                
                /* Send the heartbeat message. The heartbeat protocol will handle these messages and continue according to the protocol*/
                netEntity.send(msg, NetAddress.ANY, Constants.NET_PROTOCOL_HEARTBEAT, Constants.NET_PRIORITY_D_BESTEFFORT, (byte)100, null);  // TTL 100
                
                heartbeatInitiated = true;
          }
         
         /* Wait 1 hour for the heartbeat-bootstrap to finish, then slow down to allow users to interact in real-time*/
         if (JistAPI.getTime()/Constants.HOUR >= 1 && !flag) 
         {
              myNode.getSimControl().setSpeed(SimManager.X1);
              flag = true;
         }
          
          if (JistAPI.getTime()/Constants.MINUTE < 60)
          {
               JistAPI.sleep(5000*Constants.MILLI_SECOND);  // 5000 milliseconds
              
              /* this is to schedule the next run(args) */
              ((AppInterface)self).run(null);  /* !!! Pay attention to the way we re-run the app-layer code. We don't use a while loop, but rather let JiST call this again and again */
              
              return;
          }
      }
    
    
    public void run() {
        //Location currentLoc = field.getRadioData(new Integer(nodenum)).getLocation();
        JistAPI.sleep(2 + (long)((1000-2)*Constants.random.nextFloat())); 
        run(null);
    }
    
    
    /* Sensing the phenomena is most likely a periodic process. We wrote a procedure to do so.
     * Since the sensing() takes place at various simulation-time, this function should be called through a proxy reference, rather than directly to avoid
     * an infinite starvation loop */
      public void sensing(List params)
      {
           long samplingInterval = (Long)params.get(0);
           long endTime          = (Long)params.get(1);
           int queryId           = (Integer)params.get(2);
           long sequenceNumber    = (Long)params.get(3);
           NetAddress sinkAddress= (NetAddress)params.get(4);
                     
           JistAPI.sleepBlock(samplingInterval);
        
           double sensedValue = myNode.readAnalogSensorData(0);
           
           myNode.getNodeGUI().colorCode.mark(ColorProfileBezier.SENSE, 5);
           myNode.getNodeGUI().repaint();
           
           // Prepare the Aggregation Message containing the measurement
           MessageDataP2P msgDataP2P = new MessageDataP2P(sensedValue, queryId, sequenceNumber);
           
           stats.markDataPacketSent(sequenceNumber);
           
           // Transmit the Bezierd Message to the parent. Let the routing figure out who is the parent and do the aggregation, so the dest address is NULL
           netEntity.send(msgDataP2P, sinkAddress, routingProtocolIndex, Constants.NET_PRIORITY_D_BESTEFFORT, (byte)40, null);  // TTL 100
           
           myNode.getNodeGUI().colorCode.mark(ColorProfileBezier.TRANSMIT, 5);
           myNode.getNodeGUI().repaint();
           
           if (JistAPI.getTime() < endTime)
           {
                sequenceNumber++;
                
                params.set(0, samplingInterval);
                params.set(1, endTime);
                params.set(2, queryId);
                params.set(3, sequenceNumber);
                params.set(4, sinkAddress);
                
                /* this is to schedule the next run(args). DO NOT use WHILE loops to do this, nor call the function directly. Let JiST handle it */
                ((AppInterface)self).sensing(params);
           }
      }
      
    /* Callback registered with the terminal,
     * The terminal will call this function whenever the user posts a new query or just closes the terminal window
     * <p>
     * You should inspect the myNode.localTerminalDataSet.getQueryList() to check for new posted queries that your node must act upon
     * Have a look at the TerminalDataSet.java for the available data that is exchanged between this node and the terminal
     */
    public void signalUserRequest()
    {
        /* We'll assume that the node through which the user has posted a query becomes a sink node */
        if (myNode.getQueryList().size() > 0 )
        {         
            Query query = ((LinkedList<Query>)myNode.getQueryList()).getLast();
            myNode.getNodeGUI().colorCode.mark(ColorProfileGeneric.SINK, ColorProfile.FOREVER); // to make easier to you to see the node you've posted the query through (the sink node)
          
            if (!query.isDispatched())
            {        
                myNode.getNodeGUI().colorCode.mark(ColorProfileBezier.SINK, ColorProfileBezier.FOREVER);
                myNode.getNodeGUI().repaint();
                
                int[] rootIDArray = new int[1];
                rootIDArray[0] = myNode.getID();
                
                MessageDataP2P msgInitP2P = new MessageDataP2P(query, myNode.getID() /* queryId */);
                
                netEntity.send(msgInitP2P, null /* unknown Dest IP, but we know its approx location */, routingProtocolIndex /* (see Driver) */, Constants.NET_PRIORITY_D_BESTEFFORT, (byte)100, null);  // TTL 100                  
                            
                query.dispatched(true);
            }
        }
    }
    
    
    /* Message has been received. This node must be the either the sink or the source nodes */
    public void receive(Message msg, NetAddress src, MacAddress lastHop, byte macId, NetAddress dst, byte priority, byte ttl) 
    {   
        if (myNode.getEnergyManagement().getBattery().getPercentageEnergyLevel() < 5)
            return;
        
                    
        
        
        if (msg instanceof MessageDataP2P) /* This is a source node. It receives the query request, and not it prepares to do the periodic sensing/sampling */
        {
             MessageDataP2P msgInitP2P = (MessageDataP2P)msg;
         
             if (msgInitP2P.query != null) /* a query init message */
             {
                if (!startedSensing) /* To avoid creating duplicated sensing tasks due to duplicated requests, which may happen */
                {
                    myNode.getNodeGUI().colorCode.mark(ColorProfileBezier.SOURCE, ColorProfileBezier.FOREVER);
                    myNode.getNodeGUI().repaint(); 
                    
                    startedSensing = true;
                
                    LinkedList params = new LinkedList();
                    params.add(msgInitP2P.query.getSamplingInterval());   /* sampling interval */
                    params.add(JistAPI.getTime()/Constants.MILLI_SECOND + msgInitP2P.query.getEndTime()); /* endTime */
                    params.add(msgInitP2P.query.getID());
                    params.add((long)0);
                    params.add(msgInitP2P.query.getSinkIP());
                    
                    JistAPI.sleepBlock(msgInitP2P.query.getSamplingInterval());
                    
                    sensing(params);
                }
             }
             else /* it is a data message, which means this node is the sink (consumer node). */
             {
                 stats.markDataPacketReceived(msgInitP2P.sequenceNumber);
                 myNode.getNodeGUI().setUserDefinedData1((int)msgInitP2P.dataValue);
                 myNode.getNodeGUI().setUserDefinedData2((int)msgInitP2P.sequenceNumber);
                 /* Connecting a terminal to this node, at run time, allows the user to visualize the result of the posted query */
                 myNode.getNodeGUI().getTerminal().appendConsoleText(myNode.getNodeGUI().localTerminalDataSet, "Sample #"+msgInitP2P.sequenceNumber + " | val: " + msgInitP2P.dataValue);
             }
        }
    }
 
    
    
    
    
    /* **************************************** *
     * SWANS network's stack hook-up interfaces *
     * **************************************** */
    
     public void setNetEntity(NetInterface netEntity)
    /**
    * Set network entity.
    *
    * @param netEntity network entity
    */
    {
       this.netEntity = netEntity;
    } 
    
    public AppInterface getAppProxy()
    /**
    * Return self-referencing APPLICATION proxy entity.
    *
    * @return self-referencing APPLICATION proxy entity
    */
    {
        return (AppInterface)self;
    } 
}
