/* Copyright (c) 2011, Geert Bergman (geert@scrivo.nl)
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice,
 *    this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution.
 * 3. Neither the name of "Scrivo" nor the names of its contributors may be
 *    used to endorse or promote products derived from this software without
 *    specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 *
 * $Id: Dragger.js 616 2013-04-22 23:48:38Z geert $
 */

"use strict";

SUI.Dragger = SUI.defineClass(
	/** @lends SUI.Dragger.prototype */{

	/** @ignore */ baseClass: SUI.Box,

	/**
	 * @class
	 * A dragger is a box that you can drag with the mouse between certain
	 * restriction. It is a utility component that is used when certain parts
	 * of a component need to make draggable. You can set the restrictions and
	 * direction you want to drag and the dragger will follow the mouse within
	 * the allowed region.
	 *
	 * @augments SUI.Box
	 *
	 * @description
	 * Create a dragger component.
	 *
	 * @constructs
	 * @param see base class
	 */
	initializer: function(arg) {

		SUI.Dragger.initializeBase(this, arg);

		// set initial values to window size
		this._xMin = 0;
		this._yMin = 0;
		this._xMax = SUI.browser.viewportWidth;
		this._yMax = SUI.browser.viewportHeight;
	},

	/**
	 * Constant to use when you want to drag into the east direction.
	 */
	EAST: "e",

	/**
	 * Constant to use when you want to drag into the horizontal direction.
	 */
	HORIZONTAL: "h",

	/**
	 * Constant to use when you want to drag into the north direction.
	 */
	NORTH: "n",

	/**
	 * Constant to use when you want to drag into the south direction.
	 */
	SOUTH: "s",

	/**
	 * Constant to use when you want to drag into the vertical direction.
	 */
	VERTICAL: "v",

	/**
	 * Constant to use when you want to drag into the west direction.
	 */
	WEST: "w",

	/**
	 * Set the drag direction.
	 * @param {const} dir Direction into which to drag.
	 */
	direction: function(dir) {
		this._dir = dir;
	},

	/**
	 * The onEndDrag event handler: this method will be called when the user
	 * stops dragging.
	 */
	onEndDrag: function() {
	},

	/**
	 * The onDrag event handler: this method will be called while the user
	 * is dragging.
	 */
	onDrag: function() {
	},

	/**
	 * Start the dragging action. This will initialize the dragging action
	 * and will typically be called in the onmousedown handler of the box
	 * that must trigger the drag action.
	 * @param {SUI.Event} e An onmousedown event.
	 */
	start: function(e) {

		// make sure the dragger is on top of everything
		this.el().style.zIndex = 1000;

		// get the current mouse position
		this._xPos = SUI.browser.getX(e.event);
		this._yPos = SUI.browser.getY(e.event);

		// and the start positions/dimension
		this._lStart = this.left();
		this._hStart = this.height();
		this._tStart = this.top();
		this._wStart = this.width();

		var that = this;

		// set the onmousemove event handler to _drag()
		SUI.browser.addEventListener(
			document,
			"mousemove",
			this._ehDrag = function(e) {
				if (!that._drag(new SUI.Event(this, e))) {
					SUI.browser.noPropagation(e);
				}
			}
		);

		// set the onmouseup event handler to _endDrag()
		SUI.browser.addEventListener(
			document,
			"mouseup",
			this._ehEndDrag = function(e) {
				if (!that._endDrag()) {
					SUI.browser.noPropagation(e);
				}
			}
		);
	},

	/**
	 * Set the maximum x position (at document level) for the dragging motion.
	 * @param {int} xMax maximum x position (at document level) for the
	 *    dragging motion.
	 */
	xMax: function(xMax) {
		this._xMax = xMax;
	},

	/**
	 * Set the minimum x position (at document level) for the dragging motion.
	 * @param {int} xMin minimum x position (at document level) for the
	 *    dragging motion.
	 */
	xMin: function(xMin) {
		this._xMin = xMin;
	},

	/**
	 * Set the maximum y position (at document level) for the dragging motion.
	 * @param {int} yMax maximum y position (at document level) for the
	 *    dragging motion.
	 */
	yMax: function(yMax) {
		this._yMax = yMax;
	},

	/**
	 * Set the minimum y position (at document level) for the dragging motion.
	 * @param {int} yMin minimum y position (at document level) for the
	 *    dragging motion.
	 */
	yMin: function(yMin) {
		this._yMin = yMin;
	},

	// direction for dragging motion
	_dir: "",

	// reference to the drag function (for unregistering)
	_ehDrag: null,

	// reference to the endDrag function (for unregistering)
	_ehEndDrag: null,

	// dragger height at start dragging action
	_hStart: 0,

	// dragger left at start dragging action
	_lStart: 0,

	// dragger top at start dragging action
	_tStart: 0,

	// dragger width at start dragging action
	_wStart: 0,

	// maximum x position for the dragging action
	_xMax: 0,

	// minimum x position for the dragging action
	_xMin: 0,

	// current x position during the dragging action
	_xPos: 0,

	// maximum y position for the dragging action
	_yMax: 0,

	// minimum y position for the dragging action
	_yMin: 0,

	// current y position during the dragging action
	_yPos: 0,

	/* Function to keep variable value within boundaries.
	 */
	_bounds: function(v, min, max) {
		return v < min ? min : v > max ? max : v;
	},

	/* Drag the component: set the new position and size based on the current
	 * mouse position.
	 */
	_drag: function(e) {

		// get the current mouse position
		var x = SUI.browser.getX(e.event);
		var y = SUI.browser.getY(e.event);

		// if we're dragging in horizontal direction just set the left position
		// of the dragger
		if (this._dir.indexOf("h") != -1) {
			var t = this._lStart + x - this._xPos;
			t = this._bounds(t, this._xMin, this._xMax);
			this.left(t);
		}

		// if we're dragging in vertical direction just set the top position
		// of the dragger
		if (this._dir.indexOf("v") != -1) {
			var l = this._tStart + y - this._yPos;
			l = this._bounds(l, this._yMin, this._yMax);
			this.top(l);
		}

		// if we're dragging into the east direction just set the width
		// of the dragger
		if (this._dir.indexOf("e") != -1) {
			var w = this._wStart + x - this._xPos;
			w = this._bounds(w, this._xMin, this._xMax);
			this.width(w);
		}

		// if we're dragging into the west direction set the width and the
		// left position of the dragger
		if (this._dir.indexOf("w") != -1) {
			var w = this._wStart - x + this._xPos;
			w = this._bounds(w, this._xMin, this._xMax);
			var l = this._lStart - (w - this._wStart);
			this.width(w);
			this.left(l);
		}

		// if we're dragging into the north direction set the height and the
		// top position of the dragger
		if (this._dir.indexOf("n") != -1) {
			var h = this._hStart - y + this._yPos;
			h = this._bounds(h, this._yMin, this._yMax);
			var t = this._tStart - (h - this._hStart);
			this.height(h);
			this.top(t);
		}

		// if we're dragging into the south direction just set the height
		// of the dragger
		if (this._dir.indexOf("s") != -1) {
			var h = this._hStart + y - this._yPos;
			h = this._bounds(h, this._yMin, this._yMax);
			this.height(h);

		}

		// now set the CSS dimensions of the dragger
		this.setDim();

		// ... and call the onDrag event handler
		this.callListener("onDrag");
	},

	/* End the dragging motion
	 */
	_endDrag: function() {

		// Unregister the event handlers ...
		SUI.browser.removeEventListener(document, "mousemove", this._ehDrag);
		SUI.browser.removeEventListener(document, "mouseup", this._ehEndDrag);

		// ... and call the onEndDrag event handler
		this.callListener("onEndDrag");
	}

});