package mundosk_libraries.java_websocket;

import mundosk_libraries.java_websocket.drafts.*;
import mundosk_libraries.java_websocket.handshake.*;
import mundosk_libraries.java_websocket.util.Charsetfunctions;
import mundosk_libraries.java_websocket.drafts.Draft.CloseHandshakeType;
import mundosk_libraries.java_websocket.drafts.Draft.HandshakeState;
import mundosk_libraries.java_websocket.exceptions.IncompleteHandshakeException;
import mundosk_libraries.java_websocket.exceptions.InvalidDataException;
import mundosk_libraries.java_websocket.exceptions.InvalidHandshakeException;
import mundosk_libraries.java_websocket.exceptions.WebsocketNotConnectedException;
import mundosk_libraries.java_websocket.framing.CloseFrame;
import mundosk_libraries.java_websocket.framing.CloseFrameBuilder;
import mundosk_libraries.java_websocket.framing.Framedata;
import mundosk_libraries.java_websocket.framing.Framedata.Opcode;
import mundosk_libraries.java_websocket.server.WebSocketServer.WebSocketWorker;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.nio.ByteBuffer;
import java.nio.channels.ByteChannel;
import java.nio.channels.NotYetConnectedException;
import java.nio.channels.SelectionKey;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;

/**
 * Represents one end (client or server) of a single WebSocketImpl connection.
 * Takes care of the "request" phase, then allows for easy sending of
 * text frames, and receiving frames through an event-based model.
 */
public class WebSocketImpl implements WebSocket {

	public static final List<Draft> defaultdraftlist = new ArrayList<Draft>( 4 );
	public static int RCVBUF = 16384;
	public static/*final*/ boolean DEBUG = false; // must be final in the future in order to take advantage of VM optimization

	static {
		defaultdraftlist.add( new Draft_17() );
		defaultdraftlist.add( new Draft_10() );
		defaultdraftlist.add( new Draft_76() );
		defaultdraftlist.add( new Draft_75() );
	}

	/**
	 * Queue of buffers that need to be sent to the client.
	 */
	public final BlockingQueue<ByteBuffer> outQueue;
	/**
	 * Queue of buffers that need to be processed
	 */
	public final BlockingQueue<ByteBuffer> inQueue;
	/**
	 * The listener to notify of WebSocket events.
	 */
	private final WebSocketListener wsl;
	public SelectionKey key;
	/**
	 * the possibly wrapped channel object whose selection is controlled by {@link #key}
	 */
	public ByteChannel channel;
	/**
	 * Helper variable meant to store the thread which ( exclusively ) triggers this objects decode method.
	 **/
	public volatile WebSocketWorker workerThread; // TODO reset worker?
	/**
	 * When true no further frames may be submitted to be sent
	 */
	private volatile boolean flushandclosestate = false;
	private READYSTATE readystate = READYSTATE.NOT_YET_CONNECTED;
	private List<Draft> knownDrafts;

	private Draft draft = null;

	private Role role;

	private Framedata current_continuous_frame = null;

	/**
	 * the bytes of an incomplete received request
	 */
	private ByteBuffer tmpHandshakeBytes = ByteBuffer.allocate( 0 );

	/**
	 * stores the request sent by this websocket ( Role.CLIENT only )
	 */
	private ClientHandshake handshakerequest = null;

	private String closemessage = null;
	private Integer closecode = null;
	private Boolean closedremotely = null;

	private String resourceDescriptor = null;

	/**
	 * Creates a websocket with server role
	 *
	 * @param listener The listener for this instance
	 * @param drafts The drafts which should be used
	 */
	public WebSocketImpl( WebSocketListener listener, List<Draft> drafts ) {
		this( listener, ( Draft ) null );
		this.role = Role.SERVER;
		// draft.copyInstance will be called when the draft is first needed
		if( drafts == null || drafts.isEmpty() ) {
			knownDrafts = defaultdraftlist;
		} else {
			knownDrafts = drafts;
		}
	}

