import java.util.*;
import java.io.*;
import java.awt.Frame;
import java.awt.TextArea;
import java.awt.Color;
import java.awt.Canvas;
import java.awt.*;





class param
{
    public static final int datasize=1024;
    public static final int headersize=80;
    public static final int linkspeed=18;
    public static final int pdelay=3;
    public static final int intdelay=3;
    public static  double droprate=0;
    public static  double timeout=(2*(pdelay+intdelay)+2*(datasize+headersize)/linkspeed);
    public static final int delay_low=0;
    public static final int delay_high=(int)timeout/5;
    public static final int reportgap=(int)timeout*5;
    public static  double th_th;
	
}

class ana
{
    public static int datasent1=0;
    public static int headersent1=0;
    public static int datasent2=0;
    public static int headersent2=0;
    public static int delay1=0;
    public static int delay2=0;
} 



    
    
class MyFrame extends Frame
{
    TextArea t=null;
    MyFrame(String title)
    {
	super(title);
	setSize(300,300);
	t=new TextArea();
	t.setBackground(Color.white);
	
	add(t);
	show();
	//validate(true);
    }
    public void addtext(String s,Color c)
    {
       t.setForeground(c);
	t.appendText("\n"+s);
    }
    
}


/*

class MyFrame extends JFrame
{
    TextArea t=null;
   MyCan can;
    MyFrame(String title)
    {
	super(title);
	setSize(300,300);
	//t=new TextArea();
	can=new MyCan();
	JScrollPane s=new JScrollPane(can);
	
	getContentPane().add(s);
	
	//t.setBackground(Color.white)
	
	//add(t);
	show();
	//validate(true);
    }
    public void addtext(String s,Color c)
    {
	//t.setForeground(c);
	//t.appendText("\n"+s);
	can.c=c;
	can.str=s;
	
	can.repaint();
	can.y+=20;
    }
    
}

class MyCan extends  Canvas
{
    public Color c;

    public String str;
    int x,y;

    MyCan()
    {
	str=new String("");
	x=5;
	y=3;
	
    }
    
    public void paint(Graphics g)
    {
	g.setColor(c);
	
	g.drawString(str,x,y);
	
    }
}
*/



	
   

public class a extends Thread
{
    public synchronized static void main(String args[])
    {

	if(args.length<2)
	    {
		System.out.println("Usage  : 1bit drop-probabilty timeoutfactor");
		
		return;
	    }

	param.droprate=Double.parseDouble(args[0]);
	param.timeout*=Double.parseDouble(args[1]);


	double bothrate=1.0-(double)(1.0-param.droprate)*2.0;
	
	 param.th_th=((double)param.datasize*param.linkspeed)/
  	( (bothrate/(1-bothrate)*(param.datasize+param.headersize+param.linkspeed*param.timeout))
 				     + 2*(param.datasize+param.headersize+param.linkspeed*(param.pdelay+param.intdelay)) );
	
	


	
	try{
	 
	PipedOutputStream s1out=new PipedOutputStream();
	PipedInputStream c1in=new PipedInputStream(s1out);

	PipedOutputStream s2out=new PipedOutputStream();
	PipedInputStream c2in=new PipedInputStream(s2out);

	PipedOutputStream c1out=new PipedOutputStream();
	PipedInputStream s2in=new PipedInputStream(c1out);

	PipedOutputStream c2out=new PipedOutputStream();
	PipedInputStream s1in=new PipedInputStream(c2out);

	MyFrame instrument=new MyFrame("Instrumentation");

	MyFrame fr_pr=new MyFrame("Parameters");
	fr_pr.addtext("Data size : " + param.datasize,Color.red);
	
	fr_pr.addtext("Overhead : " + param.headersize,Color.red);
	fr_pr.addtext("Link speed : " + param.linkspeed,Color.red);

	fr_pr.addtext("Propagation delay : " + param.pdelay,Color.red);
	fr_pr.addtext("Interrupt delay : " + param.intdelay,Color.red);
	fr_pr.addtext("Drop Rate : " + param.droprate,Color.red);
	fr_pr.addtext("Timer Value  " + param.timeout,Color.red);
       
		fr_pr.addtext("Delay_low :  " + param.delay_low,Color.red);
		fr_pr.addtext("Delay_high : " + param.delay_high,Color.red);
		

	   //fr_pr.addtext("Theoretical Data Throughput : " + param.th_th,Color.red);
	// Construct Threads

	
	EventManager em=new EventManager();


	SenderReceiver sender1=new SenderReceiver(s1out,s1in,em,1,true,instrument);
	
	SenderReceiver sender2=new SenderReceiver(s2out,s2in,em,2,false,instrument);


	Channel channel1=new Channel(c1out,c1in,em,2,sender1,instrument,fr_pr);
	Channel channel2=new Channel(c2out,c2in,em,1,sender2,instrument,fr_pr);
	
	
	

	channel1.start();
	channel2.start();
	sender1.start();
	sender2.start();

	channel1.interrupt();
	channel2.interrupt();
	sender1.interrupt();
	sender2.interrupt();
	

	

	}
	catch ( IOException e)
	    {

		System.out.println("main ex : " + e);

	    }
	
	
    }
}


