/*
 * ClockedSenderBehaviour.java
 *
 * Created on May 2, 2005, 5:21 PM
 *
 */

package DDMT.core.behaviour;

import jade.lang.acl.ACLMessage;
import jade.lang.acl.MessageTemplate;

import DDMT.Nodes.Node;
import DDMT.Nodes.Neighbor;
import DDMT.core.MainPanel.ControlClass;
import DDMT.core.misc.*;

import java.util.ListIterator;
import java.util.LinkedList;
import java.util.Vector;


/**
 *
 * @author  jk3
 *@description This class models a 'clocked message sender.'  Its purpose is to
 *make sure that messages don't get delivered before the global clock reaches 
 *the delivery time, which is calculated from the sent time plus the delay for
 *the edge involved.  
 *
 *The class will not increment the global clock until it recieves a confirm message 
 *from the agents that are running nodes that have been sent messages in the
 *current time period.  It makes sure that all messages that should be send in 
 *this time period are actually sent before continuing time.
 *
 *The ACLMessage.ReplyWith string is over-written in this class, so it should not
 *be used by any recieving code.
 */
public class ClockedSenderBehaviour extends jade.core.behaviours.CyclicBehaviour{
    
    //Linked List of current min delays
    LinkedList delays;
    
    //LIst of outgoing messages
    LinkedList outgoing;
    
    //List of callbacks that have not yet been processed
    LinkedList callbacks;
    
    //The list of events added to they system
    //LinkedList events;
    
    //The system time
    double time = 0;
    
    //List of nodes used to calculate delays
    Node[] nodes;
    
    //The last restriction ID given
    int restrictionID = 0;
    
    ACLMessage saved;
    /**
    *The state of the agent
    *0 = ready to send messages for this time period
    *1 = waiting for reply for the current time period
    **/
    int state = 0;
    
    //Message template to filter messages
    MessageTemplate mt;
        
    /** Creates a new instance of ClockedSenderBehaviour */
    public ClockedSenderBehaviour(Node[] n, LinkedList m)
    {
        nodes = n;
        delays = new LinkedList();
        outgoing = new LinkedList();
        callbacks = new LinkedList();
        //events = m;
        mt = MessageTemplate.MatchConversationId("callback");
    }
    
    public void action()
    {
        block();
    }
    
    /**
     *Method for sending and recieving messages
     */
    public void action(ACLMessage callback)
    {

        //System.out.println("got callback");
        //find the restriction associated with this callback and remove it
        //Mark this node as having responded
        int nodeId = Integer.parseInt(callback.getReplyWith());
        ListIterator iter = delays.listIterator();
        while( iter.hasNext() )
        {
            Restriction r = (Restriction)iter.next();
            if( r.getId() == nodeId )
            {
                iter.remove();
                break;
            }
        }

        this.send();
    }
    