	/**
	 * creates a websocket with client role
	 *
	 * @param listener The listener for this instance
	 * @param draft The draft which should be used
	 */
	public WebSocketImpl( WebSocketListener listener, Draft draft ) {
		if( listener == null || ( draft == null && role == Role.SERVER ) )// socket can be null because we want do be able to create the object without already having a bound channel
			throw new IllegalArgumentException( "parameters must not be null" );
		this.outQueue = new LinkedBlockingQueue<ByteBuffer>();
		inQueue = new LinkedBlockingQueue<ByteBuffer>();
		this.wsl = listener;
		this.role = Role.CLIENT;
		if( draft != null )
			this.draft = draft.copyInstance();
	}

	@Deprecated
	public WebSocketImpl( WebSocketListener listener, Draft draft, Socket socket ) {
		this( listener, draft );
	}

	@Deprecated
	public WebSocketImpl( WebSocketListener listener, List<Draft> drafts, Socket socket ) {
		this( listener, drafts );
	}

	/**
	 * Method to decode the provided ByteBuffer
	 * @param socketBuffer the ByteBuffer to decode
	 */
	public void decode( ByteBuffer socketBuffer ) {
		assert ( socketBuffer.hasRemaining() );

		if( DEBUG )
			System.out.println( "process(" + socketBuffer.remaining() + "): {" + ( socketBuffer.remaining() > 1000 ? "too big to display" : new String( socketBuffer.array(), socketBuffer.position(), socketBuffer.remaining() ) ) + "}" );

		if( readystate != READYSTATE.NOT_YET_CONNECTED ) {
			if ( readystate == READYSTATE.OPEN ) {
				decodeFrames( socketBuffer );
			}
		} else {
			if( decodeHandshake( socketBuffer ) ) {
				assert ( tmpHandshakeBytes.hasRemaining() != socketBuffer.hasRemaining() || !socketBuffer.hasRemaining() ); // the buffers will never have remaining bytes at the same time

				if( socketBuffer.hasRemaining() ) {
					decodeFrames( socketBuffer );
				} else if( tmpHandshakeBytes.hasRemaining() ) {
					decodeFrames( tmpHandshakeBytes );
				}
			}
		}
		assert ( isClosing() || isFlushAndClose() || !socketBuffer.hasRemaining() );
	}

