/*
	Copyright (c) 2004-2006, The Dojo Foundation
	All Rights Reserved.

	Licensed under the Academic Free License version 2.1 or above OR the
	modified BSD license. For more information on Dojo licensing, see:

		http://dojotoolkit.org/community/licensing.shtml
*/

/**
 * Slider Widget.
 *
 * The slider widget comes in three forms:
 *  1. Base Slider widget which supports movement in x and y dimensions
 *  2. Vertical Slider (SliderVertical) widget which supports movement
 *     only in the y dimension.
 *  3. Horizontal Slider (SliderHorizontal) widget which supports movement
 *     only in the x dimension.
 *
 * The key objects in the widget are:
 *  - a container div which displays a bar in the background (Slider object)
 *  - a handle inside the container div, which represents the value
 *    (sliderHandle DOM node)
 *  - the object which moves the handle (handleMove is of type
 *    SliderDragMoveSource)
 *
 * The values for the slider are calculated by grouping pixels together,
 * based on the number of values to be represented by the slider.
 * The number of pixels in a group is called the valueSize
 *  e.g. if slider is 150 pixels long, and is representing the values
 *       0,1,...10 then pixels are grouped into lots of 15 (valueSize), where:
 *         value 0 maps to pixels  0 -  7
 *               1                 8 - 22
 *               2                23 - 37 etc.
 * The accuracy of the slider is limited to the number of pixels
 * (i.e tiles > pixels will result in the slider not being able to
 *  represent some values).
 *
 * Technical Notes:
 *  - 3 widgets exist because the framework caches the template in
 *    dojo.widget.fillFromTemplateCache (which ignores the changed URI)
 *
 * Todo:
 *  - Setting of initial value currently doesn't work, because the one or
 *    more of the offsets, constraints or containing block are not set and
 *    are required to set the valueSize is not set before fillInTemplate
 *    is called.
 *  - Issues with dragging handle when page has been scrolled
 *  -
 *
 * References (aka sources of inspiration):
 *  - http://dojotoolkit.org/docs/fast_widget_authoring.html
 *  - http://dojotoolkit.org/docs/dojo_event_system.html
 *
 * @author Marcel Linnenfelser (m.linnen@synflag.de)
 * @author Mathew Pole (mathew.pole@ebor.com)
 *
 * $Id: $
 */

// tell the package system what functionality is provided in this module (file)
// (note that the package system works on modules, not the classes)
dojo.provide("dojo.widget.html.Slider");

// load dependencies
dojo.require("dojo.event.*");
dojo.require("dojo.dnd.*");
// dojo.dnd.* doesn't include this package, because it's not in __package__.js
dojo.require("dojo.dnd.HtmlDragMove");
dojo.require("dojo.widget.*");
dojo.require("dojo.style");


/**
 * Define the two dimensional slider widget class.
 */
