/**
 * License Agreement.
 *
 *  JBoss RichFaces - Ajax4jsf Component Library
 *
 * Copyright (C) 2007  Exadel, Inc.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License version 2.1 as published by the Free Software Foundation.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 */

package org.richfaces.renderkit;

import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import javax.faces.component.ContextCallback;
import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;

import org.apache.commons.collections.MultiHashMap;
import org.richfaces.component.Draggable;
import org.richfaces.component.Dropzone;
import org.richfaces.event.DnDEvent;

/**
 * @author Nick Belaevski - nbelaevski@exadel.com
 * created 27.12.2006
 * 
 */
final class DnDEventsExchangeMailer {
	private DnDEventsExchangeMailer() {

	}

	private static class EventInfoStructure {
		private DnDEvent dndEvent;
		private EventCallback eventCallback;
		
		private Object type;
		private Object value;
		
		public EventInfoStructure(DnDEvent dndEvent,
				EventCallback eventCallback, Object type, Object value) {
			super();
			this.dndEvent = dndEvent;
			this.eventCallback = eventCallback;
			this.type = type;
			this.value = value;
		}

	}

	static abstract class EventCallback {
		abstract void processEvent(DnDEvent dndEvent, UIComponent source, FacesContext facesContext, Object type, Object value);
	}

	static DnDEventsExchangeMailer getInstance(FacesContext facesContext) {
		synchronized (facesContext) {
			Map requestMap = facesContext.getExternalContext().getRequestMap();

			String attrName = DnDEventsExchangeMailer.class.getName();
			DnDEventsExchangeMailer instance;
			if ((instance = (DnDEventsExchangeMailer) requestMap.get(attrName)) == null) {
				instance = new DnDEventsExchangeMailer();

				requestMap.put(attrName, instance);
			}

			return instance;
		}
	}

	private Map<String, EventInfoStructure> queuedMap = new HashMap<String, EventInfoStructure>();

	private Map<String, UIComponent> components = new HashMap<String, UIComponent>();

	private void processEvent(UIComponent source, FacesContext facesContext, DnDEvent dndEvent, EventCallback callback, Object type, Object value) {
		if (callback != null) {
			callback.processEvent(dndEvent, source, facesContext, type, value); 
		}
	}
	
	/**
	 * Decode drag & drop events. Collect pairs of correspondent drag & drop events and send them
	 * together where OnDrag becomes always before OnDrop.   
	 * 
	 * @param sourceId Id of element event come from
	 * @param target component that receive event
	 * @param facesContext Faces context
	 * @param dndEvent drag & drop event descriptor
	 * @param callback call back method
	 * @param type type of dragged/dropped value
	 * @param value dragged/dropped value
	 * @param isDraggable whether the event related draggable component or dropzone one.
	 */
	public void mailEvent(String sourceId, UIComponent target, FacesContext facesContext, final DnDEvent dndEvent, 
			final EventCallback callback, final Object type, final Object value, boolean isDraggable) {
		
		final UIComponent component = components.get(sourceId);
		String targetId = target.getClientId(facesContext);
		
		if (component == null) {
			//component with that sourceId have never mailed anything before - wait
			if (queuedMap.containsKey(sourceId)) {
				throw new IllegalStateException("Drag source with id '" + sourceId + "' already specified.");
			}
			queuedMap.put(sourceId, new EventInfoStructure(dndEvent, callback, type, value));
			components.put(targetId, target);
		} else {
			//check queued mail lists for current component
			final EventInfoStructure eventInfo = (EventInfoStructure) queuedMap.get(targetId);
			if (eventInfo != null) {
				Draggable draggable;
				Dropzone dropzone;
				
				final EventInfoStructure dragEventInfo = isDraggable ? eventInfo : new EventInfoStructure(dndEvent, callback, type, value);
				final EventInfoStructure dropEventInfo = isDraggable ? new EventInfoStructure(dndEvent, callback, type, value) : eventInfo;
				
				Object acceptedTypes;
				Object dragType;

				if (isDraggable) {
					draggable = (Draggable) target;
					dropzone = (Dropzone) component;
					
					acceptedTypes = eventInfo.type;
					dragType = type;
				} else {
					draggable = (Draggable) component;
					dropzone = (Dropzone) target;
					
					acceptedTypes = type;
					dragType = eventInfo.type;
				}

				if (DnDValidator.validateAcceptTypes(facesContext, 
						draggable, dropzone, 
						dragType, acceptedTypes)) {
					
					// Make sure that we will have OnDrag event occur first
					facesContext.getViewRoot().invokeOnComponent(facesContext, isDraggable ? targetId : sourceId, new ContextCallback() {
						public void invokeContextCallback(FacesContext fc, 
								      UIComponent targetComponent) {
							
							processEvent(targetComponent, fc, dragEventInfo.dndEvent, dragEventInfo.eventCallback, 
									dropEventInfo.type, dropEventInfo.value);
							
							dropEventInfo.dndEvent.queue();
						}
					});
					
					facesContext.getViewRoot().invokeOnComponent(facesContext, isDraggable ? sourceId : targetId, new ContextCallback() {
						public void invokeContextCallback(FacesContext fc, 
								      UIComponent targetComponent) {
							
							processEvent(targetComponent, fc, dropEventInfo.dndEvent, dropEventInfo.eventCallback, 
									dragEventInfo.type, dragEventInfo.value);
							
							dragEventInfo.dndEvent.queue();
						}
					});
				}
					
				queuedMap.remove(targetId);
			}
		}
	}

}