	/**
	 * Returns whether the request phase has is completed.
	 * In case of a broken request this will be never the case.
	 **/
	private boolean decodeHandshake( ByteBuffer socketBufferNew ) {
		ByteBuffer socketBuffer;
		if( tmpHandshakeBytes.capacity() == 0 ) {
			socketBuffer = socketBufferNew;
		} else {
			if( tmpHandshakeBytes.remaining() < socketBufferNew.remaining() ) {
				ByteBuffer buf = ByteBuffer.allocate( tmpHandshakeBytes.capacity() + socketBufferNew.remaining() );
				tmpHandshakeBytes.flip();
				buf.put( tmpHandshakeBytes );
				tmpHandshakeBytes = buf;
			}

			tmpHandshakeBytes.put( socketBufferNew );
			tmpHandshakeBytes.flip();
			socketBuffer = tmpHandshakeBytes;
		}
		socketBuffer.mark();
		try {
			if( draft == null ) {
				HandshakeState isflashedgecase = isFlashEdgeCase( socketBuffer );
				if( isflashedgecase == HandshakeState.MATCHED ) {
					try {
						write( ByteBuffer.wrap( Charsetfunctions.utf8Bytes( wsl.getFlashPolicy( this ) ) ) );
						close( CloseFrame.FLASHPOLICY, "" );
					} catch ( InvalidDataException e ) {
						close( CloseFrame.ABNORMAL_CLOSE, "remote peer closed connection before flashpolicy could be transmitted", true );
					}
					return false;
				}
			}
			HandshakeState handshakestate;

			try {
				if( role == Role.SERVER ) {
					if( draft == null ) {
						for( Draft d : knownDrafts ) {
							d = d.copyInstance();
							try {
								d.setParseMode( role );
								socketBuffer.reset();
								Handshakedata tmphandshake = d.translateHandshake( socketBuffer );
								if( !( tmphandshake instanceof ClientHandshake ) ) {
									flushAndClose( CloseFrame.PROTOCOL_ERROR, "wrong http function", false );
									return false;
								}
								ClientHandshake handshake = ( ClientHandshake ) tmphandshake;
								handshakestate = d.acceptHandshakeAsServer( handshake );
								if( handshakestate == HandshakeState.MATCHED ) {
									resourceDescriptor = handshake.getResourceDescriptor();
									ServerHandshakeBuilder response;
									try {
										response = wsl.onWebsocketHandshakeReceivedAsServer( this, d, handshake );
									} catch ( InvalidDataException e ) {
										flushAndClose( e.getCloseCode(), e.getMessage(), false );
										return false;
									} catch ( RuntimeException e ) {
										wsl.onWebsocketError( this, e );
										flushAndClose( CloseFrame.NEVER_CONNECTED, e.getMessage(), false );
										return false;
									}
									write( d.createHandshake( d.postProcessHandshakeResponseAsServer( handshake, response ), role ) );
									draft = d;
									open( handshake );
									return true;
								}
							} catch ( InvalidHandshakeException e ) {
								// go on with an other draft
							}
						}
						if( draft == null ) {
							close( CloseFrame.PROTOCOL_ERROR, "no draft matches" );
						}
						return false;
					} else {
						// special case for multiple step handshakes
						Handshakedata tmphandshake = draft.translateHandshake( socketBuffer );
						if( !( tmphandshake instanceof ClientHandshake ) ) {
							flushAndClose( CloseFrame.PROTOCOL_ERROR, "wrong http function", false );
							return false;
						}
						ClientHandshake handshake = ( ClientHandshake ) tmphandshake;
						handshakestate = draft.acceptHandshakeAsServer( handshake );

						if( handshakestate == HandshakeState.MATCHED ) {
							open( handshake );
							return true;
						} else {
							close( CloseFrame.PROTOCOL_ERROR, "the request did finaly not match" );
						}
						return false;
					}
				} else if( role == Role.CLIENT ) {
					draft.setParseMode( role );
					Handshakedata tmphandshake = draft.translateHandshake( socketBuffer );
					if( !( tmphandshake instanceof ServerHandshake) ) {
						flushAndClose( CloseFrame.PROTOCOL_ERROR, "wrong http function", false );
						return false;
					}
					ServerHandshake handshake = ( ServerHandshake ) tmphandshake;
					handshakestate = draft.acceptHandshakeAsClient( handshakerequest, handshake );
					if( handshakestate == HandshakeState.MATCHED ) {
						try {
							wsl.onWebsocketHandshakeReceivedAsClient( this, handshakerequest, handshake );
						} catch ( InvalidDataException e ) {
							flushAndClose( e.getCloseCode(), e.getMessage(), false );
							return false;
						} catch ( RuntimeException e ) {
							wsl.onWebsocketError( this, e );
							flushAndClose( CloseFrame.NEVER_CONNECTED, e.getMessage(), false );
							return false;
						}
						open( handshake );
						return true;
					} else {
						close( CloseFrame.PROTOCOL_ERROR, "draft " + draft + " refuses request" );
					}
				}
			} catch ( InvalidHandshakeException e ) {
				close( e );
			}
		} catch ( IncompleteHandshakeException e ) {
			if( tmpHandshakeBytes.capacity() == 0 ) {
				socketBuffer.reset();
				int newsize = e.getPreferedSize();
				if( newsize == 0 ) {
					newsize = socketBuffer.capacity() + 16;
				} else {
					assert ( e.getPreferedSize() >= socketBuffer.remaining() );
				}
				tmpHandshakeBytes = ByteBuffer.allocate( newsize );

				tmpHandshakeBytes.put( socketBufferNew );
				// tmpHandshakeBytes.flip();
			} else {
				tmpHandshakeBytes.position( tmpHandshakeBytes.limit() );
				tmpHandshakeBytes.limit( tmpHandshakeBytes.capacity() );
			}
		}
		return false;
	}

