/*
    starter file for the BLAST client, java version
*/
//import java.lang.*;     //deprecated
import java.net.*;      //pld
import java.io.*;
import java.security.*;	// for MD5
//import java.io.Externalizable;

public class bclient {

    // The following is useful if you run this from a dynamic console.
    // But don't do that.
/*    static public void winwait() {
        byte ch[] = new byte[1];
        try {
            System.in.read(ch);
        } catch (IOException ioe) {
            return;
        }
    }
/* */

    static blastpkt bp = new blastpkt();    // stupid inner-class nonsense

    static public String byte2string (byte b) {
    	int high = (b >>4) & 0x0F;
    	int low = (b & 0x0F);
    	final String convert = "0123456789abcdef";
    	String result = "";
    	result += convert.charAt(high);
    	result += convert.charAt(low);
    	return result;
    }
    
    static public String byte2string(byte[] b) {
        String result = "";
        for (int i = 0; i < b.length; i++) {
            result += byte2string(b[i]);
        }
        return result;
    }
    
    //============================================================
    //============================================================

    static public void main(String args[]) {
		String USAGE = "bclient [messagenum [host [port]]]";
		int mesgnum = 0;
        int destport = blastpkt.SERVERPORT;
        String desthost = "ulam2.cs.luc.edu";
        desthost = "localhost";

        if (args.length > 0)
        	mesgnum = (new Integer(args[0])).intValue();
       	if (args.length > 1)
       	    desthost = args[1];
       	if (args.length > 2)
       	    destport = (new Integer(args[2])).intValue();
       	if (args.length > 3) {
			System.err.println(USAGE);
			return;
		}

        DatagramSocket s;
        try {
            s = new DatagramSocket();
        }
        catch (SocketException se) {
            System.err.println("no socket available");
            return;
        }

        try {
            s.setSoTimeout(blastpkt.DONE);       // time in milliseconds; DONE is 5 sec, TOO LONG
        } catch (SocketException se) {
            System.err.println("socket exception: timeout not set!");
        }

        InetAddress dest;
        System.err.print("Looking up address of " + desthost + "...");
        try {
            dest = InetAddress.getByName(desthost);
        }
        catch (UnknownHostException uhe) {
            System.err.println("unknown host: " + desthost);
            return;
        }
        System.err.println(" got it!");

        blastpkt.request req = bp.new request(mesgnum); // ctor for REQ

        System.err.println("req size = " + req.size() + "; message number = " + mesgnum);

        DatagramPacket reqDG
            = new DatagramPacket(req.write(), req.size(), dest, destport);

        try {s.send(reqDG);}
        catch (IOException ioe) {
            System.err.println("send() failed");
            return;
        }

	MessageDigest md;
	try {
		md = MessageDigest.getInstance("MD5");
	} catch (NoSuchAlgorithmException nsae) {
		md=null;
		System.err.println("MD5 not available");
	}
	
	// if you have a buffer "byte[] buf", include it in the checksum with:
	// md.update(buf) (or, if appropriate, md.update(buf, offset, len))
	
	// the following would go at the END of your processing the fragments.
	// byte[] md5result = md.digest();
	
        //============================================================

        // now receive the response
		blastpkt.data  bdata = bp.new data();		// blastpkt.data format

        DatagramPacket blastDG            // DatagramPacket format, for RECEIVING
            = new DatagramPacket(new byte[blastpkt.MAXSIZE] , blastpkt.MAXSIZE);
            
        DatagramPacket ssrDG		// SSR, for SENDING by client
            = new DatagramPacket(new byte[blastpkt.BHEADSIZE], blastpkt.BHEADSIZE);

        long starttime = System.currentTimeMillis();

	while (true) {
		try {
			s.receive(blastDG);
		}
		catch (InterruptedIOException iioe) {
			System.err.println("Response timed out!");
			break; 		// you want to RESPOND to timeouts, but not here!
		}
		catch (IOException ioe) {
			System.err.println("receive() failed");
			return;
		}

		//byte[] replybuf = blastDG.getData();	// the actual packet rec'd in byte[] format

		bdata.read(blastDG.getData(), blastDG.getLength());	// now transfer into blastpkt format

		System.err.print("received packet of length = " + blastDG.getLength());
		System.err.print("; port =");
		System.err.print(blastDG.getPort());
		System.err.print("; host=");
		System.err.print(blastDG.getAddress().toString());
		System.err.print("; time = ");
		System.err.print(System.currentTimeMillis()-starttime);
		System.err.print("; data size = ");
		System.err.print(bdata.Data().length);
		System.err.print("; fragment position = " + blastpkt.fragpos(bdata.FragMask()));
		System.err.println();

		// we *should* do some sanity checks on the packet just received!!

		md.update(bdata.Data());
		
		if (mesgnum == 0) {
			byte[] data = bdata.Data();
			int msgcount = (data[0]<<24) + (data[1]<<16) + (data[2]<<8) + data[3];  // lazy convert
			System.err.println("Message 0 indicates there are " + msgcount + " more messages");
		}

		// now send SSR. If mesgnum == 0, we don't have to wait for more frags
		if (mesgnum == 0) {
            		blastpkt.ssr ssr = bp.new ssr(
				bdata.ProtNum(),
				bdata.MID(),
				blastpkt.BHEADSIZE,		// Length
				(short) 1,				// NumFrags
				blastpkt.TYPE_SSR,
				blastpkt.maskmaker(1)	// mask for a single data packet
			); // ctor for REQ

			ssrDG.setAddress(dest);
			ssrDG.setPort(blastDG.getPort());
			ssrDG.setData(ssr.write());
			ssrDG.setLength(ssr.size());

			try {s.send(ssrDG);}
			catch (IOException ioe) {
				System.err.println("ssr send() failed");
				return;
			}
		}
		
		break;

	} // end of while
	
	byte[] md5result = md.digest();
	System.out.println("md5 checksum: " + byte2string(md5result));
	
        //============================================================

    }
}
