package org.torproject.jtor.socks.impl;

import java.io.Closeable;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;

import org.torproject.jtor.Logger;
import org.torproject.jtor.circuits.Stream;

public class SocksStreamConnection {
	
	public static void runConnection(Socket socket, Stream stream, Logger logger) {
		SocksStreamConnection ssc = new SocksStreamConnection(socket, stream, logger);
		ssc.run();
	}
	private final static int TRANSFER_BUFFER_SIZE = 1024;
	private final InputStream torInputStream;
	private final OutputStream torOutputStream;
	private final Socket socket;
	private final Logger logger;
	private final Thread incomingThread;
	private final Thread outgoingThread;
	private final Object lock = new Object();
	private volatile boolean outgoingClosed;
	private volatile boolean incomingClosed;

	private SocksStreamConnection(Socket socket, Stream stream, Logger logger) {
		this.socket = socket;
		torInputStream = stream.getInputStream();
		torOutputStream = stream.getOutputStream();
		
		this.logger = logger;
		incomingThread = createIncomingThread();
		outgoingThread = createOutgoingThread();
	}

	private void run() {
		incomingThread.start();
		outgoingThread.start();
		synchronized(lock) {
			while(!(outgoingClosed && incomingClosed)) {
				try {
					lock.wait();
				} catch (InterruptedException e) {
					Thread.currentThread().interrupt();
					return;
				}
			}
			
			try {
				socket.close();
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			closeStream(torInputStream);
			closeStream(torOutputStream);
			
		}
	}

	private Thread createIncomingThread() {
		return new Thread(new Runnable() { public void run() {
			try {
				incomingTransferLoop();
			} catch (IOException e) {
				logger.warn("System error on incoming stream IO : "+ e.getMessage());
			} finally {
				synchronized(lock) {
					incomingClosed = true;
					lock.notifyAll();
				}
			}
		}});
	}

	private Thread createOutgoingThread() {
		return new Thread(new Runnable() { public void run() {
			try {
				outgoingTransferLoop();
			} catch (IOException e) {
				e.printStackTrace();
				logger.warn("System error on outgoing stream IO : "+ e.getMessage());
			} finally {
				synchronized(lock) {
					outgoingClosed = true;
					lock.notifyAll();
				}
			}
		}});
	}

	private void incomingTransferLoop() throws IOException {
		final byte[] incomingBuffer = new byte[TRANSFER_BUFFER_SIZE];
		while(true) {
			final int n = torInputStream.read(incomingBuffer);
			if(n == -1) {
				logger.debug("EOF on TOR input stream "+ torInputStream);
				socket.shutdownOutput();
				return;
			} else if(n > 0) {
				logger.debug("Transferring "+ n +" bytes from "+ torInputStream +" to SOCKS socket");
				if(!socket.isOutputShutdown()) {
					socket.getOutputStream().write(incomingBuffer, 0, n);
					socket.getOutputStream().flush();
				} else {
					closeStream(torInputStream);
					return;
				}
			}
		}
	}

	private void outgoingTransferLoop() throws IOException {
		final byte[] outgoingBuffer = new byte[TRANSFER_BUFFER_SIZE];
		while(true) {
			final int n = socket.getInputStream().read(outgoingBuffer);
			if(n == -1) {
				logger.debug("EOF on SOCKS socket connected to "+ torOutputStream);
				return;
			} else if(n > 0) {
				logger.debug("Transferring "+ n +" bytes from SOCKS socket to "+ torOutputStream);
				torOutputStream.write(outgoingBuffer, 0, n);
				torOutputStream.flush();
			}
		}
	}

	private void closeStream(Closeable c) {
		try {
			c.close();
		} catch (IOException e) {
			logger.warn("Close failed on "+ c + " : "+ e.getMessage());
		}	
	}
}

@@ -520,12 +520,12 @@
 E = 
-1024
+4096
 ;%0A%09p

Increase transfer buffer size from 1k to 4k.