package org.jgroups.blocks.mux;

import org.jgroups.Event;
import org.jgroups.Message;
import org.jgroups.UpHandler;
import org.jgroups.logging.Log;
import org.jgroups.logging.LogFactory;
import org.jgroups.util.ImmutableReference;
import org.jgroups.util.Util;

import java.util.Map;

/**
 * Allows up handler multiplexing.
 * 
 * @author Bela Ban
 * @author Paul Ferraro
 */
public class MuxUpHandler implements UpHandler, Muxer<UpHandler> {

    protected final Log log=LogFactory.getLog(getClass());
    private final Map<Short, UpHandler> handlers=Util.createConcurrentMap();
    private volatile UpHandler defaultHandler;
    private volatile Event lastFlushEvent;
    private final Object flushMutex = new Object();
    
    /**
     * Creates a multiplexing up handler, with no default handler.
     */
    public MuxUpHandler() {
        this.defaultHandler = null;
    }

    /**
     * Creates a multiplexing up handler using the specified default handler.
     * @param defaultHandler a default up handler to handle messages with no {@link MuxHeader}
     */
    public MuxUpHandler(UpHandler defaultHandler) {
        this.defaultHandler = defaultHandler;
    }

    /**
     * {@inheritDoc}
     * @see org.jgroups.blocks.mux.Muxer#add(short, java.lang.Object)
     */
    @Override
    public void add(short id, UpHandler handler) {
        synchronized (flushMutex)
        {          
            if (lastFlushEvent != null)
            {
                handler.up(lastFlushEvent);
            }
            handlers.put(id, handler);
        }
    }

    /**
     * {@inheritDoc}
     * @see org.jgroups.blocks.mux.Muxer#get(short)
     */
    @Override
    public UpHandler get(short id) {
        return handlers.get(id);
    }
    
    /**
     * {@inheritDoc}
     * @see org.jgroups.blocks.mux.Muxer#remove(short)
     */
    @Override
    public void remove(short id) {
        handlers.remove(id);
    }

    @Override
    public UpHandler getDefaultHandler() {
        return defaultHandler;
    }

    @Override
    public void setDefaultHandler(UpHandler handler) {
        this.defaultHandler = handler;      
    }

   /**
     * {@inheritDoc}
     * @see org.jgroups.UpHandler#up(org.jgroups.Event)
     */
    @Override
    public Object up(Event evt) {
        switch (evt.getType()) {
            case Event.MSG: {
                Message msg = (Message) evt.getArg();
                MuxHeader hdr = (MuxHeader) msg.getHeader(MuxRequestCorrelator.MUX_ID);
                if (hdr != null) {
                    short id = hdr.getId();
                    UpHandler handler = handlers.get(id);
                    return (handler != null) ? handler.up(evt) : new NoMuxHandler(id);
                }
                break;
            }
            case Event.GET_APPLSTATE:
            case Event.GET_STATE_OK: 
            case Event.STATE_TRANSFER_OUTPUTSTREAM: 
            case Event.STATE_TRANSFER_INPUTSTREAM: {
                ImmutableReference<Object> wrapper = handleStateTransferEvent(evt);
                if (wrapper != null)
                {
                   return wrapper.get();
                }
                break;
            } 
            case Event.BLOCK: 
            case Event.UNBLOCK: {
               synchronized (flushMutex)
               {
                  this.lastFlushEvent = evt;
                  passToAllHandlers(evt);
                  break;
               }
            }
            case Event.VIEW_CHANGE:
            case Event.SET_LOCAL_ADDRESS: 
            case Event.SUSPECT:  {
               passToAllHandlers(evt);
               break;
           }
           default: {
                passToAllHandlers(evt);
                break;
           }
        }
        
        return (defaultHandler != null) ? defaultHandler.up(evt) : null;
    }

    /**
      * Extension point for subclasses called by up() when an event
      * related to state transfer is received, allowing the subclass
      * to override the default behavior of passing the event to the
      * default up handler.
      *
      * @return an AtomicReference containing the return value for the event 
      *         if the event was handled and no further processing
      *         should be done in up(), or <code>null</code> if up() needs to 
      *         handle the event. If the event was handled but the return value 
      *         is <code>null</code>, an AtomicReference initialized to 
      *         <code>null</code> should be returned. This default 
      *         implementation always returns <code>null</code>
      */
    protected ImmutableReference<Object> handleStateTransferEvent(Event evt) {
         return null;
    } 
    
    

    private void passToAllHandlers(Event evt)
    {
       for (UpHandler handler: handlers.values()) {
            handler.up(evt);
        }
    }
}