	private void decodeFrames( ByteBuffer socketBuffer ) {

		List<Framedata> frames;
		try {
			frames = draft.translateFrame( socketBuffer );
			for( Framedata f : frames ) {
				if( DEBUG )
					System.out.println( "matched frame: " + f );
				Opcode curop = f.getOpcode();
				boolean fin = f.isFin();
				//Not evaluating any further frames if the connection is in READYSTATE CLOSE
				if( readystate == READYSTATE.CLOSING )
					return;

				if( curop == Opcode.CLOSING ) {
					int code = CloseFrame.NOCODE;
					String reason = "";
					if( f instanceof CloseFrame ) {
						CloseFrame cf = ( CloseFrame ) f;
						code = cf.getCloseCode();
						reason = cf.getMessage();
					}
					if( readystate == READYSTATE.CLOSING ) {
						// complete the close request by disconnecting
						closeConnection( code, reason, true );
					} else {
						// echo close request
						if( draft.getCloseHandshakeType() == CloseHandshakeType.TWOWAY )
							close( code, reason, true );
						else
							flushAndClose( code, reason, false );
					}
					continue;
				} else if( curop == Opcode.PING ) {
					wsl.onWebsocketPing( this, f );
					continue;
				} else if( curop == Opcode.PONG ) {
					wsl.onWebsocketPong( this, f );
					continue;
				} else if( !fin || curop == Opcode.CONTINUOUS ) {
					if( curop != Opcode.CONTINUOUS ) {
						if( current_continuous_frame != null )
							throw new InvalidDataException( CloseFrame.PROTOCOL_ERROR, "Previous continuous frame sequence not completed." );
						current_continuous_frame = f;
					} else if( fin ) {
						if( current_continuous_frame == null )
							throw new InvalidDataException( CloseFrame.PROTOCOL_ERROR, "Continuous frame sequence was not started." );
						//Check if the whole payload is valid utf8, when the opcode indicates a text
						if( current_continuous_frame.getOpcode() == Opcode.TEXT ) {
							//Checking a bit more from the frame before this one just to make sure all the code points are correct
							int off = Math.max( current_continuous_frame.getPayloadData().limit() - 64, 0 );
							current_continuous_frame.append( f );
							if( !Charsetfunctions.isValidUTF8( current_continuous_frame.getPayloadData(), off ) ) {
								throw new InvalidDataException( CloseFrame.NO_UTF8 );
							}
						}
						current_continuous_frame = null;
					} else if( current_continuous_frame == null ) {
						throw new InvalidDataException( CloseFrame.PROTOCOL_ERROR, "Continuous frame sequence was not started." );
					}
					//Check if the whole payload is valid utf8, when the opcode indicates a text
					if( curop == Opcode.TEXT ) {
						if( !Charsetfunctions.isValidUTF8( f.getPayloadData() ) ) {
							throw new InvalidDataException( CloseFrame.NO_UTF8 );
						}
					}
					//Checking if the current continous frame contains a correct payload with the other frames combined
					if( curop == Opcode.CONTINUOUS && current_continuous_frame != null && current_continuous_frame.getOpcode() == Opcode.TEXT ) {
						//Checking a bit more from the frame before this one just to make sure all the code points are correct
						int off = Math.max( current_continuous_frame.getPayloadData().limit() - 64, 0 );
						current_continuous_frame.append( f );
						if( !Charsetfunctions.isValidUTF8( current_continuous_frame.getPayloadData(), off ) ) {
							throw new InvalidDataException( CloseFrame.NO_UTF8 );
						}
					}
					try {
						wsl.onWebsocketMessageFragment( this, f );
					} catch ( RuntimeException e ) {
						wsl.onWebsocketError( this, e );
					}

				} else if( current_continuous_frame != null ) {
					throw new InvalidDataException( CloseFrame.PROTOCOL_ERROR, "Continuous frame sequence not completed." );
				} else if( curop == Opcode.TEXT ) {
					try {
						wsl.onWebsocketMessage( this, Charsetfunctions.stringUtf8( f.getPayloadData() ) );
					} catch ( RuntimeException e ) {
						wsl.onWebsocketError( this, e );
					}
				} else if( curop == Opcode.BINARY ) {
					try {
						wsl.onWebsocketMessage( this, f.getPayloadData() );
					} catch ( RuntimeException e ) {
						wsl.onWebsocketError( this, e );
					}
				} else {
					throw new InvalidDataException( CloseFrame.PROTOCOL_ERROR, "non control or continious frame expected" );
				}
			}
		} catch ( InvalidDataException e1 ) {
			wsl.onWebsocketError( this, e1 );
			close( e1 );
			return;
		}

	}