class frame
{
    public int seq,ack;
    public String info;
    public String header;

    public int size()
    {
	return 4+4+info.length()+header.length();
    }

    public String toString()
    {
	return String.valueOf(seq)+"/" + String.valueOf(ack)+"/"+info+"/" +header;
    }


    public void restore(String s)
    {
	StringTokenizer st=new StringTokenizer(s,"/");

		seq=Integer.parseInt(st.nextToken());

		ack=Integer.parseInt(st.nextToken());
		info=st.nextToken();
		header=st.nextToken();

    }
    
}



class Event
{
    int owner;
    int time;
    int type;

    
    public Event(int a,int b , int c)
    {
	owner=a;
	time=b;
	type =c;
    }

    public Event()
    {}

    public void  display()
    {
	System.out.println("Event Owner : " + owner  + " time : " + time + " type : " +type);

    }


}

    
class EventManager
{

    int clock;
    int topclock;
    Event head;
    Vector EventList=new Vector(500,2);
    int count;
    

    EventManager()
    {
	count =0;
	clock=0;
	topclock=0;

    }


    public void  updateClock()
    {

	clock=topclock;

    }
    
    public void display()
    {
	System.out.println("Event list start");

	int i;
	for(i=0;i<count;i++)
	    {
		Event e1=(Event)EventList.elementAt(i);

		e1.display();
	    }
	

	System.out.println("Event list end");
    }


    public void removetimer(int id)
    {
	int i;
	for(i=0;i<count;i++)
	    {
		Event e1=(Event)EventList.elementAt(i);

		if(e1.owner==id && e1.type==2)
		    {
		    EventList.remove(i);
		    break;
		    
		    }


		
	    }

	count--;
	
	
    }
    
 
    public synchronized void add(Event e)
    {

	
	int i;
	
	EventList.add(e);
	count++;
	Collections.sort(EventList,new Comparator() {
		public int compare(Object a,Object b)
		{
		    int diff=((Event)a).time-((Event)b).time;
		    if(diff<0) return -1;
		    if(diff>0) return 1;
		    return 0;
		}
	    }
			 );
	

	
    }


 
    public  synchronized Event top()
    {
	if(count==0)
	    return new Event(-1,1,1);
	Event e= (Event)EventList.elementAt(0);
	topclock=e.time;
	return e;
	
    }

    public synchronized Event extract()
    {

	Event r=(Event)EventList.remove(0);
	count--;
	clock=r.time;
	return r;
    }

    public synchronized void remove(Event e)
    {
	EventList.remove(e);
	count--;
    }			
}


class Channel extends Thread
{
    private DataOutputStream out;
    private DataInputStream in; 
    EventManager eMan;
    frame f;
    int rid;
    SenderReceiver sr;
    MyFrame fr=null,fr_ins,fr_pr;
    int datasent=0,overheadsent=0;
    int lastclock=0;
    int packetssent=0;
    int packetsdropped= 0;
    int totaldelay=0;

    int lasttried=0;
    
   
    

    
    public Channel(OutputStream os,InputStream is,EventManager em,int rid,SenderReceiver sr,MyFrame fr_ins,MyFrame fr_pr)
    {
	
	
	try
	    {
		
	       	out=new DataOutputStream(os);
		in=new DataInputStream(is);
	    }

	catch(Exception e){
	    System.out.println("Channel Exception");
	}
	
	eMan=em;
	this.rid=rid;
	this.sr=sr;
	
	fr=new MyFrame("Channel from Module " + ((rid==2) ? 1 : 2) + " to Module " + rid);

	this.fr_ins=fr_ins;
	this.fr_pr=fr_pr;
	
	
    }

    public void run()
    {
	//System.out.println("Start waiting");
	//System.out.flush();
	
	try{
	    while(!interrupted())
		{}
	    
	}
	catch(Exception e){

	    System.out.println("channel exception : " + e);
	    
	}

	
	while(true)
	    {
		send();
	    }
    }

