package ca.sebastiendionne.grizzly.parser;

import java.nio.ByteBuffer;
import java.nio.charset.CharacterCodingException;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.sun.grizzly.ProtocolParser;
import com.sun.grizzly.util.ByteBufferFactory;
import com.sun.grizzly.util.WorkerThread;

public class EchoQueryProtocolParser implements ProtocolParser<String> {
	
	private static final Logger s_logger = LoggerFactory.getLogger(EchoQueryProtocolParser.class);
	
    // the limit, if the limit is reach the connection will be closed
    protected static final int LIMITBB = 5;
    protected CharsetDecoder f_asciiDecoder = Charset.forName("ISO-8859-1").newDecoder();
    protected ByteBuffer processingBuffer;
    protected String query = null;
    private boolean eoqFound = false;
    
    private boolean maxBufferReached = false;
    
    
    public static final String EOQ = "[EOQ]";
    
    
    /**
     * Get the next complete message from the buffer, which can then be
     * processed by the next filter in the protocol chain. Because not all
     * filters will understand protocol messages, this method should also
     * set the position and limit of the buffer at the start and end
     * boundaries of the message. Filters in the protocol chain can
     * retrieve this message via context.getAttribute(MESSAGE)
     *
     * @return The next message in the buffer. If there isn't such a message,
     *	return <code>null.</code>
     *
     */
    public String getNextMessage() {
        
    	if(s_logger.isDebugEnabled()){
    		s_logger.debug("getNextMessage");
    	}
    	
    	if(maxBufferReached){
    		return "MAX";
    	}
        return query;
    }
    
    // next method after isExpectingMoreData or releaseBuffer
    /**
     * Are there more bytes to be parsed in the {@link ByteBuffer} given
     * to this ProtocolParser's <code>setBuffer</code> ?
     * 
     * This method is typically called after a call to <code>parseBytes()</code>
     * to determine if the {@link ByteBuffer} has more bytes which need to
     * parsed into a message.
     * 
     * @return <tt>true</tt> if there are more bytes to be parsed.
     *         Otherwise <tt>false</tt>.
     */
    public boolean hasMoreBytesToParse() {
    	
    	if(s_logger.isDebugEnabled()){
    		s_logger.debug("hasMoreBytesToParse");
    	}

    	if (eoqFound && processingBuffer!=null && processingBuffer.hasRemaining()){
    		return true;
    	}
    	
    	return eoqFound && processingBuffer != null && processingBuffer.position() > 0;
    }
    
    /**
     * Indicates whether the buffer has a complete message that can be
     * returned from <code>getNextMessage</code>. Smart implementations of
     * this will set up all the information so that an actual call to
     * <code>getNextMessage</code> doesn't need to re-parse the data.
     */
    public boolean hasNextMessage() {
    	if(s_logger.isDebugEnabled()){
    		s_logger.debug("hasNextMessage");
    	}

    	if (processingBuffer == null) {
            return false;
        }
        if (processingBuffer.hasRemaining()) {
        	if(s_logger.isDebugEnabled()){
        		s_logger.debug("hasNextMessage: " + new String(processingBuffer.array(), processingBuffer.arrayOffset(), processingBuffer.remaining()));
        	}
            // decode the buffer
            String msg = null;
            try {
                ByteBuffer tmp = processingBuffer.duplicate();
                msg = f_asciiDecoder.decode(tmp).toString();
            } catch (CharacterCodingException e) {
            	s_logger.error("Error decoding query",e);
            	return false;
            }

            if(s_logger.isDebugEnabled()){
            	s_logger.debug("msg=" + msg);
            }

            int index = msg.indexOf(EOQ);
            if (index > -1) {

                query = msg.substring(0, index);

                // We need to kept what is after the EOQ
                processingBuffer.clear();
                
                if(s_logger.isDebugEnabled()){
                	String substr = msg.substring(index + EOQ.length());
                	s_logger.debug("index: " + index + " substr: " + substr + " substr.len: " + substr.length());
                }
                
                processingBuffer.put(msg.substring(index + EOQ.length()).getBytes());
                processingBuffer.flip();
                eoqFound = true;
            } else {
                // Check if buffer is full
                if (processingBuffer.remaining() == processingBuffer.capacity()) {
                    // If full - reallocate
                    
                    // but check if the max length is attain
                	if(processingBuffer.capacity() + processingBuffer.remaining()<LIMITBB){
	                    ByteBuffer newBB = ByteBufferFactory.allocateView(
	                            processingBuffer.capacity() * 2, 
	                            processingBuffer.isDirect());
	                    newBB.put(processingBuffer);
	                    processingBuffer = newBB;
	                    WorkerThread workerThread = (WorkerThread) Thread.currentThread();
	                    workerThread.setByteBuffer(processingBuffer);
                	} else {
                		
                		s_logger.info("BUFFER max length was reached");
                		
                        processingBuffer.clear();
                        
                		maxBufferReached = true;
                		
                		return maxBufferReached;
                	}
                }
                
                eoqFound = false; 
            }

        }

        return eoqFound;
    }
    
    // first method to been called
    /**
     * Is this ProtocolParser expecting more data ?
     * 
     * This method is typically called after a call to <code>parseBytes()</code>
     * to determine if the {@link ByteBuffer} which has been parsed
     * contains a partial message
     * 
     * @return - <tt>true</tt> if more bytes are needed to construct a
     *           message;  <tt>false</tt>, if no 
     *           additional bytes remain to be parsed into a <code>T</code>.
     *	 	 Note that if no partial message exists, this method should
     *		 return false.
     */
    public boolean isExpectingMoreData() {

    	if(s_logger.isDebugEnabled()){
    		s_logger.debug("isExpectingMoreData");
    	}

        // we need to loop until when get a query
    	// we need to validate is there isn't another query in buffer
    	
        return !eoqFound;
    }

    // if isExpectingMoreData : 2th method
    /**
     * No more parsing will be done on the buffer passed to
     * <code>startBuffer.</code>
     * Set up the buffer so that its position is the first byte that was
     * not part of a full message, and its limit is the original limit of
     * the buffer.
     *
     * @return -- true if the parser has saved some state (e.g. information
     * data in the buffer that hasn't been returned in a full message);
     * otherwise false. If this method returns true, the framework will
     * make sure that the same parser is used to process the buffer after
     * more data has been read.
     */
    public boolean releaseBuffer() {
        
    	if(s_logger.isDebugEnabled()){
    		s_logger.debug("releaseBuffer");
    	}

        if (processingBuffer != null) {
            processingBuffer.compact();
        }
        
        processingBuffer = null;

        eoqFound = false;

        return false;
    }

    // method after hasMoreBytesToParse if it's true
    /**
     * Set the buffer to be parsed. This method should store the buffer and
     * its state so that subsequent calls to <code>getNextMessage</code>
     * will return distinct messages, and the buffer can be restored after
     * parsing when the <code>releaseBuffer</code> method is called.
     */
    public void startBuffer(ByteBuffer bb) {
    	if(s_logger.isDebugEnabled()){
    		s_logger.debug("startBuffer");
    	}

        bb.flip();
        processingBuffer = bb;
        
    }
}
