/*
 * 
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 * 
 * Copyright 2007-2008 Sun Microsystems, Inc. All rights reserved.
 * 
 * The contents of this file are subject to the terms of either the GNU
 * General Public License Version 2 only ("GPL") or the Common Development
 * and Distribution License("CDDL") (collectively, the "License").  You
 * may not use this file except in compliance with the License. You can obtain
 * a copy of the License at https://glassfish.dev.java.net/public/CDDL+GPL.html
 * or glassfish/bootstrap/legal/LICENSE.txt.  See the License for the specific
 * language governing permissions and limitations under the License.
 * 
 * When distributing the software, include this License Header Notice in each
 * file and include the License file at glassfish/bootstrap/legal/LICENSE.txt.
 * Sun designates this particular file as subject to the "Classpath" exception
 * as provided by Sun in the GPL Version 2 section of the License file that
 * accompanied this code.  If applicable, add the following below the License
 * Header, with the fields enclosed by brackets [] replaced by your own
 * identifying information: "Portions Copyrighted [year]
 * [name of copyright owner]"
 * 
 * Contributor(s):
 * 
 * If you wish your version of this file to be governed by only the CDDL or
 * only the GPL Version 2, indicate your decision by adding "[Contributor]
 * elects to include this software in this distribution under the [CDDL or GPL
 * Version 2] license."  If you don't indicate a single choice of license, a
 * recipient has the option to distribute your version of this file under
 * either the CDDL, the GPL Version 2 or to extend the choice of license to
 * its licensees as provided above.  However, if you add GPL Version 2 code
 * and therefore, elected the GPL Version 2 license, then the option applies
 * only if the new code is made subject to such option by the copyright
 * holder.
 *
 */

package com.sun.grizzly.filter;

import com.sun.grizzly.Context;
import com.sun.grizzly.Context.AttributeScope;
import com.sun.grizzly.ProtocolFilter;
import com.sun.grizzly.ProtocolParser;
import com.sun.grizzly.util.AttributeHolder;
import com.sun.grizzly.util.WorkerThread;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;

import com.sun.grizzly.util.ThreadAttachment;
import com.sun.grizzly.util.ThreadAttachment.Mode;

/**
 * Simple ProtocolFilter implementation which read the available bytes
 * and delegate the decision of reading more bytes or not to a ProtocolParser.
 * The ProtocolParser will decide if more bytes are required before continuing
 * the invokation of the ProtocolChain.
 *
 * @author Jeanfrancois Arcand
 */
public abstract class ParserProtocolFilter extends ReadFilter{
    
    /**
     * Should ParserProtocolFilter read the data from channel
     */
    protected boolean isSkipRead;
    
    public ParserProtocolFilter() {
    }
    
    /**
     * Read available bytes and delegate the processing of them to the next
     * ProtocolFilter in the ProtocolChain.
     * @return <tt>true</tt> if the next ProtocolFilter on the ProtocolChain
     * need to be invoked.
     */
    @Override
    public boolean execute(Context ctx) throws IOException {
        ProtocolParser parser = null;
        
        // Remove Parser from Connection related attributes (if it was there)
        
        AttributeHolder connectionAttrs =
                ctx.getAttributeHolderByScope(AttributeScope.CONNECTION);
        if (connectionAttrs != null) {
            parser = (ProtocolParser) connectionAttrs.removeAttribute(ProtocolParser.PARSER);
        }
        
	if (parser == null) {
            parser = (ProtocolParser) ctx.getAttribute(ProtocolParser.PARSER);
            
	    if (parser == null) {
                parser = newProtocolParser();
                ctx.setAttribute(ProtocolParser.PARSER, parser);
            }
	} else {
            ctx.setAttribute(ProtocolParser.PARSER, parser);
        }
        
	if (!parser.hasMoreBytesToParse() || parser.isExpectingMoreData()) {
            if (parser.isExpectingMoreData()) {
                // Saved ByteBuffer was restored. May be we will not need it next time
                ((WorkerThread) Thread.currentThread()).updateAttachment(Mode.ATTRIBUTES_ONLY);
            }
            
            boolean continueExecution = isSkipRead || super.execute(ctx);
            WorkerThread workerThread = (WorkerThread)Thread.currentThread();
            ByteBuffer byteBuffer = workerThread.getByteBuffer();
	    parser.startBuffer(byteBuffer);
            if (!continueExecution){
                return continueExecution;
            }
	}
	if (!parser.hasNextMessage()) {
	    return false;
	}
        
        return invokeProtocolParser(ctx, parser);
    }
    