    /**Method to send as many messages as possible given the current time 
     *and the list of restrictions.
     */
    public void send()
    {
        //System.out.println(delays.size());
        
        //Find the amount that we can forward the clock
        double increment = Double.MAX_VALUE;
        ListIterator iterator = delays.listIterator();
        Restriction restriction = null;
        while( iterator.hasNext() )
        {
            Restriction next = (Restriction)iterator.next();
            //System.out.println("restriction: "+next.getRestriction() + ", "+next.getNode());
            if( next.getRestriction() < increment )
            {
                increment = next.getRestriction();
                restriction = next;
            }
        }

        /*Now find the max that we can increment based on how fast this
        node's neighbors can respond*/
        if( increment == Double.MAX_VALUE )
        {
            increment = 0;
        }
        
        //Increment the clock and update the restrictions
        ListIterator iter = delays.listIterator();
        while( iter.hasNext() )
        {
            ((Restriction)iter.next()).incrementTime(increment);
        }

        //Increment time
        time += increment;

        //System.out.println("time is: "+time+" incremented by "+increment);

        //See what messages I need to send this time period
        iterator = outgoing.listIterator();
        while( iterator.hasNext() )
        {
            ACLMessage temp = (ACLMessage)iterator.next();
            
            double sendTime = Double.parseDouble( temp.getProtocol() );
            
            //If we should send this message this time period...
           // if( sendTime <= this.time )
            {
                //Send the message
                myAgent.send(temp);

                //Remove the message from the outgoing queue
                iterator.remove();
            }
        }
    }
    /**
     *Method for adding messages to the outgoing queue
     *Will return the scheduled send time for the message,
     *which is the same as recieve time
     */
    public void sendMessage(ACLMessage m)
    {   
        //Get the node number
        int node = Integer.parseInt( m.getLanguage() );
        
        //Get the recieving node number
        int toNode = Integer.parseInt( m.getEncoding() );
        
        //get the delay for this message
        double delay = 0;
        
        /*If the sender and reciever are the same, then it's a timeout and 
        we should get the time from the .content field*/
        if( node == toNode)
        {
            delay = Double.parseDouble( m.getContent() );
        }
        
        //Otherwise, we should look in the list
        else {
            Vector nbors = nodes[node].getNeighbors();
            for( int i = 0; i< nbors.size(); i++)
            {
                if( ((Neighbor)nbors.get(i)).getNumber() == toNode )
                {
                    delay = ((Neighbor)nbors.get(i)).getDelay();
                }
            }
        }
        
        //Get the id num
        int idNum = Integer.parseInt( m.getReplyWith() );
        
        //set the sendtime for the case that this is the first message
        double sendTime = 0;
        
        /*
         *Find the send time for this message (the time that it should be sent)
         *This is found by getting the send time for the message that caused
         *this message to be sent plus the delay for that message ( the send 
         *time for this message is the recieved time for the last message )*/
        boolean found = false;
        for( int i = 0; i< delays.size(); i++){
            if( ((Restriction)delays.get(i)).getId() == idNum){
                sendTime = ((Restriction)delays.get(i)).getTime() 
                    +((Restriction)delays.get(i)).getDelay();
                found = true;
                break;
            }
        }
        if( !found && idNum > 0)
            System.out.println("ILLEGAL STATE "+idNum);

        
        /*Find the longest time before this message could result in other 
         messages being sent.
         This is calculated by finding the list of neighbors of the recieving
         node and finding the shortest delay on edges to those neighbors.  This
         is the shortest time that another node could recieve a message 
         resulting from this one being delivered*/
        Vector nbors = nodes[toNode].getNeighbors();
        double minDelay = Double.MAX_VALUE;
        for( int i = 0; i< nbors.size(); i++)
        {
            if( ((Neighbor)nbors.get(i)).getDelay() < minDelay )
            {
                minDelay = ((Neighbor)nbors.get(i)).getDelay();
            }
        }
        double restriction = sendTime + minDelay - time;
        
        
        //Increment the IDnum
        this.restrictionID++;

        //Create a new restriction from the message
        Restriction r = new Restriction(
            restrictionID,
            node,
            sendTime,
            delay,
            restriction);

        //Set the restriction ID on the message
        m.setReplyWith( ""+restrictionID );
        
        //set the send time on the message
        m.setProtocol( ""+sendTime );
        
        //System.out.println("adding message to be sent at "+m.getProtocol());
        
        //Add the restriction
        delays.add(r);
                
        //add the message
        outgoing.add(m);
        
        //If this is the first message, send
        if( idNum == 0)
        {
            this.send();
        }
        
        //Add the event for the message
        EventType event = new MessageType( 
            node,
            toNode,
            m.getContent(),
            sendTime);
        this.addEvent(event);
    }
    
    /**
     *Method to process a print message
     */
    public void processPrint( ACLMessage message )
    {
        //Get the node number
        int nodeNum = Integer.parseInt( message.getLanguage() );
        
        //Get the sent time
        int id = Integer.parseInt( message.getReplyWith() );
        double sentTime = 0;
        ListIterator iter = delays.listIterator();
        while( iter.hasNext() )
        {
            Restriction curr = (Restriction)iter.next();
            if( curr.getId() == id )
            {
                sentTime = curr.getTime();
                break;
            }
        }
        
        //Make the event
        EventType event = new UserType( nodeNum, message.getContent(), sentTime);
        this.addEvent(event);
    }
    
    /**
     *Method to process a pause message
     */
    
    /*
     *Method to add an event to the list sorted by time
     */
    public void addEvent( EventType event )
    {
        /*ListIterator iter = events.listIterator();
        boolean found = false;
        while( iter.hasNext() )
        {
            EventType curr = (EventType)iter.next();
            if( (curr.Compare( event ) > 0) )
            {
                found = true;
                iter.add(event);
                break;
            }
        }
        if( !found )
        {
            iter.add(event);
        }*/
    }
    
    /**
     *Class that models one element of the linked list
     *Needs to show:
     *1.) the min delay left for the edge
     *2.) the node that should 'callback' to remove this restriction
     */
    class Restriction
    {
        //The remaining delay for this restriction
        double restriction;
        
        //the delay for this message (time + delay) is the received time
        double delay;
        
        //The time that this restriction was set in place
        double time;
        
        //The node that can remove the restriction
        int node;
        
        //The id number for this restriction
        int idNum;
        
        //Constructor
        Restriction(int id, int n, double t, double d, double r)
        {
            idNum = id;
            node = n;
            time = t;
            delay = d;
            restriction = r;
        }
        
        //Accessor functions
        public double getTime(){
            return time;
        }
        public int getNode(){
            return node;
        }
        public double getDelay(){
            return delay;
        }
        public double getRestriction(){
            return restriction;
        }
        public int getId(){
            return idNum;
        }
        
        //Mutator functions
        public void setTime( double t)
        {
            time = t;
        }
        public void setNode( int n){
            node = n;
        }
        public void setDelay( double d){
            delay = d;
        }
        public void setRestriction( double r){
            restriction = r;
        }
        public void setId(int id){
            idNum = id;
        }
        
        /**
         *Method to reduce this restriction as the time increases.
         */
        public void incrementTime( double t ){
            restriction -= t;
            if( restriction < 0 )
                restriction = 0;
        }
        
    }
}
