import java.util.*;
import java.io.*;
import java.rmi.Naming;
import java.rmi.RemoteException;
import java.rmi.RMISecurityManager;
import java.rmi.server.UnicastRemoteObject;

//-----------------------------------------------------------------------------
//
// File Tester.java
//
// Created by:
//          Tamer Elsharnouby.    (sharno@cs.umd.edu)
// 
//
// Description :
//         This file:
//            - Specifies the data transfer service (details later in this 
//              file.)
//            - Provides methods to lock and unlock a lock, where all the
//              RMI methods that define the data transfer service.
//            - Provides methods to print data into log file "List.log".
//-----------------------------------------------------------------------------
class Tester  extends UnicastRemoteObject implements TesterInter{
    static PrintWriter log;
    static final boolean AUTO_FLUSH = true;
    static RWLock lock = new RWLock();

    Tester() throws RemoteException {super();}

    
    public void lock () throws RemoteException {
        lock.getWriteLock();
    }

    public void unlock () throws RemoteException {
        lock.releaseLock();
    }

    public void printlnLog(String str) throws RemoteException {
        log .println (str);
    }

    public void printLog(String str) throws RemoteException {
        log .print (str);
    }


    public static void main(String args[]){
        try {
            log =
                new PrintWriter(new BufferedWriter(new FileWriter("List.log")),
                                AUTO_FLUSH);
        } catch (IOException ioe){
            System.out.println("Output file can not be created");
            return;
        }

        // Create and install a security manager
        if (System.getSecurityManager() == null) {
            System.setSecurityManager(new RMISecurityManager());
        }
        try {
            Tester tester = new Tester();
            Naming.rebind("Tester", tester);
            System.out.println("Start Rebinding Tester");
            //Naming.rebind("//alexandria.cs.umd.edu/Tester", tester);
        } catch (Exception e) {
            System.out.println("Tester err: " + e.getMessage());
            e.printStackTrace();
        }
    }


    //      The following part describes Data Transfer (DT) service.
    //      DT service specifies a reliable data transfer service  from a
    //      source entity to a sink entity, that is,
    //         - Safety: data is delivered in the same sequence without loss or
    //                   duplication.
    //      DT assumes that source and sink are always connected and correctly
    //      initialized.
    //
    //      DT has two groups corresponding to source and sink entities
    //      and events associated with each group.
    //      - Group "Source" :
    //          Four events are associated with Source:
    //              - constructor(localPort, remoteDN, remotePort, availBufSize)
    //                constructs source entity with parameters: entity's local
    //                port, remote  domain name, remote port and entity's buffer
    //                size (in bytes).
    //              - sendData (data)
    //                sends data from local user to source entity to be 
    //                delivered to remote user.
    //              - ackData (n)
    //                notifies the entity user that "n" bytes have been acked by
    //                remote user.
    //              - close();
    //                closes the entity.
    //      - Group "Sink" :
    //          It has four events:
    //              - constructor(localPort, remoteDN, remotePort, sinkBufAvail)
    //                constructs sink entity with parameters: entity's local
    //                port, remote domain name, remote port and entity user  
    //                 avail buffer size (in bytes).
    //              - readyToAccept (n)
    //                informs sink entity that its user can accept cumulative
    //                amount of data (in bytes) equals to "n".
    //              - deliverData(data)
    //                delivers "data"  to local user, such that, data is 
    //                delivered in sequence without loss or duplication.
    //              - close();
    //                closes the entity.
    //
    //  Variables :
    //  ------------
    //  srcHist        : source entity history (<=4GB).
    //  srcBufSize     : srcBuf size in bytes.
    //  srcBufUsed     : occupied portion of srcBuf in bytes.
    //  srcNumSent     : number of bytes accepted from source's local user.
    //  srcNumAcked    : number of acked bytes (at source entity).
    //  sinkBufAvail   : number of bytes that sink user can accept.
    //  sinkNumDelivered : number of bytes delivered to sink user.
    //--------------------------------------------------------------------------

    // Source entity variables.
    ByteArrayOutputStream srcHist = new ByteArrayOutputStream ();
    long srcBufSize =32 *1024 ;
    int  srcBufUsed =0 ;
    long srcNumSent =0 ;
    long srcNumAcked =0 ;

    // Sink entity variables.
    long sinkNumDelivered =0 ;
    long sinkBufAvail =32 *1024 ;


