import java.util.*;
import java.io.*;

public class StudentNetworkSimulator extends NetworkSimulator
{
    /*
     * Predefined Constants (static member variables):
     *
     *   int MAXDATASIZE : the maximum size of the Message data and
     *                     Packet payload
     *
     *   int A           : a predefined integer that represents entity A
     *   int B           : a predefined integer that represents entity B 
     *
     * Predefined Member Methods:
     *
     *  void stopTimer(int entity): 
     *       Stops the timer running at "entity" [A or B]
     *  void startTimer(int entity, double increment): 
     *       Starts a timer running at "entity" [A or B], which will expire in
     *       "increment" time units, causing the interrupt handler to be
     *       called.  You should only call this with A.
     *  void toLayer3(int callingEntity, Packet p)
     *       Puts the packet "p" into the network from "callingEntity" [A or B]
     *  void toLayer5(String dataSent)
     *       Passes "dataSent" up to layer 5
     *  double getTime()
     *       Returns the current time in the simulator.  Might be useful for
     *       debugging.
     *  int getTraceLevel()
     *       Returns TraceLevel
     *  void printEventList()
     *       Prints the current event list to stdout.  Might be useful for
     *       debugging, but probably not.
     *
     *
     *  Predefined Classes:
     *
     *  Message: Used to encapsulate a message coming from layer 5
     *    Constructor:
     *      Message(String inputData): 
     *          creates a new Message containing "inputData"
     *    Methods:
     *      boolean setData(String inputData):
     *          sets an existing Message's data to "inputData"
     *          returns true on success, false otherwise
     *      String getData():
     *          returns the data contained in the message
     *  Packet: Used to encapsulate a packet
     *    Constructors:
     *      Packet (Packet p):
     *          creates a new Packet that is a copy of "p"
     *      Packet (int seq, int ack, int check, String newPayload)
     *          creates a new Packet with a sequence field of "seq", an
     *          ack field of "ack", a checksum field of "check", and a
     *          payload of "newPayload"
     *      Packet (int seq, int ack, int check)
     *          chreate a new Packet with a sequence field of "seq", an
     *          ack field of "ack", a checksum field of "check", and
     *          an empty payload
     *    Methods:
     *      boolean setSeqnum(int n)
     *          sets the Packet's sequence field to "n"
     *          returns true on success, false otherwise
     *      boolean setAcknum(int n)
     *          sets the Packet's ack field to "n"
     *          returns true on success, false otherwise
     *      boolean setChecksum(int n)
     *          sets the Packet's checksum to "n"
     *          returns true on success, false otherwise
     *      boolean setPayload(String newPayload)
     *          sets the Packet's payload to "newPayload"
     *          returns true on success, false otherwise
     *      int getSeqnum()
     *          returns the contents of the Packet's sequence field
     *      int getAcknum()
     *          returns the contents of the Packet's ack field
     *      int getChecksum()
     *          returns the checksum of the Packet
     *      int getPayload()
     *          returns the Packet's payload
     *
     */

    /*   PLEASE USE THE FOLLOWING VARIABLES IN YOUR ROUTINES AS APPROPRIATE.
     *   int WindowSize  : the window size
     *   double RxmtInterval   : the retransmission timeout
     *   int LimitSeqNo  : when sequence number reaches this value, it wraps around
     */

    public static final int FirstSeqNo = 0;
    private int WindowSize;
    private double RxmtInterval;
    private int LimitSeqNo;
    
    private byte astate = 0;
    private Packet astored_pkt;
    private byte bstate = 0;
    private byte bOnceThru = 0;
    private Packet bstored_pkt;
    
    // Add any necessary class variables here.  Remember, you cannot use
    // these variables to send messages error free!  They can only hold
    // state information for A or B.
    




    // This is the constructor.  Don't touch!
    public StudentNetworkSimulator(int numMessages,
                                   double loss,
                                   double corrupt,
                                   double avgDelay,
                                   int trace,
                                   int seed,
                                   int winsize,
                                   double delay)
    {
        super(numMessages, loss, corrupt, avgDelay, trace, seed);
	WindowSize = winsize;
	LimitSeqNo = winsize+1;
	RxmtInterval = delay;
    }

    
    // This routine will be called whenever the upper layer at the sender [A]
    // has a message to send.  It is the job of your protocol to insure that
    // the data in such a message is delivered in-order, and correctly, to
    // the receiving upper layer.
    protected void aOutput(Message message)
    {
    	//On crée un nouveau paquet avec ack=0 
    	astored_pkt = new Packet(0, 0, 0, message.getData());

    	//Si on est en State 0
    	if(astate == 0) {
    		
    		//Calcul du checksum
    		astored_pkt.setChecksum(calculateChecksum(message.getData(), 0, 0));
    		
    		//Envoie du paquet
    		toLayer3(0, astored_pkt);
    		
    		//Début du timer et passage dans l'état 1 (attente de ACK 0)
    		startTimer(0, RxmtInterval);
    		astate = 1;
    		System.out.println("A: sending packet: " + astored_pkt.toString());
    		
    	//Si on est en state 2 (envoie paquet ACK 1)
    	} else if(astate == 2) {
    		//On change le numéro de séquence et l'ack. Puis on calcul le checksum
    		astored_pkt.setSeqnum(1);
    		astored_pkt.setAcknum(1);
    		astored_pkt.setChecksum(calculateChecksum(message.getData(), 1, 1));
    		
    		//Envoie du paquet
    		toLayer3(0, astored_pkt);
    		
    		//Début du timer et passage dans l'état 3 (attente de ACK 0)
    		startTimer(0, RxmtInterval);
    		astate = 3;
    		
    		System.out.println("A: sending packet: " + astored_pkt.toString());
    	} else {
    		//SI on est dans un état invalide. 
    		startTimer(0,  RxmtInterval* 4);
    		System.out.println("A: sending packet: denied.");
    	}
    }
    
