package com.subgraph.orchid.sockets;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ConnectException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.net.SocketException;
import java.net.SocketImpl;
import java.net.SocketOptions;
import java.net.SocketTimeoutException;
import java.util.concurrent.TimeoutException;

import com.subgraph.orchid.OpenFailedException;
import com.subgraph.orchid.Stream;
import com.subgraph.orchid.TorClient;

public class OrchidSocketImpl extends SocketImpl {
	
	private final TorClient torClient;
	private final Object streamLock = new Object();	

	private Stream stream;

	OrchidSocketImpl(TorClient torClient) {
		this.torClient = torClient;
	}

	public void setOption(int optID, Object value) throws SocketException {
		throw new UnsupportedOperationException();
	}

	public Object getOption(int optID) throws SocketException {
		if(optID == SocketOptions.SO_LINGER) {
			return 0;
		} else if(optID == SocketOptions.TCP_NODELAY) {
			return Boolean.TRUE;
		}
		throw new UnsupportedOperationException();
	}

	@Override
	protected void create(boolean stream) throws IOException {
		
	}

	@Override
	protected void connect(String host, int port) throws IOException {
		throw new UnsupportedOperationException();
	}

	@Override
	protected void connect(InetAddress address, int port) throws IOException {
		throw new UnsupportedOperationException();
		
	}

	@Override
	protected void connect(SocketAddress address, int timeout)
			throws IOException {
		if(!(address instanceof InetSocketAddress)) {
			throw new IllegalArgumentException("Unsupported address type");
		}
		final InetSocketAddress inetAddress = (InetSocketAddress) address;
		
		doConnect(addressToName(inetAddress), inetAddress.getPort());
	}
	
	private String addressToName(InetSocketAddress address) {
		if(address.getAddress() != null) {
			return address.getAddress().getHostAddress();
		} else {
			return address.getHostName();
		}
	}

	private void doConnect(String host, int port) throws IOException {
		synchronized(streamLock) {
			if(stream != null) {
				throw new SocketException("Already connected");
			}
			try {
				stream = torClient.openExitStreamTo(host, port);
			} catch (InterruptedException e) {
				Thread.currentThread().interrupt();
				throw new SocketException("connect() interrupted");
			} catch (TimeoutException e) {
				throw new SocketTimeoutException();
			} catch (OpenFailedException e) {
				throw new ConnectException(e.getMessage());
			}
		}
	}

	@Override
	protected void bind(InetAddress host, int port) throws IOException {
		throw new UnsupportedOperationException();
	}

	@Override
	protected void listen(int backlog) throws IOException {
		throw new UnsupportedOperationException();
	}

	@Override
	protected void accept(SocketImpl s) throws IOException {
		throw new UnsupportedOperationException();
	}

	@Override
	protected InputStream getInputStream() throws IOException {
		synchronized (streamLock) {
			if(stream == null) {
				throw new IOException("Not connected");
			}
			return stream.getInputStream();
		}
	}

	@Override
	protected OutputStream getOutputStream() throws IOException {
		synchronized (streamLock) {
			if(stream == null) {
				throw new IOException("Not connected");
			}
			return stream.getOutputStream();
		}
	}

	@Override
	protected int available() throws IOException {
		synchronized(streamLock) {
			if(stream == null) {
				throw new IOException("Not connected");
			}
			return stream.getInputStream().available();
		}
	}

	@Override
	protected void close() throws IOException {
		synchronized (streamLock) {
			stream.close();
			stream = null;
		}
	}

	@Override
	protected void sendUrgentData(int data) throws IOException {
		throw new UnsupportedOperationException();
	}
	
	 protected void shutdownInput() throws IOException {
      //throw new IOException("Method not implemented!");
    }
	 
	 protected void shutdownOutput() throws IOException {
      //throw new IOException("Method not implemented!");
    }
}

@@ -31,16 +31,47 @@
 ckets;%0A%0A
+import java.io.FileDescriptor;%0A
 import j
@@ -741,16 +741,17 @@
 stream;%0A
+%09
 %0A%09Orchid
@@ -814,16 +814,50 @@
 Client;%0A
+%09%09this.fd = new FileDescriptor();%0A
 %09%7D%0A%0A%09pub
@@ -1170,53 +1170,94 @@
 %0A%09%09%7D
-%0A%09%09throw new UnsupportedOperationException();
+ else if(optID == SocketOptions.SO_TIMEOUT) %7B%0A%09%09%09return 0;%0A%09%09%7D else %7B%0A%09%09%09return 0;%0A%09%09%7D
 %0A%09%7D%0A
@@ -3806,16 +3806,41 @@
 Lock) %7B%0A
+%09%09%09if(stream != null) %7B%0A%09
 %09%09%09strea
@@ -3850,16 +3850,17 @@
 lose();%0A
+%09
 %09%09%09strea
@@ -3869,16 +3869,21 @@
 = null;%0A
+%09%09%09%7D%0A
 %09%09%7D%0A%09%7D%0A%0A

Make more robust by not necessarily blowing up upon access to unsupported socket option, by providing a dummy FileDescriptor, and by only closing underlying stream one time.