import java.util.*;
import java.net.*;
import java.lang.*;
import java.io.*;
import java.rmi.Naming ;
import java.rmi.RemoteException ;
import java.rmi.RMISecurityManager ;

//-----------------------------------------------------------------------------
//
// File SourceTester.java
//
// Created by:
//          Tamer Elsharnouby.    (sharno@cs.umd.edu)
// 
//
// Description :
//         This file tests the source data transfer entity (SW_Source) by 
//         sending a file via srcDTE(Source Data Transfer) to sink node. 
//         SourceTester 
//         - Accepts five runtime arguments:
//                 1- inputfile name (file to be sent to sink domain)
//                 2- local port number
//                 3- remote (sink) domain name
//                 4- remote (sink) port number
//                 5- intersendDelay: set to 1 to introduce intersend delay
//                    between sending consecutive messages. 
//         - Opens inputfile and determines its size. 
//         - Creates srcDTE with appropriate parameters 
//         - Loops through the following:
//            - Reads a portion of inputfile (this portion does not exceed 
//              maxPayload, 
//            - Calls SW_Source.sendData with this portion as a parameter, 
//            - Sleeps for a random duration (if intersendDelay is set),
//            - Waits until SW_Source's buffer is not full 
//            - Randomly waits till the whole srcDTE buffer is empty.
//            - Determines the size of next portion to be read from inputfile
//           until the whole inputfile is read. 
//         - Waits till the whole sent data is acked or timeout (900 seconds)
//           fires.
//
//          Method ackData is called by srcDTE to indicate that 
//          data is acked (presumably whenever srcDTE receives a
//          new ack).
//         
//         Tester estimates available space in srcDTE buffer by srcDTE_bufSize 
//         (fixed at srcDTE creation) plus amount of data transffered by tester
//         to SW_Source minus amount of data acked.
//----------------------------------------------------------------------------- 


//  Class SourceTester
//  
//
//  Attributes :
//  ------------
//  srcDTE         : Source data transfer entity (object of class SW_Source).
//  maxPayload     : Maximum number of bytes delivered to srcDTE during single
//                   call to sendData (Usually equal to msgMaxSize minus 
//                   msg header).
//  srcDTE_bufSize : Buffer size of srcDTE. 
//  srcDTE_bufUsed : Occupied portion of srcDTE buffer in bytes.
//  bytesSent      : Number of bytes accepted by srcDTE
//  bytesAcked     : Number of bytes acknowledged by sink 
//-----------------------------------------------------------------------------

class SourceTester {
    static TesterInter tester;

    static int localPort ;
    static String remoteDN ;
    static int remotePort ;
    static File inputFile ;
    static boolean intersendDelay ;

    SW_Source srcDTE;
    int  maxPayload     = 2000 ;
    int srcDTE_bufSize = 32 *1024 ;
    int srcDTE_bufUsed = 0 ;
    long bytesSent      = 0 ;
    long bytesAcked     = 0 ;
    static Object coordinator =new Object();
    static Object lock = new Object();
 
    static SourceTester currentSourceTester ;

    public static void main (String []args ){

        inputFile  = new File (args [0].toString());
        localPort  = Integer.parseInt(args[1]);
        remoteDN   = args[2].toString();
        remotePort = Integer.parseInt(args[3]);
        intersendDelay = (Integer.parseInt(args[4]) == 1);

        if (System.getSecurityManager() == null)
            System.setSecurityManager(new RMISecurityManager());
        
        try {
            tester = (TesterInter) 
		Naming.lookup ("Tester");
	    //Naming.lookup ("//alexandria.cs.umd.edu/Tester");
	    System.out.println ("Tester found at registry");
        } catch (Exception e){
            System.out.println ("Tester is not bound in registry");
        }
        currentSourceTester = new SourceTester();
        currentSourceTester.startWork ();
    }

