define(['scripts/knockout-3.3.0', 'mod/properties/propertyutils', 'jqx/jqxbuttons', 'jqx/jqxslider'],
	function(ko, PropertyUtils, jqxslider) {

    function PropertySliderViewModel(params, element) {
		this.utils = new PropertyUtils();
		this.utils.Init(this, params, element);

		var isSliding = false;
		var $slider = $(element).find('.jqxSlider');
		var maxDigitsNumber = parseInt(params.Attributes["maxdigitsnumber"]) || 5;

		// Looks for min/max to compute the ticks' frequency accordingly.
		var min = parseFloat(params.Attributes["min"]) || 0.0;
		var max = parseFloat(params.Attributes["max"]) || 10.0;
		var numberOfTicks = 5;
		var digitsNumber = 5;

		$slider.jqxSlider({
			showButtons:false,
			ticksPosition:'top',
			width: '150px',
			mode: params.Control === "integer" ? 'fixed' : 'default',
			min: min,
			max: max,
			ticksFrequency: Math.ceil((max - min) / numberOfTicks),
			showTicks: false
		});

		params.sliderValue.extend({notify: 'always'});

		// Variable used to store the timer before a call to NotifyACT.
		var debounceNotifyACT;

		// Notifies ACT that the property's value has changed after a 500 ms timer.
		// If the function is called before the end of the timer, it is reset.
		var NotifyACT = function (){
			if (!$slider.jqxSlider('disabled')) {
				var stringValue = "" + params.sliderValue();
				if (params.DecimalSeparator === ',') {
					stringValue = stringValue.replace('.',',');
				}
				clearTimeout(debounceNotifyACT);
				debounceNotifyACT = setTimeout(function() {
					params.value(stringValue);
				}, 500);
			}
		};

		// Registers any action that can lead to the thumb being moved. If counter equals 0
		// then the user is not moving the thumb anymore be it with the mouse or the keyboard.
		var counter = 0;
		var registeredInputs = [];
		$slider.IncrementCounter = function(inputCode)
		{
			if (inputCode) {
				for (var i = 0; i < registeredInputs.length; i++) {
					if (registeredInputs[i] === inputCode)
					{
						// Key is being held down or mousedown has been registered several times.
						return true;
					}
				}
				registeredInputs.push(inputCode);
			}

			counter++;
		};

		$slider.DecrementCounter = function(inputCode)
		{
			if (inputCode) {
				for (var i = 0; i < registeredInputs.length; i++) {
					if (registeredInputs[i] === inputCode)
					{
						// The key has been released, removes its code from the array.
						registeredInputs.splice(i, 1);
					}
				}
			}

			if (counter > 0) {
				counter --;
			}
		};
		// Add listener to some events from the user.
		$.jqx.addHandler($slider.jqxSlider('host'), 'keydown', function(event) {
			if ($slider.FilterKeyCode(event.keyCode)) {
				$slider.IncrementCounter(event.keyCode);
				NotifyACT();
			}
		});

		$.jqx.addHandler($slider.jqxSlider('host'), 'keyup', function(event) {
			if ($slider.FilterKeyCode(event.keyCode)) {
				$slider.DecrementCounter(event.keyCode);
				params.sliderValue(params.sliderValue());
				NotifyACT();
			}
		});

		$slider.FilterKeyCode = function(keyCode) {
			var code = parseInt(keyCode);
			// Down, Left, Up, Right, Home, End.
			return code === 40 || code === 37 || code === 38 || code === 39 || code === 36 ||
				code === 35;
		}
		// On a touch device the events are different.
		var mousedownEvent = $slider.jqxSlider('_isTouchDevice') ?
			$.jqx.mobile.getTouchEventName('touchstart') : 'mousedown';
		var mouseupEvent = $slider.jqxSlider('_isTouchDevice') ?
			$.jqx.mobile.getTouchEventName('touchend') : 'mouseup';

		$.jqx.addHandler($slider.jqxSlider('_slider').right, mousedownEvent, function(event) {
			$slider.IncrementCounter('mouse');
			NotifyACT();
		});

		$.jqx.addHandler($slider.jqxSlider('_slider').left, mousedownEvent, function(event) {
			$slider.IncrementCounter('mouse');
			NotifyACT();
		});

		$.jqx.addHandler($slider.jqxSlider('_track'), mousedownEvent, function(event) {
			$slider.IncrementCounter('mouse');
			// Since the event 'slide' is raised before the call to this function when the user
			// directly clicks on the track the value is rounded here. If it was done during the
			// 'slide' event for counter === 0 a value entered by the user would also be rounded.
			params.sliderValue(parseFloat(new Number(params.sliderValue()).toPrecision(maxDigitsNumber)));
			NotifyACT();
		});

		$.jqx.addHandler($(document), mouseupEvent, function(event) {
			var previousState = counter;
			$slider.DecrementCounter('mouse');
			// Forces an update
			if (counter === 0 && previousState > 0) {				
				NotifyACT();
			}
		});

		$slider.on('slide', function (event) {
		 	if (counter !== 0) {	
		 		// Thumb is being moved	 		
     			var value = event.args.value;
     			$slider.jqxSlider('setValue', parseFloat(new Number(value).toPrecision(maxDigitsNumber)));
				NotifyACT();
		 	}
 		});

        // Keeps the update local, only between the slider widget and the text input, while the user is interacting with the slider.
        params.sliderValue.subscribe(function(newVal) {
        	if (typeof newVal === 'string' || newVal instanceof String) {
        		// Value entered by the user in the input field. Since the value stored in sliderValue should always be
        		// a float we parse it here.
        		if (params.DecimalSeparator === ',') {
        			newVal = newVal.replace(',','.');
        		}
        		params.sliderValue(parseFloat(newVal));
        		// Notifies ACT
				NotifyACT();
        	}
        }.bind(params));


        params.value.subscribe(function(newVal) {
        	// sliderValue should always be a float (jqxSlider does not check if its value is a float, we need
        	// to do the conversion every time) while params.value should always be a string since ACT expects a 
        	// string when the value is sent back to C#.
        	if (typeof newVal === 'string' || newVal instanceof String) {
        		if (params.DecimalSeparator === ',') {
        			newVal = newVal.replace(',','.');
        		}
        		newVal = parseFloat(newVal);
        	}
        	// We could use the != operator but since the decimal separator might vary it is unsafe hence
        	// the conversion above in order to use !==.
            if (params.sliderValue() !== newVal) {
                params.sliderValue(newVal);
            }
        });

		params.onResize = function() {
			var totalWidth = $(element).find(".controlmessage").width();

			var $input = $(element).find("input");
			var inputWidth = $input.outerWidth(true);

			var sliderWidth = (totalWidth - inputWidth - 3) + "px";
			$slider.jqxSlider("width", sliderWidth);
		}

		params.unit = ko.observable(params.Unit);

		this.utils.Ready(params);
		return params;
    }

    return {
		createViewModel: function(params, componentInfo) {
			return PropertySliderViewModel(params, componentInfo.element);
		}
	};
});