    private  synchronized void send()
    {

	String s=null;
	try
	    {
		if(in.available()>0)
		    {
			s=in.readUTF();
		       
		    }
		
		else
		    {
	    

			return;
		    }
	    }
	
	catch(Exception e)
	    {
		
		System.out.println("channel exception : " + e);

	    }
			
	//___Set Event for Sender 
	

     

	String dropped="";
	//__ decide if drop
	double dropp=Math.random();
	if(dropp<param.droprate)
	    {
		dropped="Dropped !";
	    }
	
	try{

	    int delay=calcDelay();
			
	    Event e=new Event();
	    e.type=1;
	    e.owner=rid;
			
	    e.time=eMan.clock+delay;
	    //eMan.display();
	    
	 

	    
	    System.out.println(dropped + " From Sender :" +( (rid==1) ? 2 : 1) + " " + s + " at time : " + (eMan.clock+param.intdelay) + " Reach time : " + e.time );
	    
	    fr.addtext(dropped + " Data:" +  s + " at time : " + eMan.clock+param.intdelay + " Reach time : " + e.time,Color.black );


	    if(rid==2)
		{
		    datasent=ana.datasent2;
		    overheadsent=ana.headersent2;
		    totaldelay+=ana.delay2;
		}
	    else
		{
		    datasent=ana.datasent1;
		    overheadsent=ana.headersent1;
		     totaldelay+=ana.delay1;

		}
	    

	  
	      
	    if(eMan.clock-lastclock>param.reportgap)
		{
		    lastclock=eMan.clock;
		     fr_pr.addtext("  ",Color.blue);
		    fr_pr.addtext("Channel to : " + rid + " Performance report ",Color.blue);
		    fr_pr.addtext("No. of packets sent : " + packetssent,Color.blue);
		    fr_pr.addtext("No. of packets dropped : " + packetsdropped,Color.blue);
			    
			    
		    fr_pr.addtext("Effective Throughput ( Including header ): " +
				  (double)(datasent+overheadsent)/eMan.clock/param.linkspeed + "  w/o header : " +  (double)(datasent)/eMan.clock/param.linkspeed,Color.blue);
		    fr_pr.addtext("Effective delay : " + (double)totaldelay/packetssent,Color.blue);
		    fr_pr.addtext("  ",Color.blue);
		   
		}

	    
	    
	    if(dropped.equals("")==false)
		  {
		 
		    packetsdropped++;
		    /*if(lasttried==0)
			{
			    lasttried=eMan.clock+param.intdelay;
			    }*/
		
		    
		    
		  }
	    else
		{

		    /*  if(lasttried==0)
			{
			    lasttried=eMan.clock+param.intdelay;
			    }*/
		 
		    eMan.add(e);
		    out.writeUTF(s);


		    //totaldelay+=e.time-lasttried;
		    packetssent++;
		    //datasent+=param.datasize;
		    //overheadsent+=param.headersize;
		       lasttried=0;

		
		    
		    out.flush();


		}
	  
	    

	
	
	    
	}
       
	catch(Exception ex){}
			
	sr.interrupt();
	
    }
    

    private int calcDelay()
    {
	Random r=new Random();
	return param.intdelay+param.pdelay+(param.datasize+param.headersize)/param.linkspeed+r.nextInt(param.delay_high-param.delay_low)+param.delay_low;
	
    }
}

class SenderReceiver extends Thread
{
    private DataOutputStream out;
    private DataInputStream in;
    
    //  private Channel outChannel;
    frame r,s;
    int next_frame_to_send,next_frame_expected;
    String buffer;
    EventManager eMan;

    Event oldtimer=null;
    int id;
    int timerDelay=7000;
    boolean later;
    MyFrame fr,fr_ins;
    MyFrame fr_net;
    int count=0;
    String prevdata;
    int potime=0;
    
    
    public SenderReceiver(OutputStream os,InputStream is,EventManager em,int i,boolean later,MyFrame fr_ins)
    {
	id=i;
	
	
	try{

	    out=new DataOutputStream(os);	
	    in=new DataInputStream(is);       
	    out.flush();
	}

	catch(Exception e){}
	
	eMan=em;
	id=i;
	//	this.outChannel=outChannel;

	fr=new MyFrame("Module No : " + id);
	fr_net=new MyFrame("Network Layer for Module " + id);
	this.fr_ins=fr_ins;
	this.later=later;
	
	
    }

    private void start_timer(int cl)
    {

	try
	    {
		
		Event e=new Event();
		e.type=2;
		e.owner=id;
			
		e.time=cl+(int)param.timeout;
		//System.out.println("Sender  : " + id +  " Setting timer at : "  + e.time);


		//eMan.removetimer(id);
		if(oldtimer!=null)
		    eMan.remove(oldtimer);

		//System.out.println("Adding timer at time : " + e.time);
		eMan.add(e);
		oldtimer=e;
	    }
	catch(Exception ex)
	    {
		System.out.println("Exception : " + ex);

	    }

	
	
    }
    
