/*
 * Copyright (c) 2006 Sam Collett (http://www.texotela.co.uk)
 * Licensed under the MIT License:
 * http://www.opensource.org/licenses/mit-license.php
 * A time picker for jQuery
 * Based on original timePicker by Sam Collet (http://www.texotela.co.uk)
 * @name     timePicker
 * @version  0.1 
 * @author   Anders Fajerson (http://perifer.se)
 */

(function($){

     $.fn.timePicker = function(options) {
          // Build main options before element iteration
          var settings = $.extend({}, $.fn.timePicker.defaults, options);  

          return this.each(function() {
                    $.timePicker(this, settings);
               });
     };

     /*Description of Vars:
               elm  -    Element in which this timepicker is attached to
          */
     $.timePicker = function (elm, settings) {
          var elm = $(elm)[0];  
          return elm.timePicker || (elm.timePicker = new jQuery._timePicker(elm, settings));
     };

     $._timePicker = function(elm, settings) {
          $(elm).attr('autocomplete', 'OFF'); // Disable browser autocomplete
          
          var tpOver = false;
          var startTime = normaliseTime(settings.startTime);
          var endTime = normaliseTime(settings.endTime);
          var times = [];
          var time = new Date(startTime); // Create a new date object.

          // Store element offset.
          var elmOffset = $(elm).offset();
          
          while(time <= endTime) {
               times[times.length] = formatTime(time, settings);
               time = new Date(time.setMinutes(time.getMinutes() + settings.step));
          }

          var tpDiv = $('<div class="time-picker'+ (settings.show24Hours ? '' : ' time-picker-12hours') +'"></div>');
          var tpList = $('<ul></ul>');

          // Build the list.
          for(var i = 0; i < times.length; i++) {
               tpList.append("<li>" + times[i] + "</li>");
          }
     
          tpDiv.append(tpList);

          // Append the timPicker to the body and position it.
          tpDiv.appendTo('body').css({'top':elmOffset.top +$(elm).height(), 'left':elmOffset.left }).hide();

          $("li", tpList).unbind().mouseover(function() {
                    $("li.selected", tpDiv).removeClass("selected");  // TODO: only needs to run once.
                    $(this).addClass("selected");
               }).mousedown(function() {
                    tpOver = true;
               }).click(function() {
                    setTimeVal(elm, this, tpDiv, settings);
                    tpOver = false;
               });

          // Store ananymous function in variable since it's used twice.
          var showPicker = function() {
				tpDiv.show(); // Show picker.
				repositionTimepicker(tpDiv, elm);
                    tpDiv.mouseover(function() { // Have to use mouseover instead of mousedown because of Opera
                         tpOver = true;
                    }).mouseout(function() {
                         tpOver = false;
                    });
               
               $("li", tpDiv).removeClass("selected");

               // Try to find a time in the list that matches the entered time.
               var time = this.value ? timeStringToDate(this.value, settings) : startTime;
               var startMin = startTime.getHours() * 60 + startTime.getMinutes();
               var min = (time.getHours() * 60 + time.getMinutes()) - startMin;
               var steps = Math.round(min / settings.step);
               var roundTime = normaliseTime(new Date(2001, 0, 0, 0, (steps * settings.step + (startMin)), 0));
               roundTime = (startTime < roundTime && roundTime < endTime) ? roundTime : startTime;

               var $matchedTime = $("li:contains(" + formatTime(roundTime, settings) + ")", tpDiv);

               if ($matchedTime.length) {
                    $matchedTime.addClass("selected");
                    // Scroll to matched time.
                    tpDiv[0].scrollTop = $matchedTime[0].offsetTop;
               }
          };

          $(elm).unbind().focus(showPicker).click(showPicker)
          // Hide timepicker on blur
          .blur(function() {
               if (!tpOver && tpDiv[0].parentNode) { // Don't remove when timePicker is clicked or when already removed
                    tpDiv.hide();
               }
          })

          // Key support
          .keypress(function(e) {

               switch (e.keyCode) {
                    // Up arrow 
                    case 38: case 63232:
                         var $selected = $("li.selected", tpList);
                         var prev = $selected.prev().addClass("selected")[0];
                         if (prev) {
                              $selected.removeClass("selected");
                              tpDiv[0].scrollTop = prev.offsetTop;
                         }
                         return false;
                    break;
                    // Down arrow  
                    case 40: case 63233:
                         var $selected = $("li.selected", tpList);
                         var next = $selected.length ? $selected.next().addClass("selected")[0] : $("li:first").addClass("selected")[0];
                         if (next) {
                              $selected.removeClass("selected");
                              tpDiv[0].scrollTop = next.offsetTop;
                         }
                         return false;
                    break;
                    case 13: // Enter
                         if (!tpDiv.is(":hidden")) {
                              var sel = $("li.selected", tpList)[0];
                              setTimeVal(elm, sel, tpDiv, settings);
                              return false;
                         }
                    break;
               }
          });

          // Helper function to get an inputs current time as Date object.
          // Returns a Date object.
          this.getTime = function() {
               return timeStringToDate(elm.value, settings);
          };
          
          // Helper function to set a time input.
          // Takes a Date object.
          this.setTime = function(time) {
               elm.value = formatTime(normaliseTime(time), settings);
               // Trigger element's change events.
               $(elm).change();
          };

     }; // End fn;   

     // Plugin defaults.
     $.fn.timePicker.defaults = {
          step:30,
          startTime: new Date(0, 0, 0, 0, 0, 0),
          endTime: new Date(0, 0, 0, 23, 30, 0),
          separator: ':',
          show24Hours: true
     };

     // Private functions.

     function setTimeVal(elm, sel, tpDiv, settings) {
          // Update input field
          elm.value = $(sel).text();
          
          // Trigger element's change events.
          $(elm).change();
          
          // Keep focus for all but IE (which doesn't like it)
          if (!$.browser.msie) {
               elm.focus();
          }
          
          // Hide picker
          tpDiv.hide();
     }

     function formatTime(time, settings) {
          var h = time.getHours();
          var hours = settings.show24Hours ? h : (((h + 11) % 12) + 1);
          var minutes = time.getMinutes();
          return formatNumber(hours) + settings.separator + formatNumber(minutes) + (settings.show24Hours ? '' : ((h < 12) ? ' AM' : ' PM'));
     }

     function formatNumber(value) {
          return (value < 10 ? '0' : '') + value;
     }

     function timeStringToDate(input, settings) {
          if (input) {
               var array = input.split(settings.separator);
               var hours = parseFloat(array[0]);
               var minutes = parseFloat(array[1]);
               
               /*If we are in 12 hour format we neeed to take
                 into account the am or pm and adjust the hours.
               */
               if(!settings.show24Hours){
                    var array  = array[1].split(" ");
                    minutes = array[0];
                    amPm = array[1];
                    hours += (amPm.search(/PM/i) == 0)? 12 : 0;
               }
               var time = new Date(0, 0, 0, hours, minutes, 0);
               return normaliseTime(time);
          }
          
          return null;
     }

     /* Normalise time object to a common date. */
     function normaliseTime(time) {
          time.setFullYear(2001);
          time.setMonth(0);
          time.setDate(0);
          return time;
     }
	
	function repositionTimepicker(tpDiv, elm) {
		var elmOffset = $(elm).offset();
		tpDiv.css({'top':elmOffset.top +$(elm).height(), 'left':elmOffset.left });
	}

})(jQuery);
