Ext.namespace('Zarafa.calendar.ui');

/**
 * @class Zarafa.calendar.ui.CalendarViewDragZone
 * @extends Ext.dd.DragZone
 *
 * The special DragZone which enables appointments to be dragged
 * from a calendar to a {@link Ext.dd.DropZone}. This class supports
 * dragging and resizing of appointments, but also selecting a
 * daterange in the calendar.
 */
Zarafa.calendar.ui.CalendarViewDragZone = Ext.extend(Ext.dd.DragZone, {
	/**
	 * The calendar on which this DragZone is installed
	 * @property
	 * @type Zarafa.calendar.ui.AbstractCalendarView
	 * @private
	 */
	calendar : undefined,

	/**
	 * The special Element which is displayed when dragging data
	 * over the DragZone.
	 * @property
	 * @type Ext.Element
	 * @private
	 */
	ddel : undefined,

	/**
	 * @cfg {Boolean} headerMode True of this DropZone is installed on the header of
	 * the calendar, or in the body. This determines if the {@link Zarafa.calendar.ui.AbstractCalendarView#header}
	 * or {@link Zarafa.calendar.ui.AbstractCalendarView#body} will be used to connect the event handlers.
	 */
	headerMode : false,

	/**
	 * @constructor
	 * @param {Zarafa.calendar.ui.AbstractCalendarView} calendar The calendar on which this Dragzone is installed
	 * @param {Object} config Configuration object
	 */
	constructor : function(calendar, config)
	{
		config = config || {};

		this.calendar = calendar;

		var element = this.calendar.body;
		if (config.headerMode === true) {
			element = this.calendar.header;
		}

		Ext.applyIf(config, {
			ddGroup : 'AppointmentDD'
		});

		Zarafa.calendar.ui.CalendarViewDragZone.superclass.constructor.call(this, element, config);

		this.ddel = document.createElement('div');
		this.ddel.className = 'x-data-dd-wrap';
	},

	/**
	 * <p>The provided implementation of the getDragData method which collects the data to be dragged from the DataView on mousedown.</p>
	 * <p>This data is available for processing in the {@link Ext.dd.DropZone#onNodeEnter onNodeEnter}, {@link Ext.dd.DropZone#onNodeOver onNodeOver},
	 * {@link Ext.dd.DropZone#onNodeOut onNodeOut} and {@link Ext.dd.DropZone#onNodeDrop onNodeDrop} methods of a cooperating {@link Ext.dd.DropZone DropZone}.</p>
	 * <p>The data object contains the following properties:<ul>
	 * <li><b>calendar</b> : Zarafa.calendar.ui.AbstractCalendarView<div class="sub-desc">The CalendarView from which the data is being dragged.</div></li>
	 * <li><b>ddel</b> : htmlElement<div class="sub-desc">An htmlElement which provides the "picture" of the data being dragged.</div></li>
	 * <li><b>selections</b> : Array<div class="sub-desc">An Array of the selected Records which are being dragged from the DataView.</div></li>
	 * <li><b>target</b : Zarafa.calendar.ui.AppointmentView<div class="sub-desc">The appointment on which the drag action started.</div></li>
	 * </ul></p>
	 * @private
	 */
	getDragData : function(e)
	{
		var sm = this.calendar.selectionModel;
		var origEvent = new Ext.EventObjectImpl(e);
		var appointment = this.getAppointmentFromEvent(e);
		var state = this.getDragStateFromEvent(e, appointment);

		return {
			calendar : this.calendar,
			ddel : this.ddel,
			selections : sm.getSelections(),
			target : appointment,
			state : state,
			origEvent : origEvent
		};
	},

	/**
	 * This function is provided so that it is possible to perform a custom action before the initial
	 * drag event begins and optionally cancel it.
	 * @param {Object} data An object containing arbitrary data to be shared with drop targets
	 * @param {Event} e The event object
	 * @return {Boolean} isValid True if the drag event is valid, else false to cancel
	 * @protected
	 */
	onBeforeDrag : function(data, e)
	{
		var selectedAppointment = data.target;

		// We don't allow dragging of private appointments.
		if (selectedAppointment && selectedAppointment.getRecord().get('access') === 0) {
			return false;
		}

		return Zarafa.calendar.ui.CalendarViewDragZone.superclass.onBeforeDrag.apply(this, arguments);
	},

	/**
	 * Called before the user starts dragging an item.
	 * @param {Number} x The x coordinate from where the dragging started
	 * @param {Number} y The y coordinate from where the dragging started
	 * @private
	 */
	b4StartDrag : function(x, y)
	{
		Zarafa.calendar.ui.CalendarViewDragZone.superclass.b4StartDrag.apply(this, arguments);

		// ExtJs will make the proxy visible at the end of the b4StartDrag function, but
		// if our parent has a DropZone installed, we hide our proxy by default, as
		// the CalendarViewDropZone will have a custom proxy installed.
		if (this.calendar.enableDD || this.calendar.enableDrop) {
			this.proxy.hide();
		}
	},

	/**
	 * Called when the user starts dragging an item.
	 * @param {Number} x The x coordinate from where the dragging started
	 * @param {Number} y The y coordinate from where the dragging started
	 * @private
	 */
	startDrag : function(x, y)
	{
		Zarafa.calendar.ui.CalendarViewDragZone.superclass.startDrag.apply(this, arguments);

		// ExtJs will make the proxy visible at the end of the startDrag function, but
		// if our parent has a DropZone installed, we hide our proxy by default, as
		// the CalendarViewDropZone will have a custom proxy installed.
		if (this.calendar.enableDD || this.calendar.enableDrop) {
			this.proxy.hide();
		}
	},

	/**
	 * <p>The provided implementation of the onInitDrag method. Sets the <tt>innerHTML</tt> of the drag proxy which provides the "picture"
	 * of the data being dragged.</p>
	 * <p>The <tt>innerHTML</tt> data is found by calling the owning GridPanel's {@link Ext.grid.GridPanel#getDragDropText getDragDropText}.</p>
	 * @param {Number} x The x coordinate from where the dragging started
	 * @param {Number} y The y coordinate from where the dragging started
	 * @return {Boolean} true to continue the drag, false to cancel
	 * @private
	 */
	onInitDrag : function(x, e)
	{
		var data = this.dragData;

		// Check if the dragData contains a origEvent and target (as initialized by getDragData).
		// If this is present, we have to manually call the onMouseDown event handler
		// on the view to ensure that the item we are about to drag is marked as selected.
		//
		// We can't do that in getDragData because of the ordering of the event handlers
		// of both this class as well as the DataView.
		if (data && data.origEvent) {
			var selectedAppointment = data.target;
			var sm = this.calendar.selectionModel;

			if (selectedAppointment) {
				this.calendar.onInitDrag(data.origEvent, selectedAppointment);
			}

			if (!selectedAppointment || !sm.isSelected(selectedAppointment.getRecord())) {
				this.calendar.onMouseDown(data.origEvent, selectedAppointment);

				// The selection has been changed, update the selections array
				data.selections = sm.getSelections();
			}

			delete data.origEvent;
		}

		this.ddel.innerHTML = this.calendar.getDragDropText();
		this.proxy.update(this.ddel);
		// fire start drag?
	},

	/**
	 * An empty function by default, but provided so that you can perform a custom action before the dragged
	 * item is dragged out of the target without dropping, and optionally cancel the onDragOut.
	 * @param {Ext.dd.DragDrop} target The drop target
	 * @param {Event} e The event object
	 * @param {String} id The id of the dragged element
	 * @return {Boolean} isValid True if the drag event is valid, else false to cancel
	 */
	beforeDragOut : function(target, e, id)
	{
		// If we are selecting a range, we want to preserve our drag action,
		// this allows the user to shortly exit the browser while dragging while
		// maintaining he selection.
		return this.dragData.state !== Zarafa.calendar.data.DragStates.SELECTING;
	},

	/**
	 * Event handler that fires when a drag/drop obj gets a mouseup
	 *
	 * When the {@link #dragData} still contains the 'origEvent' field, then this
	 * function was called without that anything has been dragged (normally 'origEvent')
	 * is cleared in {@link #onInitDrag}. In this case we call {@link Zarafa.calendar.ui.AbstractCalendarView#handleMouseDown}
	 * to update the selection inside the calendar.
	 *
	 * @param {Event} e the mouseup event
	 */
	onMouseUp : function(e)
	{
		var data = this.dragData;

		// If the dragData still contains the origEvent and target, then onInitDrag has not been
		// called which means the user only clicked on the row and we must update
		// our selection.
		if (data && data.origEvent) {
			this.calendar.onMouseDown(data.origEvent, data.target);

			delete data.origEvent;
		}

		this.hideProxy();

		Zarafa.calendar.ui.CalendarViewDragZone.superclass.onMouseUp.apply(this, arguments);
	},

	/**
	 * An empty immplementation. Implement this to provide behaviour after a repair of an invalid drop. An implementation might highlight
	 * the selected rows to show that they have not been dragged.
	 * @private
	 */
	afterRepair : function()
	{
		this.dragging = false;
	},

	/**
	 * <p>An empty implementation. Implement this to provide coordinates for the drag proxy to slide back to after an invalid drop.</p>
	 * <p>Called before a repair of an invalid drop to get the XY to animate to.</p>
	 * @param {EventObject} e The mouse up event
	 * @return {Array} The xy location (e.g. [100, 200])
	 * @private
	 */
	getRepairXY : function(e, data)
	{
		return false;
	},

	/**
	 * Called when the dragging has ended. This will call the
	 * {@link Zarafa.calendar.ui.AbstractCalendarView#onMouseUp}
	 * on the {@link #calendar} to which the dragged appointment belongs.
	 *
	 * @param {Object} data The data which was used during the drag
	 * @param {Ext.EventObject} e The event object
	 * @private
	 */
	onEndDrag : function(data, e)
	{
		var appointment = data.target;
		this.calendar.onEndDrag(e, appointment);
		this.calendar.onMouseUp(e, appointment);
	},

	/**
	 * Called when an item has been dropped
	 * @param {Ext.dd.DropZone} dd The zone in which the item was dropped
	 * @param {Ext.EventObject} e The event object
	 * @param {String} id The id of the Ext.Element where the item was dropped
	 * @private
	 */
	onValidDrop : function(dd, e, id)
	{
		// fire drag drop?
		this.hideProxy();
	},

	/**
	 * Called when an item has been dropped on an invalid location
	 * @param {Ext.EventObject} e The event object
	 * @param {String} id The id of the Ext.Element where the item was dropped
	 * @private
	 */
	beforeInvalidDrop : function(e, id)
	{
	},

	/**
	 * Search for the {@link Zarafa.calendar.ui.AppointmentView appointment} on which the
	 * given {@link Ext.EventObject event} tool place. This requires {@link Zarafa.calendar.ui.AppointmentView#eventOverBody}
	 * to be true when {@link #headerMode} is false, otherwise {@link Zarafa.calendar.ui.AppointmentView#eventOverHeader} must
	 * be true.
	 * @param {Ext.EventObject} e The event from where we obtain the appointment
	 * @return {Zarafa.calendar.ui.AppointmentView} The appointment on which the event occurred
	 * @private
	 */
	getAppointmentFromEvent : function(e)
	{
		var appointments = this.calendar.appointments;

		for (var i = 0, len = appointments.length; i < len; i++) {
			var app = appointments[i];

			if (this.headerMode !== true) {
				if (app.eventOverBody(e)) {
					return app;
				}
			} else {
				if (app.eventOverHeader(e)) {
					return app;
				}
			}
		}

		return undefined;
	},

	/**
	 * This function checks over which part of the appointment the event took place,
	 * and will return the appropriate {@link Zarafa.calendar.data.DragStates} value.
	 * @param {Ext.EventObject} e The event
	 * @param {Zarafa.calendar.ui.AppointmentView} appointment The appointment to check
	 * @return {Zarafa.calendar.data.DragStates} The Drag State for the current action
	 * @private
	 */
	getDragStateFromEvent : function(e, appointment)
	{
		if (this.headerMode !== true) {
			if (!Ext.isDefined(appointment)) {
				return Zarafa.calendar.data.DragStates.SELECTING;
			} else if (appointment.eventOverBodyStartHandle(e)) {
				return Zarafa.calendar.data.DragStates.RESIZING_START;
			} else if (appointment.eventOverBodyDueHandle(e)) {
				return Zarafa.calendar.data.DragStates.RESIZING_DUE;
			} else {
				return Zarafa.calendar.data.DragStates.DRAGGING;
			}
		} else {
			if (!Ext.isDefined(appointment)) {
				return Zarafa.calendar.data.DragStates.SELECTING;
			} else if (appointment.eventOverHeaderStartHandle(e)) {
				return Zarafa.calendar.data.DragStates.RESIZING_START;
			} else if (appointment.eventOverHeaderDueHandle(e)) {
				return Zarafa.calendar.data.DragStates.RESIZING_DUE;
			} else {
				return Zarafa.calendar.data.DragStates.DRAGGING;
			}
		}
	}
});