    void startWork (){
        FileInputStream in =null ;
        try {
            in =new FileInputStream (inputFile );

            long fileSize = inputFile.length ();
            System.out.println ("Inputfile Size: " + fileSize);

            srcDTE =new SW_Source (localPort, remoteDN, remotePort, 
				   srcDTE_bufSize );
            srcDTE.dtsource=currentSourceTester ;
            int effPayload ;
	    synchronized(coordinator){
		synchronized(lock) {
		    effPayload =(maxPayload >srcDTE_bufSize )?
			(int )srcDTE_bufSize :maxPayload ;
		    srcDTE_bufUsed =0 ;
		}
	    }
	    
            byte buf[] = new byte[effPayload + 1];
            int c;
            Random rn = new Random();
            System .out .println ("Data reading section starts ");

            while ((c = in.read(buf, 0, effPayload)) != -1){
                System.out.println("Source reads " + c + " bytes");
                byte data[] = new byte[c];
                System.arraycopy (buf, 0, data, 0, c);
                if (intersendDelay )
                    Thread .sleep ((int )rn .nextDouble ()*10000 );

		synchronized(coordinator){
		    synchronized(lock){
			bytesSent +=c ;
			srcDTE_bufUsed +=c ;
		    }
		}
		srcDTE.sendData(data );
                Thread .yield ();
                boolean block = false;

		synchronized(coordinator){
		    synchronized(lock){
			block = (srcDTE_bufUsed == srcDTE_bufSize) ? true:false;
		    }
		    if (block) {
			long start = (new Date()).getTime();
			coordinator.wait(900 *1000);
			if ( (new Date()).getTime() - start >= 900*1000)
			    throw new IOException();
		    }
		}
		
		// 10% of the time, SourceTester waits till the buffer is 
		// empty.  
                if (rn.nextDouble() * 100 > 90){
                    for (;;){
                        // coordinator .P (9 *100 );
			synchronized(coordinator){
			    long start = (new Date()).getTime();
			    coordinator.wait(900 *1000 );
			    if ( (new Date()).getTime() - start >= 900 * 1000)
				throw new IOException();
			    synchronized(lock){
				if (srcDTE_bufUsed == 0)
				    break;
			    }
			}
                    }
                }
		synchronized(coordinator){
		    synchronized(lock){
			effPayload =
			    (maxPayload > (srcDTE_bufSize -srcDTE_bufUsed ))?
			    (int )(srcDTE_bufSize -srcDTE_bufUsed ):maxPayload ;
		    }
		}
		buf =new byte [effPayload+1];
            }

            for (;;){
		//tester .lock ();
		synchronized(coordinator){
		    synchronized(lock){
			if (srcDTE_bufUsed ==0 ) 
			    break ;
		    }
		    long start = (new Date()).getTime();
		    coordinator.wait (900 * 1000);
		    if ( (new Date()).getTime() - start >= 900 * 1000)
			throw new IOException();
		}
            }
        }catch (FileNotFoundException fnfe ){
            System .out .println ("File is not found.");
        }catch (InterruptedException ie ){
            System .out .println ("Timeout of file transfer fired. "+
                                  "Operation is not complete.");
	}catch (RemoteException re ){
	    re.printStackTrace();
        }catch (Exception e ){
            System .out .println ("Error in -readLine- of in buffer.");
        }finally {
            try {
                srcDTE.closeSource ();
            }catch (Exception e ){
		e.printStackTrace();
	    }
        }
    }


    public void ackData(int n){
	try {
	    tester.lock();
	    if (!(bytesSent >=bytesAcked ))
		throw new Error ("Enabled Condition Failure: ackData ");
            tester.printlnLog (Thread.currentThread().getName() +
			     ":SourceTester:ackData(" + n + ")");
	    tester.ackData(n);
	    synchronized(coordinator) {
		synchronized(lock) {
		    srcDTE_bufUsed -= n;
		    bytesAcked = bytesAcked + (long) n;
		}
		coordinator.notifyAll();
	    }
	    tester.unlock();
	}catch (RemoteException re ){
	    re.printStackTrace();
	}
    }
}// End class