	private void close( int code, String message, boolean remote ) {
		if( readystate != READYSTATE.CLOSING && readystate != READYSTATE.CLOSED ) {
			if( readystate == READYSTATE.OPEN ) {
				if( code == CloseFrame.ABNORMAL_CLOSE ) {
					assert ( !remote );
					readystate = READYSTATE.CLOSING;
					flushAndClose( code, message, false );
					return;
				}
				if( draft.getCloseHandshakeType() != CloseHandshakeType.NONE ) {
					try {
						if( !remote ) {
							try {
								wsl.onWebsocketCloseInitiated( this, code, message );
							} catch ( RuntimeException e ) {
								wsl.onWebsocketError( this, e );
							}
						}
						sendFrame( new CloseFrameBuilder( code, message ) );
					} catch ( InvalidDataException e ) {
						wsl.onWebsocketError( this, e );
						flushAndClose( CloseFrame.ABNORMAL_CLOSE, "generated frame is invalid", false );
					}
				}
				flushAndClose( code, message, remote );
			} else if( code == CloseFrame.FLASHPOLICY ) {
				assert ( remote );
				flushAndClose( CloseFrame.FLASHPOLICY, message, true );
			} else {
				flushAndClose( CloseFrame.NEVER_CONNECTED, message, false );
			}
			if( code == CloseFrame.PROTOCOL_ERROR )// this endpoint found a PROTOCOL_ERROR
				flushAndClose( code, message, remote );
			readystate = READYSTATE.CLOSING;
			tmpHandshakeBytes = null;
			return;
		}
	}

	@Override
	public void close( int code, String message ) {
		close( code, message, false );
	}

	/**
	 * This will close the connection immediately without a proper close request.
	 * The code and the message therefore won't be transfered over the wire also they will be forwarded to generation/onWebsocketClose.
	 * @param code the closing code
	 * @param message the closing message
	 * @param remote Indicates who "generated" <code>code</code>.<br>
	 *               <code>true</code> means that this endpoint received the <code>code</code> from the other endpoint.<br>
	 *               false means this endpoint decided to send the given code,<br>
	 *               <code>remote</code> may also be true if this endpoint started the closing request since the other endpoint may not simply echo the <code>code</code> but close the connection the same time this endpoint does do but with an other <code>code</code>. <br>
	 **/
	protected synchronized void closeConnection( int code, String message, boolean remote ) {
		if( readystate == READYSTATE.CLOSED ) {
			return;
		}

		if( key != null ) {
			// key.attach( null ); //see issue #114
			key.cancel();
		}
		if( channel != null ) {
			try {
				channel.close();
			} catch ( IOException e ) {
				wsl.onWebsocketError( this, e );
			}
		}
		try {
			this.wsl.onWebsocketClose( this, code, message, remote );
		} catch ( RuntimeException e ) {
			wsl.onWebsocketError( this, e );
		}
		if( draft != null )
			draft.reset();
		handshakerequest = null;

		readystate = READYSTATE.CLOSED;
		this.outQueue.clear();
	}