    // This routine will be called whenever a packet sent from the B-side 
    // (i.e. as a result of a toLayer3() being done by a B-side procedure)
    // arrives at the A-side.  "packet" is the (possibly corrupted) packet
    // sent from the B-side.
    protected void aInput(Packet packet)
    {
    	//Si on est dans un état non logique (en cours d'envoie de paquet) on ignore l'ack
    	if(astate == 0) ;
    	
    	//On doit reçevoir un ACK 0
    	else if(astate == 1) {
    		//Verification du checksum
    		if(packet.getChecksum() != calculateChecksum(packet.getPayload(),
    													 packet.getSeqnum(),
    													 packet.getAcknum())) {
    			System.out.println("aInput: received packet is corrupted");
    			//Si mauvais ACK
    		} else if(packet.getAcknum() == 1) {
    			System.out.println("A: got ACK 1 , we're waiting for ACK1");    			
    			//Verification de l'ack
    		} else if(packet.getAcknum() == 0) {
    			//On passe dans le state d'envoie de paquet avec ack 1
    			astate = 2;
    			//On arrete le timer qui sert à vérifier les timeouts
    			stopTimer(0);
    			System.out.println("A: got ACK: " + packet.getAcknum() + " timeout timer stoped");
    		}
    		//Si on est dans un état non logique (en cours d'envoie de paquet) on ignore l'ack
    	} else if(astate == 2) ;
    	
    	//Comme astate==1 sauf qu'on attend un ACK ==1 
    	else if(astate == 3) {
    		//Verif checksum
    		if(packet.getChecksum() != calculateChecksum(packet.getPayload(),
    				packet.getSeqnum(),
    				packet.getAcknum())) {
    			System.out.println("aInput: received packet is corrupted");
    			
			//Mauvais ACK
    		} else if(packet.getAcknum() == 0) {
    			System.out.println("A: got ACK0, we're waiting for ACK 1");
    			
			//Bon ACK
    		} else if(packet.getAcknum() == 1) {
    			stopTimer(0);
    			astate = 0;
    			System.out.println("A: got ACK: " + packet.getAcknum());
    		} else ;
    	}
    }
    
    // This routine will be called when A's timer expires (thus generating a 
    // timer interrupt). You'll probably want to use this routine to control 
    // the retransmission of packets. See startTimer() and stopTimer(), above,
    // for how the timer is started and stopped. 
    protected void aTimerInterrupt()
    {
    		System.out.println("A: Timer interrupt, resending packet");
    		
    		toLayer3(0, astored_pkt);
    		
    		startTimer(0, RxmtInterval);
    }
    
    // This routine will be called once, before any of your other A-side 
    // routines are called. It can be used to do any required
    // initialization (e.g. of member variables you add to control the state
    // of entity A).
    protected void aInit()
    {
//You probably won't need to put any code in this method.  It is from another assignment.
    }
    
    // This routine will be called whenever a packet sent from the B-side 
    // (i.e. as a result of a toLayer3() being done by an A-side procedure)
    // arrives at the B-side.  "packet" is the (possibly corrupted) packet
    // sent from the A-side.
    protected void bInput(Packet packet)
    {
    	//Si le paquet est corrompu on demande un réenvoi.
    		if(packet.getChecksum() != calculateChecksum(packet.getPayload(), packet.getSeqnum(), packet.getAcknum())) {
    			if(bOnceThru == 1)
    			{
    				System.out.println("B: Received corrupted resending");
    				toLayer3(1, bstored_pkt);
    			}
			
			//Si le seqnum coresspond à l'état actuel de b
    		} else {
    			if((packet.getSeqnum() == 0 && bstate == 0) || (packet.getSeqnum() == 1 && bstate == 1)) {
    				
    				//On transmet le paquet au layers au dessus.
    				toLayer5(packet.getPayload());
    				System.out.println("B: got packet " + packet.toString());
    				
    				//On emet l'ack correspondante.
    				bstored_pkt = packet;
    				toLayer3(1, bstored_pkt);
    				System.out.println("B: sending ACK " + packet.getAcknum());
    				
    				//On change d'etat pour B. 
    				bstate = (byte) ((bstate+1) % 2);
    				bOnceThru = 1;
    			} else {
    				//Si on a reçu un paquet dans le mauvais état on réémet l'ACK.
    				if(bstate == 1 || bOnceThru == 1) {
    					toLayer3(1, bstored_pkt);
    					System.out.println("B: sending ACK: " + packet.getAcknum());
    				}
    			}
    		}
    }
    
    // This routine will be called once, before any of your other B-side 
    // routines are called. It can be used to do any required
    // initialization (e.g. of member variables you add to control the state
    // of entity B).
    protected void bInit()
    {
//You probably won't need to put any code in this method.  It is from another assignment.
    }

 private int calculateChecksum(String payload, int seqno, int ackno) 
    {
	int singlecharval;
	int sum = 0;

	// calculate checksum 
	int max = payload.length();
     
	for (int i = 0; i < max; i++) {
	    singlecharval = payload.charAt(i);
	    sum += singlecharval;
	}

	sum += seqno;
	sum += ackno;
	return sum;

    }
}