    //---------------------------------------------------------------------
    // Methods called by source side (SourceTester.java and SW_Source.java)
    //---------------------------------------------------------------------

    // Called by constructor of class SW_Source
    public void  Source(int localPort, String remoteDN, int remotePort, 
			int aBufSize) {
        srcBufSize =aBufSize ;
	srcBufSize    = aBufSize;           
	srcBufUsed    = 0;
	srcNumSent    = 0;
	srcNumAcked   = 0;
    }



    //  Sends data from source user to source entity to deliver it to 
    //  remote user.
    //  Called by SW_Source.sendData
    public void sendData(byte []data)  throws RemoteException {
        synchronized (lock){
            log.print( Thread.currentThread().getName() 
			      +":DT:sendData("+")");
            checkAssertions(true);
            if (srcBufUsed +data.length <=srcBufSize &&data.length >0 ){
                srcHist.write (data ,0 ,data.length );
                srcNumSent +=data.length ;
                srcBufUsed +=data.length ;
            }
            else System.out.println("usr sendDataSource failed ");
        }
    }

    //  Closes source entity.
    //  Called by SW_Source.closeSource
    public void closeSource()  throws RemoteException {
        synchronized (lock){
            log.print( Thread.currentThread().getName() + 
			      ":DT:closeSource("+")");
            checkAssertions(true);
        }
    }

    //  Notifies user that n bytes have been acked.
    //  Called by SourceTester.ackData
    public void ackData(int n)  throws RemoteException  {
        synchronized(lock){
            log.print(Thread.currentThread().getName() + 
			     ":DT:ackData("+ n +")");
            checkAssertions(true);
            if (srcNumAcked +n  <=srcNumSent ){
                srcBufUsed = srcBufUsed - n ;
                srcNumAcked = srcNumAcked + n ;
            }
            else System.out.println("usr ackedData failed ");
        }
    }

    //---------------------------------------------------------------------
    // Methods called by sink side (SinkTester.java and SW_Sink.java)
    //---------------------------------------------------------------------

    // Called by constructor of class SW_Sink
    public void  Sink(int localPort, String remoteDN, int remotePort, 
		      int aSinkBufAvail) {
	sinkBufAvail = aSinkBufAvail;
	sinkNumDelivered = 0;
    }


    //  Informs the entity that user can accept n more bytes of data.
    //  Called by SW_Sink.readyToAccept
    public void readyToAccept(long n)  throws RemoteException {
        synchronized (lock){
            log.print( Thread.currentThread().getName() 
			      +":DT:readyToAccept("+ n +")");
            checkAssertions(true);
	    sinkBufAvail =n ;
        }
    }


    //  Closes sink entity.
    //  Called by SW_Sink.closeSink
    public void closeSink()  throws RemoteException {
        synchronized (lock){
            log.print( Thread.currentThread().getName() + 
			      ":DT:closeSink("+")");
            checkAssertions(true);
        }
    }

    //  Delivers "data" received to entity user.
    //  Called by SinkTester.deliverData
    public void deliverData(byte []data)  throws RemoteException  {
        synchronized(lock){
            log.print(Thread.currentThread().getName() + 
			     ":DT:deliverData("+")");
            checkAssertions(true);
            if (sinkNumDelivered +data.length <=srcNumSent &&
                    data.length <=sinkBufAvail &&data.length >0 &&
                    correctData (data )){
                sinkNumDelivered =sinkNumDelivered +data.length ;
                sinkBufAvail =sinkBufAvail -data.length ;
            }
            else System.out.println("usr deliverData failed ");
        }
    }


    boolean correctData (byte []data ){
        byte srcData []=srcHist.toByteArray ();
        for (int i =0 ;i <data.length ;i ++)
            if (srcData [((int ) sinkNumDelivered)+i ]!=data [i ])
                return false ;
        return true ;
    }

    //---------------------------------------------------------------------
    // ASSERTIONS section
    //---------------------------------------------------------------------	
    void checkAssertions(boolean debugInfo){

        try{
            printLog(":");
	    if ( ( srcBufUsed >= 0 ) && ( srcBufUsed <= srcBufSize ) ) 
                printLog(":bufCondition(true)");
	    else
		printLog(":bufCondition(false)");
            printlnLog("");
        } catch (RemoteException re) {}
    }
}