	protected void closeConnection( int code, boolean remote ) {
		closeConnection( code, "", remote );
	}

	public void closeConnection() {
		if( closedremotely == null ) {
			throw new IllegalStateException( "this method must be used in conjuction with flushAndClose" );
		}
		closeConnection( closecode, closemessage, closedremotely );
	}

	public void closeConnection( int code, String message ) {
		closeConnection( code, message, false );
	}

	protected synchronized void flushAndClose( int code, String message, boolean remote ) {
		if( flushandclosestate ) {
			return;
		}
		closecode = code;
		closemessage = message;
		closedremotely = remote;

		flushandclosestate = true;

		wsl.onWriteDemand( this ); // ensures that all outgoing frames are flushed before closing the connection
		try {
			wsl.onWebsocketClosing( this, code, message, remote );
		} catch ( RuntimeException e ) {
			wsl.onWebsocketError( this, e );
		}
		if( draft != null )
			draft.reset();
		handshakerequest = null;
	}

	public void eot() {
		if( getReadyState() == READYSTATE.NOT_YET_CONNECTED ) {
			closeConnection( CloseFrame.NEVER_CONNECTED, true );
		} else if( flushandclosestate ) {
			closeConnection( closecode, closemessage, closedremotely );
		} else if( draft.getCloseHandshakeType() == CloseHandshakeType.NONE ) {
			closeConnection( CloseFrame.NORMAL, true );
		} else if( draft.getCloseHandshakeType() == CloseHandshakeType.ONEWAY ) {
			if( role == Role.SERVER )
				closeConnection( CloseFrame.ABNORMAL_CLOSE, true );
			else
				closeConnection( CloseFrame.NORMAL, true );
		} else {
			closeConnection( CloseFrame.ABNORMAL_CLOSE, true );
		}
	}

	@Override
	public void close( int code ) {
		close( code, "", false );
	}

	public void close( InvalidDataException e ) {
		close( e.getCloseCode(), e.getMessage(), false );
	}

	/**
	 * Send Text data to the other end.
	 *
	 * @throws NotYetConnectedException websocket is not yet connected
	 */
	@Override
	public void send( String text ) throws WebsocketNotConnectedException {
		if( text == null )
			throw new IllegalArgumentException( "Cannot send 'null' data to a WebSocketImpl." );
		send( draft.createFrames( text, role == Role.CLIENT ) );
	}

	/**
	 * Send Binary data (plain bytes) to the other end.
	 *
	 * @throws IllegalArgumentException the data is null
	 * @throws NotYetConnectedException websocket is not yet connected
	 */
	@Override
	public void send( ByteBuffer bytes ) throws IllegalArgumentException, WebsocketNotConnectedException {
		if( bytes == null )
			throw new IllegalArgumentException( "Cannot send 'null' data to a WebSocketImpl." );
		send( draft.createFrames( bytes, role == Role.CLIENT ) );
	}

	@Override
	public void send( byte[] bytes ) throws IllegalArgumentException, WebsocketNotConnectedException {
		send( ByteBuffer.wrap( bytes ) );
	}

	private void send( Collection<Framedata> frames ) {
		if( !isOpen() )
			throw new WebsocketNotConnectedException();
		for( Framedata f : frames ) {
			sendFrame( f );
		}
	}

	@Override
	public void sendFragmentedFrame( Opcode op, ByteBuffer buffer, boolean fin ) {
		send( draft.continuousFrame( op, buffer, fin ) );
	}

	@Override
	public void sendFrame( Framedata framedata ) {
		if( DEBUG )
			System.out.println( "send frame: " + framedata );
		write( draft.createBinaryFrame( framedata ) );
	}

	@Override
	public boolean hasBufferedData() {
		return !this.outQueue.isEmpty();
	}

