import java.io.*;
import java.net.*;

public class LoggerProxyServer {

    private static final int UDP_LISTEN_PORT = 58328;
    private static final int UDP_SENDTO_PORT = 58329;
    public static LoggerProxyServer theApp = null;
    public static void main(String[] args) throws IOException {

	String host = "chuck-wright.com";
	int remoteport = 80;
	int localport = 8080;

	if(args.length > 3 || (args.length == 1 && args[0].equals("-help"))){
	    System.out.println("usage: java LoggerProxyServer <localport> <<remoteport> <<host_ip>>>");
	}

	switch(args.length){
	case 3:
	    host = args[2];
	case 2:
	    try{
		remoteport = Integer.parseInt(args[1]);
	    }catch(NumberFormatException e){
		System.out.println("Bad remote port: " + args[1]);
	    }
	case 1:
	    try{
		localport = Integer.parseInt(args[0]);
	    }catch(NumberFormatException e){
		System.out.println("Bad local port: " + args[0]);
	    }
	default:
	}

	theApp = new LoggerProxyServer();

	System.out.println("Starting proxy for " + host + ":" + remoteport
			       + " on port " + localport);
	    // And start running the server
	theApp.runServer(host, remoteport, localport); // never returns
    }
    
    /**
     * runs a single-threaded proxy server on
     * the specified local port. It never returns.
     */
    public void runServer(String host, int remoteport, int localport)
	throws IOException {

	UDPMon udpmon = new UDPMon();
	udpmon.start();

	// Create a ServerSocket to listen for connections with
	ServerSocket ss = new ServerSocket(localport);
	
	while (true) {
	    Socket client = null;
	    try {
		// Wait for a connection on the local port
		client = ss.accept();
		proxyThread pt = new proxyThread(client, host, remoteport);
		pt.start();
	    } catch (IOException e) {
		System.err.println(e);
	    }
	}
    }

    class proxyThread extends Thread{
	Socket client;
	String host;
	int remoteport;
	byte[] request = new byte[1024];
	byte[] reply = new byte[4096];

	public proxyThread(Socket client, String host, int remoteport){
	    System.out.println("New proxy thread to " + host + ":" + remoteport);
	    this.client = client;
	    this.host = host;
	    this.remoteport = remoteport;
	}

	public void run(){
	    Socket server = null;
	    try{
		final InputStream streamFromClient = client.getInputStream();
		final OutputStream streamToClient = client.getOutputStream();
		
		// Make a connection to the real server.
		// If we cannot connect to the server, send an error to the
		// client, disconnect, and continue waiting for connections.
		try {
		    server = new Socket(host, remoteport);
		} catch (IOException e) {  
		    PrintWriter out = new PrintWriter(streamToClient);
		    out.print("Proxy server cannot connect to " + host + ":"
			      + remoteport + ":\n" + e + "\n");
		    out.flush();
		    client.close();
		    return;
		}
		// Get server streams.
		final InputStream streamFromServer = server.getInputStream();
		final OutputStream streamToServer = server.getOutputStream();
		
		// a thread to read the client's requests and pass them
		// to the server. A separate thread for asynchronous.
		Thread t = new Thread() {
			public void run() {
			    int bytesRead;
			    try {
				while ((bytesRead = streamFromClient.read(request)) != -1) {
				    streamToServer.write(request, 0, bytesRead);
				    streamToServer.flush();
				}
			    } catch (IOException e) {
			    }
			    
				// the client closed the connection to us, so close our
				// connection to the server.
			    try {
				streamToServer.close();
			    } catch (IOException e) {
			    }
			}
		    };
		
		// Start the client-to-server request thread running
		t.start();
		
		// Read the server's responses
		// and pass them back to the client.
		int bytesRead;
		try {
		    while ((bytesRead = streamFromServer.read(reply)) != -1) {
			streamToClient.write(reply, 0, bytesRead);
			streamToClient.flush();
		    }
		} catch (IOException e) {
		}
		
		// The server closed its connection to us, so we close our
		// connection to our client.
		streamToClient.close();
	    } catch (IOException e) {
		System.err.println(e);
	    } finally {
		try {
		    if (client != null)
			client.close();
		    if (server != null)
			server.close();
		} catch (IOException e) {
		}
	    }
	    //System.out.println("finished");
	}
    }

    /** 
     *
     */

    class UDPMon extends Thread{
	private DatagramSocket dgsrcv; 
	private DatagramSocket dgsxmit; 
	private boolean keepalive = true;

	public void run(){
	    System.out.println("Starting UDPmon");
	    try{
		dgsrcv = new DatagramSocket(UDP_LISTEN_PORT);
		dgsxmit = new DatagramSocket();
	    }catch(Exception e){
		System.out.println(e);
		return;
	    }

	    System.out.println("opened UDP socket");
	    byte[] buf = new byte[1024];
	    while(keepalive){
		try{
		    DatagramPacket dgpin = new DatagramPacket(buf, buf.length);

		    // Wait for packet...
		    System.out.println("waiting for UDP packet");
		    dgsrcv.receive(dgpin);
		    System.out.println("received UDP packet");

		    String rcvd = new String(buf, 0, dgpin.getLength());
		    System.out.println(rcvd);

		    System.out.println("Forwarding UDP packet");

		    InetAddress toadr = InetAddress.getLocalHost();
		    DatagramPacket dgpout = new DatagramPacket(buf, dgpin.getLength(), toadr, UDP_SENDTO_PORT);
		    dgsxmit.send(dgpout);

		}catch(Exception e){
		    System.out.println(e);
		    keepalive = false;
		}
	    }// end while(keepalive)
	    if(dgsxmit != null) {
		dgsxmit.close();
		dgsxmit = null;
	    }
	    if(dgsrcv != null) {
		dgsrcv.close();
		dgsrcv = null;
	    }
	    System.out.println("BYE!!");
	}
    }
}