    private String from_network_layer()
    {

	count++;
	if(id==1)
	    return "This is sentence " + count + ".";
	   return "How are you " + count + ".?";
	

	
    }

    private void to_network_layer(String s,int ptime)
    {
	System.out.println("Network layer... Received packet ! -- Sender : " + id + " Data  : " + s);

	fr.addtext("Network layer... Received packet ! -- Sender : " + id + " Data  : " + s,Color.blue);
	fr_net.addtext("Network layer... Received packet ! -- Sender : " + id + " Data  : " + s,Color.blue);

	if(id==1)
	    {
		ana.datasent1+=param.datasize;
		ana.headersent1+=param.headersize;
		ana.delay1=eMan.clock-ptime;
		
	    }
	else
	    {
		ana.datasent2+=param.datasize;
		ana.headersent2+=param.headersize;

		ana.delay2=eMan.clock-ptime;

	    }
	
	
    }

    private  synchronized  frame from_physical_layer()
    {
	frame f=new frame();
	try{
	    String s=in.readUTF();
	    f.restore(s);
	    
	}
	catch(Exception e){
	    System.out.println("phy ex" + e);
	}


	return f;
	
    }
    
    
	

    private void to_physical_layer(frame f)
    {


	try{
	    

	    out.writeUTF(f.toString() );
	    out.flush();

	    //  System.out.println("Data written out ");

	    System.out.flush();
	      
	}
	catch(Exception e){

	    // System.out.println("phy later ex " + e);

	}
	
	try{
	    
	    while(!interrupted())
		{}
	}
	catch(Exception e)
	    {}

	System.out.println("Sender : " + id + " Frame sent : " + f.toString());
	System.out.flush();
	fr.addtext( "Frame sent : " + f.toString(),Color.red);
	
	
	//outChannel.sent();			
    }
    
	
    public void run()
    {
	try{
	    while(!interrupted())
		{}
	   

	}
	
	catch(Exception e){
	    System.out.println("Sender wit erro : " + e);
	}
       


	next_frame_to_send=0;
	next_frame_expected=0;
	buffer=from_network_layer();

	s=new frame();
	
	s.info=buffer;
	s.seq=next_frame_to_send;
	s.ack=1-next_frame_expected;
	s.header=String.valueOf(param.intdelay);



	if(later)
	    {
		//System.out.println("tophylayer: " + id);
		to_physical_layer(s);

		//System.out.println("starttimer " + id);
		start_timer(param.intdelay);	
	    }
	

	while(true)
	    {
		try{
		    process_events();}
		catch(Exception e)
		    {
			e.printStackTrace();
		    }
	    }
    }

    private synchronized void process_events()
    {

	try
	    {
	Event e;
	
	e=eMan.top();
	if(e.owner!=id)
	    return;


	
	eMan.updateClock();
	
	if(e.type==1) //_Frame Arrival
	    {
	      
		r=from_physical_layer();

	
		
		System.out.println("Sender : " + id + " Frame arrival event ! at " + e.time + " frame : " + r.toString());

		fr.addtext("Sender : " + id + " Frame arrival event ! at " + e.time + " frame : " + r.toString(),Color.black);


		
		
		if(r.seq==next_frame_expected)
		    {
			//if(r.seq!=nl_frame_exp)
			//  fr_ins.addtext("Sender :" id + " Last two packets had same seq no. !");

			//nl_frame_exp=1-nl_frame_exp;
			    
			to_network_layer(r.info,Integer.parseInt(r.header));
			prevdata=r.info;
			next_frame_expected=1-next_frame_expected;
			
		    }
		else //__Re transmission
		    {
			if(r.info.equals(prevdata)==false)

			    {
			        fr_ins.addtext("Sender :"  +  id + " Last two packets had same seq no. but different data !",Color.black);
				
			    }
			
			
		    }
		

		if(r.ack==next_frame_to_send)
		    {
			buffer=from_network_layer();
			potime=eMan.clock;
			next_frame_to_send=1-next_frame_to_send;

		    }
	    }
	else
	    {

		//eMan.display();
		System.out.println("Sender : " + id + " Time out!");

		fr.addtext( "Time out!",Color.green);
		//eMan.remove(e);
	    }


	
	
	int cl=eMan.clock+param.intdelay;
	
	s.info=buffer;
	s.seq=next_frame_to_send;
	s.ack=1-next_frame_expected;
	s.header=String.valueOf(potime);
	
	
	to_physical_layer(s);

	//System.out.println("Setting timer");
	//System.out.flush();

	start_timer(cl);

	//eMan.display();

	if(e.type==1)
	    eMan.remove(e);
	    }
	catch(Exception ex)
	    {
		System.out.println("Exception : " + ex);
	    }
	
    }
}


    


    

	