	private HandshakeState isFlashEdgeCase( ByteBuffer request ) throws IncompleteHandshakeException {
		request.mark();
		if( request.limit() > Draft.FLASH_POLICY_REQUEST.length ) {
			return HandshakeState.NOT_MATCHED;
		} else if( request.limit() < Draft.FLASH_POLICY_REQUEST.length ) {
			throw new IncompleteHandshakeException( Draft.FLASH_POLICY_REQUEST.length );
		} else {

			for( int flash_policy_index = 0; request.hasRemaining(); flash_policy_index++ ) {
				if( Draft.FLASH_POLICY_REQUEST[flash_policy_index] != request.get() ) {
					request.reset();
					return HandshakeState.NOT_MATCHED;
				}
			}
			return HandshakeState.MATCHED;
		}
	}

	public void startHandshake( ClientHandshakeBuilder handshakedata ) throws InvalidHandshakeException {
		assert ( readystate != READYSTATE.CONNECTING ) : "shall only be called once";

		// Store the Handshake Request we are about to send
		this.handshakerequest = draft.postProcessHandshakeRequestAsClient( handshakedata );

		resourceDescriptor = handshakedata.getResourceDescriptor();
		assert ( resourceDescriptor != null );

		// Notify Listener
		try {
			wsl.onWebsocketHandshakeSentAsClient( this, this.handshakerequest );
		} catch ( InvalidDataException e ) {
			// Stop if the client code throws an exception
			throw new InvalidHandshakeException( "Handshake data rejected by client." );
		} catch ( RuntimeException e ) {
			wsl.onWebsocketError( this, e );
			throw new InvalidHandshakeException( "rejected because of" + e );
		}

		// Send
		write( draft.createHandshake( this.handshakerequest, role ) );
	}

	private void write( ByteBuffer buf ) {
		if( DEBUG )
			System.out.println( "write(" + buf.remaining() + "): {" + ( buf.remaining() > 1000 ? "too big to display" : new String( buf.array() ) ) + "}" );

		outQueue.add( buf );
		/*try {
			outQueue.put( buf );
		} catch ( InterruptedException e ) {
			write( buf );
			Thread.currentThread().interrupt(); // keep the interrupted status
			e.printStackTrace();
		}*/
		wsl.onWriteDemand( this );
	}

	private void write( List<ByteBuffer> bufs ) {
		for( ByteBuffer b : bufs ) {
			write( b );
		}
	}

	private void open( Handshakedata d ) {
		if( DEBUG )
			System.out.println( "open using draft: " + draft.getClass().getSimpleName() );
		readystate = READYSTATE.OPEN;
		try {
			wsl.onWebsocketOpen( this, d );
		} catch ( RuntimeException e ) {
			wsl.onWebsocketError( this, e );
		}
	}

	@Override
	public boolean isConnecting() {
		assert ( !flushandclosestate || readystate == READYSTATE.CONNECTING );
		return readystate == READYSTATE.CONNECTING; // ifflushandclosestate
	}

	@Override
	public boolean isOpen() {
		assert ( readystate != READYSTATE.OPEN || !flushandclosestate );
		return readystate == READYSTATE.OPEN;
	}

	@Override
	public boolean isClosing() {
		return readystate == READYSTATE.CLOSING;
	}

	@Override
	public boolean isFlushAndClose() {
		return flushandclosestate;
	}

	@Override
	public boolean isClosed() {
		return readystate == READYSTATE.CLOSED;
	}

	@Override
	public READYSTATE getReadyState() {
		return readystate;
	}

	@Override
	public int hashCode() {
		return super.hashCode();
	}

	@Override
	public String toString() {
		return super.toString(); // its nice to be able to set breakpoints here
	}

	@Override
	public InetSocketAddress getRemoteSocketAddress() {
		return wsl.getRemoteSocketAddress( this );
	}

	@Override
	public InetSocketAddress getLocalSocketAddress() {
		return wsl.getLocalSocketAddress( this );
	}

	@Override
	public Draft getDraft() {
		return draft;
	}

	@Override
	public void close() {
		close( CloseFrame.NORMAL );
	}

	@Override
	public String getResourceDescriptor() {
		return resourceDescriptor;
	}
}