    /**
     * Invoke the {@link ProtocolParser}. If more bytes are required,
     * register the {@link SelectionKey} back to its associated
     * {@link SelectorHandler}
     * @param ctx the Context object.
     * @return <tt>true</tt> if no more bytes are needed.
     */
    protected boolean invokeProtocolParser(Context ctx, ProtocolParser parser) {
        
        if (parser == null){
            throw new IllegalStateException("ProcotolParser cannot be null");
        }
        
	Object o = parser.getNextMessage();
	ctx.setAttribute(ProtocolParser.MESSAGE, o);
	return true;
    }

    @Override
    public boolean postExecute(Context context) throws IOException {
	ProtocolParser parser = (ProtocolParser)
		context.getAttribute(ProtocolParser.PARSER);
        
        if (parser == null) return true;
        
	if (parser != null && parser.hasMoreBytesToParse()) {
	    // Need to say that we read successfully since bytes are left
	    context.setAttribute(ProtocolFilter.SUCCESSFUL_READ, Boolean.TRUE);
	    return true;
	}

        SelectionKey key = context.getSelectionKey();
        if (parser.isExpectingMoreData()) {
            if (parser.releaseBuffer()) {
                saveParser(key, parser);
            }
            
            saveByteBuffer(key);
            
            // Register to get more bytes.
            context.getSelectorHandler().register(key,SelectionKey.OP_READ);
	    return false;
        } 

	if (parser.releaseBuffer()) {
            saveParser(key, parser);
        }
        
	return super.postExecute(context);
    }
    
    /**
     * Return a new or cached ProtocolParser instance.
     */
    public abstract ProtocolParser newProtocolParser();

    private void saveByteBuffer(SelectionKey key) {
        WorkerThread workerThread = (WorkerThread) Thread.currentThread();
        // Detach the current Thread data.
        ThreadAttachment threadAttachment =
                workerThread.updateAttachment(Mode.BYTE_BUFFER);

        // Attach it to the SelectionKey so the it can be resumed latter.
        key.attach(threadAttachment);
    }

    private void saveParser(SelectionKey key, ProtocolParser parser) {
        WorkerThread workerThread = (WorkerThread) Thread.currentThread();
        // Detach the current Thread data.
        ThreadAttachment threadAttachment = workerThread.getAttachment();
        threadAttachment.setAttribute(ProtocolParser.PARSER, parser);
        // Attach it to the SelectionKey so the it can be resumed latter.
        key.attach(threadAttachment);
    }

    /**
     * Method returns true, if this Filter perform channel read operation on
     * execute, or false - Filter assumes the data was read by previous Filters in
     * a chain.
     * 
     * @return true, if Filter will perform channel read operation on execute.
     * False - Filter assumes the data was read by previous Filters in a chain. 
     */
    protected boolean isSkipRead() {
        return isSkipRead;
    }

    /**
     * Method set if this Filter should perform channel read operation on
     * execute, or should assumes the data was read by previous Filters in
     * a chain.
     * 
     * @param isSkipRead If isSkipRead is true, this Filter will perform 
     * channel read operation on execute. If false - Filter assumes the data 
     * was read by previous Filters in a chain.
     */
    protected void setSkipRead(boolean isSkipRead) {
        this.isSkipRead = isSkipRead;
    }
}