dojo.widget.defineWidget (
	"dojo.widget.html.Slider",
	dojo.widget.HtmlWidget,
	{
		// over-ride some defaults
		isContainer: false,
		widgetType: "Slider",

		// useful properties (specified as attributes in the html tag)
		// number of values to be represented by slider in the horizontal direction
		valuesX: 10,
		// number of values to be represented by slider in the vertical direction
		valuesY: 10,
		// can values be changed on the x (horizontal) axis?
		isEnableX: true,
		// can values be changed on the y (vertical) axis?
		isEnableY: true,
		// value size (pixels) in the x dimension
		valueSizeX: 0.0,
		// value size (pixels) in the y dimension
		valueSizeY: 0.0,
		// initial value in the x dimension
		initialValueX: 0,
		// initial value in the y dimension
		initialValueY: 0,

		// do we allow the user to click on the slider to set the position?
		// (note: dojo's infrastructor will convert attribute to a boolean)
		clickSelect: true,
		// should the handle snap to the grid or remain where it was dragged to?
		// (note: dojo's infrastructor will convert attribute to a boolean)
		snapToGrid: false,
		// should the value change while you are dragging, or just after drag finishes?
		activeDrag: false,

		templateCssPath: dojo.uri.dojoUri ("src/widget/templates/HtmlSlider.css"),
		templatePath: dojo.uri.dojoUri ("src/widget/templates/HtmlSlider.html"),

		// our DOM nodes
		sliderHandle: null,

		// private attributes
		// This is set to true when a drag is started, so that it is not confused
		// with a click
		isDragInProgress: false,


		// This function is called when the template is loaded
		fillInTemplate: function ()
		{
			// dojo.debug ("fillInTemplate - className = " + this.domNode.className);

			// setup drag-n-drop for the sliderHandle
			this.handleMove = new dojo.widget.html.SliderDragMoveSource (this.sliderHandle);
			this.handleMove.setParent (this);
			dojo.event.connect(this.handleMove, "onDragMove", this, "onDragMove");
			dojo.event.connect(this.handleMove, "onDragEnd", this, "onDragEnd");
			dojo.event.connect(this.handleMove, "onClick", this, "onClick");

			// keep the slider handle inside it's parent container
			this.handleMove.constrainToContainer = true;

			if (this.clickSelect) {
				dojo.event.connect (this.domNode, "onclick", this, "setPosition");
			}

			if (this.isEnableX && this.initialValueX > 0) {
				alert("setting x to " + this.initialValueX);
				this.setValueX (this.initialValueX);
			}
			if (this.isEnableY && this.initialValueY > 0) {
				this.setValueY (this.initialValueY);
			}
		},


		// Move the handle (in the x dimension) to the specified value
		setValueX: function (value) {
			if (0.0 == this.valueSizeX) {
				this.valueSizeX = this.handleMove.calcValueSizeX ();
			}
			if (value > this.valuesX) {
				value = this.valuesX;
			}
			else if (value < 0) {
				value = 0;
			}
			//dojo.debug ("value = " + value, ", valueSizeX = " + this.valueSizeX);
			this.handleMove.domNode.style.left = (value * this.valueSizeX) + "px";
		},


		// Get the number of the value that matches the position of the handle
		getValueX: function () {
			if (0.0 == this.valueSizeX) {
				this.valueSizeX = this.handleMove.calcValueSizeX ();
			}
			return Math.round (dojo.style.getPixelValue (this.handleMove.domNode, "left") / this.valueSizeX);
		},


		// set the slider to a particular value
		setValueY: function (value) {
			if (0.0 == this.valueSizeY) {
				this.valueSizeY = this.handleMove.calcValueSizeY ();
			}
			if (value > this.valuesY) {
				value = this.valuesY;
			}
			else if (value < 0) {
				value = 0;
			}

			this.handleMove.domNode.style.top = (value * this.valueSizeY) + "px";
		},


		// Get the number of the value that the matches the position of the handle
		getValueY: function () {
			if (0.0 == this.valueSizeY) {
				this.valueSizeY = this.handleMove.calcValueSizeY ();
			}
			return Math.round (dojo.style.getPixelValue (this.handleMove.domNode, "top") / this.valueSizeY);
		},


		// set the position of the handle
		setPosition: function (e) {
			//dojo.debug ("Slider#setPosition - e.clientX = " + e.clientX
			//            + ", e.clientY = " + e.clientY);
			if (this.isDragInProgress) {
				this.isDragInProgress = false;
			}

			var offset = dojo.html.getScrollOffset();
			var parent = dojo.style.getAbsolutePosition(this.domNode, true);

			if (this.isEnableX) {
				var x = offset.x + e.clientX - parent.x;
				if (x > this.domNode.offsetWidth) {
					x = this.domNode.offsetWidth;
				}
				if (this.snapToGrid && x > 0) {
					if (0.0 == this.valueSizeX) {
						this.valueSizeX = this.handleMove.calcValueSizeX ();
					}
					x = this.valueSizeX * (Math.round (x / this.valueSizeX));
				}
				this.handleMove.domNode.style.left = x + "px";
			}
			if (this.isEnableY) {
				var y = offset.y + e.clientY - parent.y;
				if (y > this.domNode.offsetHeight) {
					y = this.domNode.offsetHeight;
				}
				if (this.snapToGrid && y > 0) {
					if (0.0 == this.valueSizeY) {
						this.valueSizeY = this.handleMove.calcValueSizeY ();
					}
					y = this.valueSizeY * (Math.round (y / this.valueSizeY));
				}
				this.handleMove.domNode.style.top = y + "px";
			}
		},

		onDragMove: function(){
			this.onValueChanged(this.getValueX(), this.getValueY());
		},

		onClick: function(){
			this.onValueChanged(this.getValueX(), this.getValueY());
		},

		onValueChanged: function(x, y){
		}
	}
);


/* ------------------------------------------------------------------------- */


/**
 * Define the horizontal slider widget class.
 */
dojo.widget.defineWidget (
	"dojo.widget.html.SliderHorizontal",
	dojo.widget.html.Slider,
	{
		widgetType: "SliderHorizontal",

		value: 0,

		isEnableY: false,
		templatePath: dojo.uri.dojoUri ("src/widget/templates/HtmlSliderHorizontal.html"),

		postMixInProperties: function(){
			this.initialValue = this.value;
		},

		// wrapper for getValueX
		getValue: function () {
			return this.getValueX ();
		},

		// wrapper for setValueX
		setValue: function (value) {
			this.setValueX (value);
			this.onValueChanged(value);
		},

		onDragMove: function(){
			if(this.activeDrag){
				this.onValueChanged(this.getValue());
			}
		},

		onDragEnd: function(){
			if(!this.activeDrag){
				this.onValueChanged(this.getValue());
			}
		},

		onClick: function(){
			this.onValueChanged(this.getValue());
		},

		onValueChanged: function(value){
			this.value=value;
		}
	}
);


/* ------------------------------------------------------------------------- */


/**
 * Define the vertical slider widget class.
 */
