/*
 * The contents of this file are subject to the terms
 * of the Common Development and Distribution License
 * (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/CDDLv1.0.html or
 * glassfish/bootstrap/legal/CDDLv1.0.txt.
 * See the License for the specific language governing
 * permissions and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL
 * Header Notice in each file and include the License file
 * at glassfish/bootstrap/legal/CDDLv1.0.txt.
 * If applicable, add the following below the CDDL Header,
 * with the fields enclosed by brackets [] replaced by
 * you own identifying information:
 * "Portions Copyrighted [year] [contextPath of copyright owner]"
 *
 * Copyright 2006 Sun Microsystems, Inc. All rights reserved.
 */

package com.sun.enterprise.web.connector.grizzly.comet;

import com.sun.enterprise.web.connector.grizzly.SelectorThread;
import com.sun.enterprise.web.connector.grizzly.async.AsyncProcessorTask;
import java.io.IOException;
import java.nio.channels.SelectionKey;
import java.util.Iterator;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * The main object used by <code>CometHandler</code>. 
 * The <code>CometContext</code> is always available for <code>CometHandler</code>
 * and can be used to notify other <code>CometHandler</code>.
 *
 * Attributes can be added/removed the same way <code>HttpServletSession</code> 
 * is doing. It is not recommended to use attributes if this 
 * <code>CometContext</code> is not shared amongs multiple
 * context path (uses HttpServletSession instead).
 *
 * @author Jeanfrancois Arcand
 */
public class CometContext<E> {
    
