/**
 * 
 * Find more about the Spinning Wheel function at
 * http://cubiq.org/spinning-wheel-on-webkit-for-iphone-ipod-touch/11
 *
 * Copyright (c) 2009 Matteo Spinelli, http://cubiq.org/
 * Released under MIT license
 * http://cubiq.org/dropbox/mit-license.txt
 * 
 * Version 1.4 - Last updated: 2009.07.09
 * 
 */

var SpinningWheel = {
	cellHeight: 44,
	friction: 0.003,
	slotData: [],

	/**
	 *
	 * Event handler
	 *
	 */

	handleEvent: function (e) {
		if (e.type == 'touchstart' || e.type == 'mousedown') {
			this.lockScreen(e);
			
			if (e.currentTarget.id == 'sw-cancel' || e.currentTarget.id == 'sw-done') {
				this.tapDown(e);
			} else if (e.currentTarget.id == 'sw-frame') {
				this.scrollStart(e);
			}
		} else if (e.type == 'touchmove' || e.type == 'mousemove') {
			this.lockScreen(e);
			
			if (e.currentTarget.id == 'sw-cancel' || e.currentTarget.id == 'sw-done') {
				this.tapCancel(e);
			} else if (e.currentTarget.id == 'sw-frame') {
				this.scrollMove(e);
			}
		} else if (e.type == 'touchend' || e.type == 'mouseup') {
			if (e.currentTarget.id == 'sw-cancel' || e.currentTarget.id == 'sw-done') {
				this.tapUp(e);
			} else {
				this.scrollEnd(e);
			}
		} else if (e.type == 'webkitTransitionEnd') {
			if (e.target.id == 'sw-wrapper') {
				this.destroy();
			} else {
			    this.backWithinBoundaries(e);
			}
		} else if (e.type == 'orientationchange') {
			this.onOrientationChange(e);
		} else if (e.type == 'scroll') {
			this.onScroll(e);
		}
	},


	/**
	 *
	 * Global events
	 *
	 */

	onOrientationChange: function (e) {
		window.scrollTo(0, 0);
		//this.swWrapper.style.top = window.innerHeight + window.pageYOffset + 'px';
		this.calculateSlotsWidth();
	},
	
	onScroll: function (e) {
		//this.swWrapper.style.top = window.innerHeight + window.pageYOffset + 'px';
	},

	lockScreen: function (e) {
		e.preventDefault();
		e.stopPropagation();
	},


	/**
	 *
	 * Initialization
	 *
	 */

	reset: function () {
		this.slotEl = [];

		this.activeSlot = null;
		
		this.swWrapper = undefined;
		this.swSlotWrapper = undefined;
		this.swSlots = undefined;
		this.swFrame = undefined;
		
		// Scroll to top to make sure the DatePicker is always visible
		window.scrollTo(0, 0);
	},

	calculateSlotsWidth: function () {
		var div = this.swSlots.getElementsByTagName('div');
		
		for (var i = 0; i < div.length; i += 1) {
			this.slotEl[i].slotWidth = div[i].offsetWidth;
		}
	},
	
	create: function () {
		var i, l, out, ul, div;

		this.reset();	// Initialize object variables

		// Create the Spinning Wheel main wrapper
		div = document.createElement('div');
		div.id = 'sw-wrapper';
		div.className = 'sw-wrapper';
		div.innerHTML = '<div id="sw-slots-wrapper"><div id="sw-slots"></' + 'div></' + 'div><div id="sw-footer"><div id="sw-done">Done</' + 'div></' + 'div><div id="sw-frame"></' + 'div>';
    
        div.style.visibility = 'hidden';
        document.body.appendChild(div);

		this.swWrapper = document.getElementById('sw-wrapper');							// The SW wrapper
		this.swSlotWrapper = document.getElementById('sw-slots-wrapper');		// Slots visible area
		this.swSlots = document.getElementById('sw-slots');						// Pseudo table element (inner wrapper)
		this.swFrame = document.getElementById('sw-frame');						// The scrolling controller
		
		// Create HTML slot elements
		for (l = 0; l < this.slotData.length; l += 1) {
			// Create the slot
			ul = document.createElement('ul');
			out = '';
			
			for (i in this.slotData[l].values) {
				out += '<li>' + this.slotData[l].values[i] + '<' + '/li>';
			}
			ul.innerHTML = out;
			ul.id = (($(out).length == 28) || ($(out).length == 30) || ($(out).length == 31)) ? 'days' : '';

			div = document.createElement('div');		// Create slot container
			div.className = this.slotData[l].style;		// Add styles to the container
			div.appendChild(ul);
	
			// Append the slot to the wrapper
			this.swSlots.appendChild(div);
			
			ul.slotPosition = l;			// Save the slot position inside the wrapper
			ul.slotYPosition = 0;
			ul.slotWidth = 0;
			ul.slotMaxScroll = this.swSlotWrapper.clientHeight - ul.clientHeight - 86;
			ul.style.webkitTransitionTimingFunction = 'cubic-bezier(0, 0, 0.2, 1)';		// Add default transition
			
			this.slotEl.push(ul);			// Save the slot for later use
			
			// Place the slot to its default position (if other than 0)
			if (this.slotData[l].defaultValue) {
				this.scrollToValue(l, this.slotData[l].defaultValue);	
			}
		}
		
		this.calculateSlotsWidth();

		// Global events
		document.addEventListener('touchstart', this, false);			// Prevent page scrolling
		document.addEventListener('touchmove', this, false);			// Prevent page scrolling
		
		document.addEventListener('mousedown', this, false);     // Prevent page scrolling
		document.addEventListener('mousemove', this, false);      // Prevent page scrolling
		
		window.addEventListener('orientationchange', this, true);		// Optimize SW on orientation change
		window.addEventListener('scroll', this, true);				// Reposition SW on page scroll

		// Done button's events
		document.getElementById('sw-done').addEventListener('touchstart', this, false);
		document.getElementById('sw-done').addEventListener('mousedown', this, false);
		
		// Add scrolling to the slots
		this.swFrame.addEventListener('touchstart', this, false);
		this.swFrame.addEventListener('mousedown', this, false);
	},

	openDatePicker: function (element, minYear, maxYear) {
		this.originalElement = element[0];
		this.isTimePicker = false;

		if (this.originalElement) {
			this.currentDate = this.originalElement.value;

			this.currentMonth = new Date(Date.parse(this.currentDate))
					.getMonth() + 1;
			this.currentDay = new Date(Date.parse(this.currentDate)).getDate();
			this.currentYear = new Date(Date.parse(this.currentDate))
					.getFullYear();

		} else {
			this.currentDate = null;
		}

		this.createDateSlots(minYear, maxYear);
		this.create();
	},

	openTimePicker: function (element) {
		this.originalElement = element[0];
		this.isTimePicker = true;
		
		if (this.originalElement && $(this.originalElement).val()) {
			this.currentDate = '01-01-2010 ' + $(this.originalElement).val();
			this.date = new Date(Date.parse(this.currentDate));
		} else {
			this.currentDate = null;
			this.date = new Date();
		}
		var hour = this.date.getHours();
		var min = this.date.getMinutes();
		var ampm = 'AM';

		if (min <= 9) {
			min = "0" + min;
		}
		if (hour > 12) {
			hour = hour - 12;
			ampm = "PM";
		} else {
			hour = hour;
			ampm = "AM";
		}
		if (hour == 12) {
			ampm = "PM";
		}
		if (hour == 00) {
			hour = "12";
		}
		
		this.currentHours = hour;
		this.currentMinutes = min;
		this.currentAMPM = ampm;
		
		this.createTimeSlots(element);
		this.create();
	},
	
	
	/**
	 *
	 * Unload
	 *
	 */

	destroy: function () {
		this.swWrapper.removeEventListener('webkitTransitionEnd', this, false);

		this.swFrame.removeEventListener('touchstart', this, false);
		this.swFrame.removeEventListener('mousedown', this, false);

		document.getElementById('sw-done').removeEventListener('touchstart', this, false);
		document.getElementById('sw-done').removeEventListener('mousedown', this, false);

		document.removeEventListener('touchstart', this, false);
		document.removeEventListener('mousedown', this, false);
		document.removeEventListener('mouseup', this, false);
		document.removeEventListener('touchmove', this, false);
		document.removeEventListener('mousemove', this, false);
    
		window.removeEventListener('orientationchange', this, true);
		window.removeEventListener('scroll', this, true);

		this.slotData = [];
		this.cancelAction = function () {
			return false;
		};
		
		this.cancelDone = function () {
			return true;
		};
		
		this.reset();
		
		$('#sw-wrapper').remove();
	},
	
	close: function () {
		this.swWrapper.style.webkitTransitionTimingFunction = 'ease-in';
		this.swWrapper.style.webkitTransitionDuration = '400ms';
		this.swWrapper.style.webkitTransform = 'translate3d(0, 0, 0)';
		
		this.swWrapper.addEventListener('webkitTransitionEnd', this, false);

		$(this.swWrapper).parent().parent().hide();
		this.updateResults();
		this.destroy();
	},
	
	/**
	 *
	 * Generic methods
	 *
	 */
    createDateSlots: function(minYear, maxYear) {
    	var now = new Date();
    	var minYear = (minYear) ? parseInt(minYear) : (now.getFullYear() - 100);
        var maxYear = (maxYear) ? parseInt(maxYear) : (now.getFullYear());
        var days = { };
        var years = { };
        var months = { 1: 'January', 2: 'February', 3: 'March', 4: 'April', 5: 'May', 6: 'June', 7: 'July', 8: 'August', 9: 'September', 10: 'October', 11: 'November', 12: 'December' };
        
        for (var i = 1; i < 32; i += 1) {
            days[i] = i;
        }
        
        for (var i = minYear; i <= maxYear; i += 1) {
        	years[i] = i;
        }

        this.addSlot(months, 'center', this.currentMonth ? this.currentMonth : (now.getMonth() + 1));
        this.addSlot(days, 'center', this.currentDay ? this.currentDay : now.getDate());
        this.addSlot(years, 'center', this.currentYear ? this.currentYear : now.getFullYear());
    },
    
    createTimeSlots : function() {
		var hours = { };
		var minutes = { };
		var ampm = { 'AM': 'AM', 'PM': 'PM' };

		for ( var i = 0; i < 12; i++) {
			hours[i] = i + 1;
		}
		for ( var i = 0; i < 60; i++) {
			minutes[i] = i;
		}

		this.addSlot(hours, 'center', this.currentHours ? this.currentHours - 1 : 0);
		this.addSlot(minutes, 'center', this.currentMinutes ? this.currentMinutes : 15);
		this.addSlot(ampm, 'center', this.currentAMPM ? this.currentAMPM : 'AM');
	},
  
	addSlot: function (values, style, defaultValue) {
		if (!style) {
			style = '';
		}
		
		style = style.split(' ');

		for (var i = 0; i < style.length; i += 1) {
			style[i] = 'sw-' + style[i]; 
		}
		
		style = style.join(' ');
    
    if (!defaultValue) {
      defaultValue = defaultValue;
      
      for (i in values) {
        if (!defaultValue)
          defaultValue = i;
      }
    }
 
		var obj = { 'values': values, 'style': style, 'defaultValue': defaultValue };
		this.slotData.push(obj);
	},

    updateResults: function() {
        if (!this.isTimePicker) {
            var results = this.getSelectedValues().values.join(' ');

            $(this.originalElement).val(results);
            $(this.originalElement).next().find('.ui-btn-text').html('Date: ' + results);
        } else {
            var results = this.getSelectedValues();
            var selectedTime = this.zeroPad(results.values[0], 2) + ':' + this.zeroPad(results.values[1], 2) + ' ' + results.values[2];

            $(this.originalElement).val(selectedTime);
            $(this.originalElement).next().find('.ui-btn-text').html('Time: ' + selectedTime);
        }
    },

	getSelectedValues: function () {
		var index, count,
		    i, l,
			keys = [], values = [];

		for (i in this.slotEl) {
			// Remove any residual animation
			this.slotEl[i].removeEventListener('webkitTransitionEnd', this, false);
			this.slotEl[i].style.webkitTransitionDuration = '0';

			if (this.slotEl[i].slotYPosition > 0) {
				this.setPosition(i, 0);
			} else if (this.slotEl[i].slotYPosition < this.slotEl[i].slotMaxScroll) {
				this.setPosition(i, this.slotEl[i].slotMaxScroll);
			}

			index = -Math.round(this.slotEl[i].slotYPosition / this.cellHeight);

			count = 0;
			for (l in this.slotData[i].values) {
				if (count == index) {
					keys.push(l);
					values.push(this.slotData[i].values[l]);
					break;
				}
				
				count += 1;
			}
		}

		return { 'keys': keys, 'values': values };
	},

    getCoors: function(e, pos) {
        var coors;
        var pos;
        
        if (e.targetTouches && e.targetTouches.length) {  // iPhone
            coors = (pos == 'x') ? e.targetTouches[0].clientX : e.targetTouches[0].clientY;
        } else {  // all others
            coors = (pos == 'x') ? e.clientX : e.clientY;
        }
        
        return coors;
    },

    adjustValidDays: function () {
        var evenMonths = [4, 6, 9, 11]; 
    
        var currentDate = new Date(this.getSelectedValues().values[0] + ' 01 2010' );

        var currentMonth = currentDate.getMonth() + 1;
        
        var daysSlot = document.getElementById('days'); 
        
        var qty = document.getElementById('days').children;
        
        var maxDaysInMonth = 30;
        
        // Calculates how many days the current month has
        if (currentMonth == 2) {
            maxDaysInMonth = (this.isLeapYear(this.getSelectedValues().values[2])) ? 29 : 28;
        } else if (evenMonths.indexOf(currentMonth) != -1) {
            maxDaysInMonth = 30;
        } else {
            maxDaysInMonth = 31;
        }
        
        // Scroll to last day of the current month if the 
        // previous selected day is not present anymore 
        if (daysSlot.lastChild.textContent > maxDaysInMonth) {
            this.scrollToValue(1, maxDaysInMonth); // Needs animation
        }
        
        // AddRemove not needed days from list
        this.addRemoveDays(daysSlot, maxDaysInMonth);
    },
  
    isLeapYear: function(year) {
        return (((year % 4 == 0) && (year % 100 != 0)) || (year % 400 == 0));
    },
  
    addRemoveDays: function(slot, max) {
        var cant = slot.children.length; // slot's children quantity
        
        for (var i = 1; i <= (cant - 28); i++) {
            slot.removeChild(slot.lastChild);
        }

        cant = slot.children.length;
          
        for (var i = cant; i < max; i++) {
            var oddElement = document.createElement('li');
            
            oddElement.textContent = i+1;
          
            slot.appendChild(oddElement);
        }
    },

    zeroPad: function(num,count) {
        var numZeropad = num + '';
        
        while(numZeropad.length < count) {
            numZeropad = "0" + numZeropad;
        };
        
        return numZeropad;
    },
    
	/**
	 *
	 * Rolling slots
	 *
	 */

	setPosition: function (slot, pos) {
		this.slotEl[slot].slotYPosition = pos;
		this.slotEl[slot].style.webkitTransform = 'translate3d(0, ' + pos + 'px, 0)';
	},
	
	scrollStart: function (e) {
		// Find the clicked slot
		var xPos = this.getCoors(e, 'x') - this.swSlots.offsetLeft;  // Clicked position minus left offset (should be 11px)

		// Find tapped slot
		var slot = 0;
		for (var i = 0; i < this.slotEl.length; i += 1) {
			slot += this.slotEl[i].slotWidth;
			
			if (xPos < slot) {
				this.activeSlot = i;
				break;
			}
		}

		// If slot is readonly do nothing
		if (this.slotData[this.activeSlot].style.match('readonly')) {
			this.swFrame.removeEventListener('touchmove', this, false);
			this.swFrame.removeEventListener('touchend', this, false);

			this.swFrame.removeEventListener('mousemove', this, false);
            this.swFrame.removeEventListener('mouseup', this, false);

			return false;
		}

		this.slotEl[this.activeSlot].removeEventListener('webkitTransitionEnd', this, false);	// Remove transition event (if any)
		this.slotEl[this.activeSlot].style.webkitTransitionDuration = '0';		// Remove any residual transition
		
		// Stop and hold slot position
		var theTransform = window.getComputedStyle(this.slotEl[this.activeSlot]).webkitTransform;
		theTransform = new WebKitCSSMatrix(theTransform).m42;
		
		if (theTransform != this.slotEl[this.activeSlot].slotYPosition) {
			this.setPosition(this.activeSlot, theTransform);
		}
		
		this.startY = this.getCoors(e, 'y');
		this.scrollStartY = this.slotEl[this.activeSlot].slotYPosition;
		this.scrollStartTime = e.timeStamp;

		this.swFrame.addEventListener('touchmove', this, false);
		this.swFrame.addEventListener('touchend', this, false);
		document.addEventListener('touchmove', this, false);
		document.addEventListener('touchend', this, false);

		this.swFrame.addEventListener('mousemove', this, false);
		this.swFrame.addEventListener('mouseup', this, false);
		document.addEventListener('mousemove', this, false);
		document.addEventListener('mouseup', this, false);
		
		return true;
	},

	scrollMove: function (e) {
		var topDelta = this.getCoors(e, 'y') - this.startY;

		if (this.slotEl[this.activeSlot].slotYPosition > 0 || this.slotEl[this.activeSlot].slotYPosition < this.slotEl[this.activeSlot].slotMaxScroll) {
			topDelta /= 2;
		}
		
		this.setPosition(this.activeSlot, this.slotEl[this.activeSlot].slotYPosition + topDelta);
		this.startY = this.getCoors(e, 'y') ;

		// Prevent slingshot effect
		if (e.timeStamp - this.scrollStartTime > 80) {
			this.scrollStartY = this.slotEl[this.activeSlot].slotYPosition;
			this.scrollStartTime = e.timeStamp;
		}
	},
	
	scrollEnd: function (e) {
		this.swFrame.removeEventListener('touchmove', this, false);
		this.swFrame.removeEventListener('touchend', this, false);

		document.removeEventListener('touchmove', this, false);
		document.removeEventListener('touchend', this, false);
		
		this.swFrame.removeEventListener('mousemove', this, false);
		this.swFrame.removeEventListener('mouseup', this, false);

		document.removeEventListener('mousemove', this, false);
		document.removeEventListener('mouseup', this, false);

		// If we are outside of the boundaries, let's go back to the sheepfold
		if (this.slotEl[this.activeSlot].slotYPosition > 0 || this.slotEl[this.activeSlot].slotYPosition < this.slotEl[this.activeSlot].slotMaxScroll) {
			this.scrollTo(this.activeSlot, this.slotEl[this.activeSlot].slotYPosition > 0 ? 0 : this.slotEl[this.activeSlot].slotMaxScroll);
			return false;
		}
        
        if (!this.isTimePicker) {
            this.adjustValidDays();
        }
        
		// Lame formula to calculate a fake deceleration
		var scrollDistance = this.slotEl[this.activeSlot].slotYPosition - this.scrollStartY;

		// The drag session was too short
		if (scrollDistance < this.cellHeight / 1.5 && scrollDistance > -this.cellHeight / 1.5) {
			if (this.slotEl[this.activeSlot].slotYPosition % this.cellHeight) {
				this.scrollTo(this.activeSlot, Math.round(this.slotEl[this.activeSlot].slotYPosition / this.cellHeight) * this.cellHeight, '100ms');
			}

			return false;
		}

		var scrollDuration = e.timeStamp - this.scrollStartTime;

		var newDuration = (2 * scrollDistance / scrollDuration) / this.friction;
		var newScrollDistance = (this.friction / 2) * (newDuration * newDuration);
		
		if (newDuration < 0) {
			newDuration = -newDuration;
			newScrollDistance = -newScrollDistance;
		}

		var newPosition = this.slotEl[this.activeSlot].slotYPosition + newScrollDistance;
		
		if (newPosition > 0) {
			// Prevent the slot to be dragged outside the visible area (top margin)
			newPosition /= 2;
			newDuration /= 3;

			if (newPosition > this.swSlotWrapper.clientHeight / 4) {
				newPosition = this.swSlotWrapper.clientHeight / 4;
			}
		} else if (newPosition < this.slotEl[this.activeSlot].slotMaxScroll) {
			// Prevent the slot to be dragged outside the visible area (bottom margin)
			newPosition = (newPosition - this.slotEl[this.activeSlot].slotMaxScroll) / 2 + this.slotEl[this.activeSlot].slotMaxScroll;
			newDuration /= 3;
			
			if (newPosition < this.slotEl[this.activeSlot].slotMaxScroll - this.swSlotWrapper.clientHeight / 4) {
				newPosition = this.slotEl[this.activeSlot].slotMaxScroll - this.swSlotWrapper.clientHeight / 4;
			}
		} else {
			newPosition = Math.round(newPosition / this.cellHeight) * this.cellHeight;
		}

        this.scrollTo(this.activeSlot, Math.round(newPosition), Math.round(newDuration) + 'ms');
 
		return true;
	},

	scrollTo: function (slotNum, dest, runtime) {
		this.slotEl[slotNum].style.webkitTransitionDuration = runtime ? runtime : '100ms';
		this.setPosition(slotNum, dest ? dest : 0);

		// If we are outside of the boundaries go back to the sheepfold
		if (this.slotEl[slotNum].slotYPosition > 0 || this.slotEl[slotNum].slotYPosition < this.slotEl[slotNum].slotMaxScroll) {
			this.slotEl[slotNum].addEventListener('webkitTransitionEnd', this, false);
		}
	},
	
	scrollToValue: function (slot, value) {
		var yPos, count, i;

		this.slotEl[slot].removeEventListener('webkitTransitionEnd', this, false);
		this.slotEl[slot].style.webkitTransitionDuration = '0';
		
		count = 0;
		for (i in this.slotData[slot].values) {
			if (i == value) {
				yPos = count * this.cellHeight;
				this.setPosition(slot, yPos);
				break;
			}
			
			count -= 1;
		}
	},
	
	backWithinBoundaries: function (e) {
		e.target.removeEventListener('webkitTransitionEnd', this, false);
		
		if (!this.isTimePicker) {
            this.adjustValidDays();
        } else {
            this.scrollTo(e.target.slotPosition, e.target.slotYPosition > 0 ? 0 : e.target.slotMaxScroll, '150ms');
        }
		
		return false;
	},


	/**
	 *
	 * Buttons
	 *
	 */

	tapDown: function (e) {
		e.currentTarget.addEventListener('touchmove', this, false);
		e.currentTarget.addEventListener('touchend', this, false);
		
		e.currentTarget.addEventListener('mousemove', this, false);
		e.currentTarget.addEventListener('mouseup', this, false);
		
		e.currentTarget.className = 'sw-pressed';
	},

	tapCancel: function (e) {
		e.currentTarget.removeEventListener('touchmove', this, false);
		e.currentTarget.removeEventListener('touchend', this, false);
		
		e.currentTarget.removeEventListener('mousemove', this, false);
        e.currentTarget.removeEventListener('mouseup', this, false);
    
		e.currentTarget.className = '';
	},
	
	tapUp: function (e) {
		this.tapCancel(e);

		this.close();
	},

	setDoneAction: function (action) {
		this.doneAction = action;
	},
	
	cancelAction: function () {
		return false;
	},

	cancelDone: function () {
		return true;
	}
	
};