dojo.widget.defineWidget (
	"dojo.widget.html.SliderVertical",
	dojo.widget.html.Slider,
	{
		widgetType: "SliderVertical",

		value: 0,

		isEnableX: false,
		templatePath: dojo.uri.dojoUri ("src/widget/templates/HtmlSliderVertical.html"),

		postMixInProperties: function(){
			this.initialValueY = this.value;
		},

		// wrapper for getValueY
		getValue: function () {
			return this.getValueY ();
		},

		// wrapper for setValueY
		setValue: function (value) {
			this.setValueY (value);
		},

		onDragMove: function(){
			if(this.activeDrag){
				this.onValueChanged(this.getValue());
			}
		},

		onDragEnd: function(){
			if(!this.activeDrag){
				this.onValueChanged(this.getValue());
			}
		},

		onClick: function(){
			this.onValueChanged(this.getValue());
		},

		onValueChanged: function(value){
			this.value=value;
		}
	}
);


/* ------------------------------------------------------------------------- */


/**
 * This class extends the HtmlDragMoveSource class to provide
 * features for the slider handle.
 */
dojo.declare (
	"dojo.widget.html.SliderDragMoveSource",
	dojo.dnd.HtmlDragMoveSource,
{
	isDragInProgress: false,
	slider: null,


	/** Setup the handle for drag
	 *  Extends dojo.dnd.HtmlDragMoveSource by creating a SliderDragMoveSource */
	onDragStart: function (e) {
		this.isDragInProgress = true;
		this.constrainToContainer = true;

		var dragObj = this.createDragMoveObject ();
		var constraints = null;


		dojo.event.connect (dragObj, "onDragMove", this, "onDragMove");

		return dragObj;
	},


	onDragMove: function (e) {
		// placeholder to enable event connection
	},


	createDragMoveObject: function () {
		//dojo.debug ("SliderDragMoveSource#createDragMoveObject - " + this.slider);
		var dragObj = new dojo.widget.html.SliderDragMoveObject (this.dragObject, this.type);
		dragObj.slider = this.slider;

		// this code copied from dojo.dnd.HtmlDragSource#onDragStart
		if (this.dragClass) {
			dragObj.dragClass = this.dragClass;
		}
		if (this.constrainToContainer) {
			dragObj.constrainTo(this.constrainingContainer || this.domNode.parentNode);
		}
		return dragObj;
	},


	setParent: function (slider) {
		this.slider = slider;
	},


	calcValueSizeX: function () {
		var dragObj = this.createDragMoveObject ();
		dragObj.containingBlockPosition = dragObj.domNode.offsetParent ?
		dojo.style.getAbsolutePosition(dragObj.domNode.offsetParent) : {x:0, y:0};

		var constraints = dragObj.getConstraints ();
		return (constraints.maxX - constraints.minX) / this.slider.valuesX;
	},


	calcValueSizeY: function () {
		var dragObj = this.createDragMoveObject ();
		dragObj.containingBlockPosition = dragObj.domNode.offsetParent ?
		dojo.style.getAbsolutePosition(dragObj.domNode.offsetParent) : {x:0, y:0};
		var constraints = dragObj.getConstraints ();
		return (constraints.maxY - constraints.minY) / this.slider.valuesY;
	}
});


/* ------------------------------------------------------------------------- */


/**
 * This class extends the HtmlDragMoveObject class to provide
 * features for the slider handle.
 */
dojo.declare (
	"dojo.widget.html.SliderDragMoveObject",
	dojo.dnd.HtmlDragMoveObject,
{
	// reference to dojo.widget.html.Slider
	slider: null,

	/** Moves the node to follow the mouse.
	 *  Extends functon HtmlDragObject by adding functionality to snap handle
	 *  to a discrete value */
	onDragMove: function (e) {
		if (this.slider.isEnableX && 0.0 == this.slider.valueSizeX) {
			this.slider.valueSizeX = (this.constraints.maxX - this.constraints.minX) / this.slider.valuesX;
		}
		if (this.slider.isEnableY && 0.0 == this.slider.valueSizeY) {
			this.slider.valueSizeY = (this.constraints.maxY - this.constraints.minY) / this.slider.valuesY;
		}

		this.updateDragOffset ();

		var x = this.dragOffset.x + e.pageX;
		var y = this.dragOffset.y + e.pageY;

		if (this.constrainToContainer) {
			if (x < this.constraints.minX) { x = this.constraints.minX; }
			if (y < this.constraints.minY) { y = this.constraints.minY; }
			if (x > this.constraints.maxX) { x = this.constraints.maxX; }
			if (y > this.constraints.maxY) { y = this.constraints.maxY; }
		}

		if (this.slider.isEnableX) {
			var selectedValue = 0;
			if (x > 0) {
				selectedValue = Math.round (x / this.slider.valueSizeX);
			}
			// dojo.debug ("x = " + x + ", valueSize = " + valueSize
			//             + ", selectedValue = " + selectedValue);
			x = (selectedValue * this.slider.valueSizeX);
		}

		if (this.slider.isEnableY) {
			var selectedValue = 0;
			if (y > 0) {
				selectedValue = Math.round (y / this.slider.valueSizeY);
			}
			y = (selectedValue * this.slider.valueSizeY);
		}

		this.setAbsolutePosition (x, y);
	}
});
