/**
 * 
 */
package com.trendrr.beanstalk;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.SocketException;
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousCloseException;
import java.nio.channels.ClosedByInterruptException;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.NotYetConnectedException;
import java.nio.channels.SocketChannel;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 * 
 * Wraps the beanstalk connection.  
 * 
 * 
 * @author dustin
 *
 */
public class BeanstalkConnection {

	protected Log log = LogFactory.getLog(BeanstalkConnection.class);
	
	private SocketChannel channel;
	private ByteArrayOutputStream outbuf = new ByteArrayOutputStream();
	
	public void connect(String addr, int port) throws BeanstalkException {
		try {
			this.channel = SocketChannel.open();
			this.channel.connect(new InetSocketAddress(addr, port));
		} catch (Exception x) {
			throw new BeanstalkException(x);
		}
	}
	
	public void close() {	
		try {
			outbuf.close();
		} catch (Exception x) {
			log.info("Caught", x);
		}
		
		try {
			channel.close();
		} catch (Exception x) {
			log.info("Caught", x);
		}
	}

	public boolean isOpen() {
		return channel.isOpen();
	}
	
	public void write(String str) throws BeanstalkDisconnectedException, BeanstalkException{
		try {
			ByteBuffer buf = ByteBuffer.wrap(str.getBytes());
			while(buf.hasRemaining()) {
				channel.write(buf);
			}
		} catch (Exception x) {
			this.throwException(x);
		}
	}
	
	private void throwException(Exception x) throws BeanstalkDisconnectedException, BeanstalkException{
		if (x instanceof NotYetConnectedException) {
			throw new BeanstalkDisconnectedException(x);
		}
		if (x instanceof ClosedChannelException) {
			throw new BeanstalkDisconnectedException(x);
		}
		if (x instanceof SocketException) {
			throw new BeanstalkDisconnectedException(x);
		}
		if (x instanceof AsynchronousCloseException) {
			throw new BeanstalkDisconnectedException(x);
		}
		if (x instanceof ClosedByInterruptException) {
			throw new BeanstalkDisconnectedException(x);
		}
		
		
		throw new BeanstalkException(x);
	}
	
	public void write(byte[] bytes) throws BeanstalkDisconnectedException, BeanstalkException{
		try {
			ByteBuffer buf = ByteBuffer.wrap(bytes);
			while(buf.hasRemaining()) {
				channel.write(buf);
			}
		} catch (Exception x) {
			this.throwException(x);
		}
	}
	
	/**
	 * returns the control response.  ends with \r\n
	 * @return
	 */
	public String readControlResponse() throws BeanstalkDisconnectedException, BeanstalkException{
		//clear the old out buffer
		String response = null;
		int count = 0;
		while (response == null) {
			count++;
			
			outbuf = new ByteArrayOutputStream();
			ByteBuffer buf = ByteBuffer.allocate(4096);
			if (count > 10000) {
				throw new BeanstalkException("OH Snap, nothing to read from the buffer for 100 seconds!");
			}
			
			try {
				if (channel.read(buf) == 0) {
					log.warn("Nothing in the buffer, sleeping for 100 millis, will try again");
					try {
						Thread.sleep(100);
					} catch (Exception x) {
						log.error("CuaghT", x);
					}
					continue;
				}
			} catch (Exception x) {
				this.throwException(x);
			}
			
			byte[] bytes = buf.array();
					
			ByteArrayOutputStream stringBuf = new ByteArrayOutputStream();
			byte lastByte = ' ';
	
			for (int i=0 ; i < buf.position(); i++) {
				byte curByte = bytes[i];
	
				if (lastByte == '\r' && curByte == '\n' && response == null) {
					response =  new String(stringBuf.toByteArray()).trim();
					if (response.isEmpty()) {
						log.warn("Errant line end found, possibly from the previous request. skipping");
						response = null;
					}
					continue;
				}
				
				if (response == null) {
					stringBuf.write(curByte);
				} else {
					outbuf.write(curByte);
				}
				lastByte = curByte;
			}
	//		log.info("OUTBUF: " + outbuf.size() + " :" + new String(outbuf.toByteArray()));
		}
		return response;
	}
	
	public byte[] readBytes(int numBytes) throws BeanstalkDisconnectedException, BeanstalkException{
		
		byte[] bytes = new byte[numBytes];

		byte[] array = this.outbuf.toByteArray();
		this.outbuf = new ByteArrayOutputStream();
		
		int bytesWritten = 0;
		//first read any bytes that are already in the outbuffer.
		for (int i=0; i < array.length; i++) {
			if (bytesWritten < bytes.length) {
				bytes[i] = array[i];
				bytesWritten++;
			} else {
				this.outbuf.write(array[i]);
			}
			
		}
//		log.info("GOT 468: " + bytesWritten + " " + bytes.length);
		if (bytesWritten >= bytes.length) {
			return bytes;
		}
		
		int numRead = 1;
		while(numRead >0) {
			//then read the bytes waiting in the channel.
			ByteBuffer buf = ByteBuffer.allocate(4096);
			try {
				numRead = channel.read(buf);
			} catch (Exception x) {
				this.throwException(x);
			}
			
			byte[] read = buf.array();
			for (int i=0 ; i<numRead; i++) {
				if (bytesWritten < bytes.length) {
					bytes[bytesWritten] = read[i];
				} else {
					this.outbuf.write(read[i]);
				}
				bytesWritten++;
			}
			if (bytesWritten >= bytes.length) {
				log.info("468 GOT : " + bytesWritten + " " + bytes.length);
				return bytes;
			}
		}
//		log.info("GOT : " + bytesWritten + " " + bytes.length);
		return bytes;
	}
	
	@Override
	public void finalize() {
		this.close();//just for safety.  
	}
}

@@ -79,36 +79,8 @@
 am;%0A
-import java.io.IOException;%0A
 impo
@@ -1274,16 +1274,35 @@
 %09%09return
+ channel != null &&
  channel

Check if BeanstalkConnection.channel is null before calling isOpen