    /**
     * Main logger
     */
    private final static Logger logger = SelectorThread.logger();  
 
     
    /**
     * Attributes placeholder.
     */
    private ConcurrentHashMap attributes;
    
    
    /**
     * The context path associated with this instance.
     */
    private String contextPath;
    
    
    /**
     * Is the <code>CometContext</code> instance been cancelled.
     */
    protected boolean cancelled = false;
    
    
    /**
     * Temporary repository that associate a Thread ID with a Key.
     * NOTE: A ThreadLocal might be more efficient.
     */
    private ConcurrentHashMap<Long,SelectionKey> threadsId;    
    
    
    /**
     * The list of registered <code>CometHandler</code>
     */
    private ConcurrentHashMap<CometHandler,SelectionKey> handlers;
    
    
    /**
     * The list of registered <code>AsyncProcessorTask</code>. This object
     * are mainly keeping the state of the Comet request.
     */    
    private ConcurrentLinkedQueue<AsyncProcessorTask> asyncTasks;    
    
    
    /**
     * The <code>CometSelector</code> used to register <code>SelectionKey</code>
     * for upcoming bytes.
     */
    private CometSelector cometSelector;
    
    
    /**
     * The <code>CometContext</code> type. See <code>CometEngine</code>
     */
    protected int type;
    
    
    /**
     * The default delay expiration before a <code>CometContext</code>'s
     * <code>CometHandler</code> are interrupted.
     */
    private long expirationDelay = 30 * 1000;

    
    // ---------------------------------------------------------------------- //
    
    
    /**
     * Create a new instance
     * @param contextPath the context path 
     * @param type when the Comet processing will happen (see CometEngine).
     */
    public CometContext(String contextPath, int type) {
        this.contextPath = contextPath;
        this.type = type;
        attributes = new ConcurrentHashMap();
        handlers = new ConcurrentHashMap<CometHandler,SelectionKey>();
        threadsId = new ConcurrentHashMap<Long,SelectionKey>();        
        asyncTasks = new ConcurrentLinkedQueue<AsyncProcessorTask>();          
    }

    
    /**
     * Get the context path associated with this instance.
     * @return contextPath the context path associated with this instance
     */
    public String getContextPath(){
        return contextPath;
    }
    
    
    /**
     * Add an attibute.
     * @param key the key
     * @param value the value
     */
    public void addAttribute(Object key, Object value){
        attributes.put(key,value);
    }

    
    /**
     * Retrive an attribute.
     * @param key the key
     * @return Object the value.
     */
    public Object getAttribute(Object key){
        return attributes.get(key);
    }    
    
    
    /**
     * Remove an attribute.
     * @param key the key
     * @return Object the value
     */
    public Object removeAttribute(Object key){
        return attributes.remove(key);
    }  

    
    /**
     * Add a <code>CometHandler</code>. Client on this method might
     * make sure the <code>CometHandler</code> is removed when the 
     * <code>CometHandler.onInterrupt</code> is invoked.
     * @param handler a new <code>CometHandler</code>
     */
    public int addCometHandler(CometHandler handler){
        SelectionKey key = threadsId.remove(Thread.currentThread().getId());
        if (key == null){
            throw new 
               IllegalStateException("Grizzly Comet hasn't been registered");
        }
        
        if (handler == null){
            throw new 
               IllegalStateException("Handler cannot be null");
        }

        handlers.put(handler,key);
        CometEngine.getEngine().addUpdateKey(key);
        return handler.hashCode();
    }
    
    
    /**
     * Retrive a <code>CometHandler</code> using its hashKey;
     */
    public CometHandler getCometHandler(int hashCode){
        Iterator<CometHandler> iterator = handlers.keySet().iterator();
        CometHandler cometHandler = null;
        while (iterator.hasNext()){
            cometHandler = iterator.next();
            if ( cometHandler.hashCode() == hashCode ){
               return cometHandler;
            }
        }
        return null;
    }   
    
    
    /*
     * Notify all <code>CometHandler</code>. The attachment can be null.
     * The <code>type</code> will determine which code>CometHandler</code> 
     * method will be invoked:
     * 
     * CometEvent.INTERRUPT -> <code>CometHandler.onInterrupt</code>
     * CometEvent.NOTIFY -> <code>CometHandler.onEvent</code>
     * CometEvent.INITIALIZE -> <code>CometHandler.onInitialize</code>
     * CometEvent.TERMINATE -> <code>CometHandler.onTerminate</code>
     * CometEvent.READ -> <code>CometHandler.onEvent</code>
     *
     * @param attachment An object shared amongst <code>CometHandler</code>. 
     * @param type The type of notification. 
     * @param key The SelectionKey associated with the CometHandler.
     */
    protected void notify(E attachment, int type, SelectionKey key) 
            throws IOException{
        Iterator<CometHandler> iterator = handlers.keySet().iterator();
        CometHandler cometHandler = null;
        while (iterator.hasNext()){
            cometHandler = iterator.next();
            // Might have several handlers related to a single key.
            if (handlers.get(cometHandler) == key){
                notify(attachment,type,cometHandler.hashCode());
            }
        }
    }    
    
    
    /**
     * Remove a <code>CometHandler</code>
     */
    public void removeCometHandler(CometHandler handler){
        handlers.remove(handler);
    }
    
    
    /**
     * Notify all <code>CometHandler</code>. The attachment can be null. All
     * <code>CometHandler.onEvent()</code> will be invoked.
     * @param attachment An object shared amongst <code>CometHandler</code>. 
     */
    public void notify(E attachment) throws IOException{
        CometEvent event = new CometEvent<E>();
        event.setType(CometEvent.NOTIFY);
        event.attach(attachment);
        event.setCometContext(this);
        Iterator<CometHandler> iterator = handlers.keySet().iterator();
        while(iterator.hasNext()){
            iterator.next().onEvent(event);
        }
        registerKeys();                  
    }    
    
    
    /**
     * Notify all <code>CometHandler</code>. The attachment can be null.
     * The <code>type</code> will determine which code>CometHandler</code> 
     * method will be invoked:
     * 
     * CometEvent.INTERRUPT -> <code>CometHandler.onInterrupt</code>
     * CometEvent.NOTIFY -> <code>CometHandler.onEvent</code>
     * CometEvent.INITIALIZE -> <code>CometHandler.onInitialize</code>
     * CometEvent.TERMINATE -> <code>CometHandler.onTerminate</code>
     * CometEvent.READ -> <code>CometHandler.onEvent</code>
     *
     * @param attachment An object shared amongst <code>CometHandler</code>. 
     * @param type The type of notification. 
     * @param cometHandlerID Notify a single CometHandler.
     */
    public void notify(E attachment, int type, int cometHandlerID) 
            throws IOException{
        CometEvent event = new CometEvent<E>();
        event.setType(type);
        event.attach(attachment);
        event.setCometContext(this);
        CometHandler cometHandler = getCometHandler(cometHandlerID);
        if ( cometHandler != null ) {
            switch (type) {
                case CometEvent.INTERRUPT:
                    cometHandler.onInterrupt(event);
                    break;
                case CometEvent.NOTIFY:
                    cometHandler.onEvent(event);
                    break;
                case CometEvent.READ:
                    cometHandler.onEvent(event);
                    break;                    
                case CometEvent.INITIALIZE:
                    cometHandler.onInitialize(event);
                    break;      
                case CometEvent.TERMINATE:
                    cometHandler.onTerminate(event);
                    break;                       
                default:
                    throw new IllegalStateException();
            }
        }
        registerKeys();                  
    }   
    
    
    /**
     * Initialize the newly added <code>CometHandler</code>. 
     *
     * @param attachment An object shared amongst <code>CometHandler</code>. 
     * @param type The type of notification. 
     * @param key The SelectionKey representing the CometHandler.
     */      
    protected void initialize(SelectionKey key) throws IOException {
        CometEvent event = new CometEvent<E>();
        event.setType(event.INITIALIZE);
        event.setCometContext(this);
        
        Iterator<CometHandler> iterator = handlers.keySet().iterator();
        CometHandler cometHandler = null;
        while(iterator.hasNext()){
            cometHandler = iterator.next();
            if(handlers.get(cometHandler).equals(key)){
                cometHandler.onInitialize(event);
                break;
            }
        }
    }
    
    
    /**
     * Notify all <code>CometHandler</code>. The attachment can be null.
     * The <code>type</code> will determine which code>CometHandler</code> 
     * method will be invoked:
     * 
     * CometEvent.INTERRUPT -> <code>CometHandler.onInterrupt</code>
     * CometEvent.NOTIFY -> <code>CometHandler.onEvent</code>
     * CometEvent.INITIALIZE -> <code>CometHandler.onInitialize</code>
     * CometEvent.TERMINATE -> <code>CometHandler.onTerminate</code>
     * CometEvent.READ -> <code>CometHandler.onEvent</code>
     *
     * @param attachment An object shared amongst <code>CometHandler</code>. 
     * @param type The type of notification. 
     */   
    public void notify(E attachment, int type) throws IOException{
        // XXX Use a pool of CometEvent instance.
        CometEvent event = new CometEvent<E>();
        event.setType(type);
        event.attach(attachment);
        event.setCometContext(this);
        
        Iterator<CometHandler> iterator = handlers.keySet().iterator();
        while(iterator.hasNext()){          
            switch (type) {
                case CometEvent.INTERRUPT:
                    iterator.next().onInterrupt(event);
                    break;
                case CometEvent.NOTIFY:
                    iterator.next().onEvent(event);
                    break;
                case CometEvent.READ:
                    iterator.next().onEvent(event);
                    break;                    
                case CometEvent.INITIALIZE:
                    iterator.next().onInitialize(event);
                    break;      
                case CometEvent.TERMINATE:
                    iterator.next().onTerminate(event);
                    break;                       
                default:
                    throw new IllegalStateException();
            }
        }
        
        if ( type == CometEvent.NOTIFY){
            registerKeys();
        }
    }

    
    /**
     * Register the current <code>AsyncProcessorTask</code> keys to the 
     * <code>CometSelector</code> so new bytes can be processed.
     */
    private void registerKeys(){
        CometTask cometTask;       
        for (AsyncProcessorTask asyncTask: asyncTasks){
            SelectionKey key = asyncTask.getSelectionKey();
            
            if (key == null) continue;
            
            cometTask = (CometTask)key.attachment();            
            // Will hapens when a single CometHandler is invoked.
            if (cometTask == null){
                cometTask = CometEngine.getEngine().getCometTask(this,key);
                key.attach(cometTask);
            }
            cometTask.setExpirationDelay(expirationDelay);
            cometTask.setExpireTime(System.currentTimeMillis());
        }  
    }    
    
    
    /**
     * Recycle this object.
     */
    protected void recycle(){
        handlers.clear();
        attributes.clear();
        cancelled = false;
        asyncTasks.clear();
    }    

    
    /**
     * Is this instance beeing cancelled by the <code>CometSelector</code>
     * @return boolean cancelled or not.
     */
    protected boolean isCancelled() {
        return cancelled;
    }

    
    /**
     * Cancel this object or "uncancel".
     * @param cancelled true or false.
     */
    protected void setCancelled(boolean cancelled) {
        this.cancelled = cancelled;
    }


