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

//-----------------------------------------------------------------------------
//
// File SinkTester.java
//
// Created by:
//          Tamer Elsharnouby.    (sharno@cs.umd.edu)
// 
//
// Description :
//          This code tests the sinkDTE (sink data transfer entity) by receiving
//          outputfile via sinkDTE, and saves outfile to the local disk.
//
//          The file accepts four runtime arguments:
//              1- Output filename (data is written into)
//              2- Local port number
//              3- Remote (source) domain name
//              4- Remote (source) port number
// 
//           Main method creates the outputfile and waits while receiving data. 
//           Every 64K of incoming data, it flushes the data into the local disk
//           and waits for four seconds (no data will be accepted during
//           this period (to test the implementation of DTSink.readyToAccept 
//           method)). If no data is received for 120 seconds consecutively, it
//           closes the output stream and exits the program.
//
//           Method deliverData (called by sinkDTE) saves data to outputfile 
//           buffer, updates the number of bytes received.
//------------------------------------------------------------------------------


//  Class SinkTester 
//
//  Attributes :
//  ------------
//  sinkDTE         : Object of class SW_Sink.
//  sinkDTE_bufSize : sinkDTE buffer size.
//  out             : The output file stream.
//  bytesRcvd       : Number of bytes received from sinkDTE.
//  bytesToBeFlushed: Number of bytes to be flushed.
//----------------------------------------------------------------------------
class SinkTester {
    static TesterInter tester = null;
    static SinkTester currentSinkTester = null;


    static int    localPort;
    static String remoteDN;
    static int    remotePort;
    static File   outputFile;

    SW_Sink sinkDTE          = null;
    int     sinkDTE_bufSize = 32 *1024 ;
    FileOutputStream out ;
    long bytesRcvd          = 0;
    long bytesToBeFlushed   = 0;
    static Object coordinator = new Object();

    public static void main (String []args ){
	// Get runtime arguments
        outputFile =new File (args [0 ].toString ());
        localPort =Integer .parseInt (args [1 ]);
        System .out .println ("Local Socket Port: "+localPort );
        remoteDN =args [2 ].toString ();
        remotePort =Integer .parseInt (args [3 ]);

	// get Remote Tester 
	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 remote registry");
        }catch (Exception e ){
            System .out .println ("Tester is not bound in registry");
        }
        currentSinkTester =new SinkTester ();
        currentSinkTester.startWork ();
    }

    void startWork (){

        try {
            out     = new FileOutputStream (outputFile);
            sinkDTE = new SW_Sink(localPort, remoteDN, remotePort, 
				  sinkDTE_bufSize );
            sinkDTE.dtsink = currentSinkTester ;
            while (true ){
                //coordinator.P (120 *100);
		long startTime = (new Date()).getTime();
		synchronized(coordinator){
		    coordinator.wait(120 * 1000);
		}
		if ((new Date()).getTime() - startTime >= 120 * 1000)
		    break;
	    }
        }catch (FileNotFoundException fnfe ){
            throw new Error ("File not found");
        }catch (IOException ioe ){
            throw new Error ("Outputfile stream failed");
        }catch (InterruptedException ie ){
	    throw new Error ("Remote user (Source) failed");
        }catch (Exception e ){
	    e.printStackTrace();
	} finally {
	    try {
		out.close();
		sinkDTE.closeSink();
	    } catch(Exception e){
		e.printStackTrace();
	    }
	}
        System .out .println ("SinkTester is closed");
    }


    // Method: deliverData
    // 
    // sinkDTE calls this method to deliver available data to user (SinkTester).
    //
    // Parameters : 
    // data      : an array contains the data received from the Sink Layer
    //-------------------------------------------------------------------------
    public void deliverData(byte data []){
	try {
	    tester.lock();
	    if (data.length <= 0)
		throw new Error("SinkTester.deliverData failed");
	    tester.deliverData(data);
            tester.printlnLog (Thread.currentThread().getName ()+
			      ":SinkTester:deliverData("+")");
            out .write (data, 0, data.length );
	    //coordinator.V ();
	    synchronized(coordinator){
		coordinator.notifyAll();
	    }
	    bytesRcvd += data.length ;
	    bytesToBeFlushed += data.length;
	    if (bytesToBeFlushed >= sinkDTE_bufSize ){
		bytesToBeFlushed =0 ;
		((Thread )new Thread (){
			public void run (){
			    try {
				out .flush ();
				Thread .sleep (4 *1000 );
				sinkDTE.readyToAccept(sinkDTE_bufSize );
			    }catch (RemoteException re ){
				re.printStackTrace();
			    }catch (IOException ioe ){
				throw new Error ("Outputfile stream operation"+
						 " failed");
			    }catch (InterruptedException ie ){
				throw new Error ("Monitor violation");
			    }
			}
		    }).start ();
	    }
	    tester.unlock();
	} catch (RemoteException re ){
	    re.printStackTrace();
        } catch (IOException ioe ){
	    ioe.printStackTrace();
            System .out .println ("Error in receiving data.");
	}
    }
}