    /**
     * Add a <code>AsyncProcessorTask</code>.
     * @param asyncTask the <code>AsyncProcessorTask</code>
     */
    protected void addAsyncProcessorTask(AsyncProcessorTask asyncTask){
        asyncTasks.add(asyncTask);
    }
    
    
    /**
     * Return the list of <code>AsyncProcessorTask</code>
     * @return ConcurrentLinkedQueue the list of <code>AsyncProcessorTask</code>
     */
    protected ConcurrentLinkedQueue<AsyncProcessorTask> getAsyncProcessorTask(){
        return asyncTasks;
    }

    
    /**
     * Get the <code>CometSelector</code> associated with this instance.
     * @return CometSelector the <code>CometSelector</code> associated with 
     *         this instance.
     */
    protected CometSelector getCometSelector() {
        return cometSelector;
    }

    
    /**
     * Set the <code>CometSelector</code> associated with this instance.
     * @param CometSelector the <code>CometSelector</code> associated with 
     *         this instance.
     */   
    protected void setCometSelector(CometSelector cometSelector) {
        this.cometSelector = cometSelector;
    }
    
    
    /**
     * Helper.
     */
    public String toString(){
        return contextPath;
    }

    
    /**
     * Return the <code>long</code> delay before a request is resumed.
     * @return long the <code>long</code> delay before a request is resumed.
     */
    public long getExpirationDelay() {
        return expirationDelay;
    }

    
    /**
     * Set the <code>long</code> delay before a request is resumed.
     * @param long the <code>long</code> delay before a request is resumed.
     */    
    public void setExpirationDelay(long expirationDelay) {
        this.expirationDelay = expirationDelay;
    }   
    
    
    /**
     * Attach a Thread ID with the <code>SelectionKey</code>
     */
    protected void attachKeyToThreadId(SelectionKey key){
        threadsId.put(Thread.currentThread().getId(),key);
    }
    
    
    /**
     * Invoke <code>CometHandler.onTerminate</code>
     */
    protected void interrupt(SelectionKey key){
        CometEvent event = new CometEvent<E>();
        event.setType(CometEvent.INTERRUPT);
        event.attach(null);
        event.setCometContext(this);
        
        closeConnection(event,key);
    }
       
    
    /**
     * Advise <code>CometHandler</code> the connection will be closed.
     */
    private void closeConnection(CometEvent event, SelectionKey key){    
        Iterator<CometHandler> iterator = handlers.keySet().iterator();
        CometHandler handler;
        while(iterator.hasNext()){         
            handler = iterator.next();
            if ( handlers.get(handler).equals(key) ){
                try{
                    handler.onInterrupt(event);
                    iterator.remove();
                } catch (IOException ex){
                    logger.log(Level.WARNING,"Exception: ",ex);
                }
                break;
            }
        }        
    }
    
}
    
