/*
 * A time picker for jQuery
 * Based on original timePicker by Sam Collet (http://www.texotela.co.uk) -
 * copyright (c) 2006 Sam Collett (http://www.texotela.co.uk)
 *
 * Dual licensed under the MIT and GPL licenses.
 * Copyright (c) 2009 Anders Fajerson
 * @name     timePicker
 * @version  0.2
 * @author   Anders Fajerson (http://perifer.se)
 * @example  $("#mytime").timePicker();
 * @example  $("#mytime").timePicker({step:30, startTime:"15:00", endTime:"18:00"});
 */

(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);
    });
  };

  $.timePicker = function (elm, settings) {
    var e = $(elm)[0];
    return e.timePicker || (e.timePicker = new jQuery._timePicker(e, settings));
  };

  $._timePicker = function(elm, settings) {

    var tpOver = false;
    var keyDown = false;
    var changeLast = false;
    var startTime = timeToDate(settings.startTime, settings);
    if (settings.endTime == '24:00') {
       settings.endTime = '00:00';
       var endTime = timeToDate(settings.endTime, settings, true);
       var changeLast = true;
    }
    else
       var endTime = timeToDate(settings.endTime, settings);
      
    $(elm).attr('autocomplete', 'OFF'); // Disable browser autocomplete

    var time = new Date(startTime); // Create a new date object.
    var firstRun = true;
    var lastAdded = false;
    
		if ((firstTimePicker == true) || (generateTimes == true)) {
	  	 firstTimePicker = false;
			 times = [];
	   	 while(time <= endTime) {
		      if (lastAdded == false)
		         times[times.length] = formatTime(time, settings);
      
		      time = new Date(time.setMinutes(time.getMinutes() + settings.step));

		      if ((changeLast == true) && (firstRun == false) && (time.getHours() == '0') && (time.getMinutes() == '0')) {
		         times[times.length] = '24:00';
		         lastAdded = true;
		      }

		      firstRun = false;
		    }
		}

    var $tpDiv = $('<div class="time-picker"></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.
    var elmOffset = $(elm).offset();
		var myOffset = elmOffset.top + parseInt(settings.offset);

    $tpDiv.appendTo('body').css({'top':myOffset, 'left':elmOffset.left}).hide();

    // Store the mouse state, used by the blur event. Use mouseover instead of
    // mousedown since Opera fires blur before mousedown.
    $tpDiv.mouseover(function() {
      tpOver = true;
    }).mouseout(function() {
      tpOver = false;
    });

    $("li", $tpList).mouseover(function() {
      if (!keyDown) {
        $("li.selected", $tpDiv).removeClass("selected");
        $(this).addClass("selected");
      }
    }).mousedown(function() {
       tpOver = true;
    }).click(function() {
      setTimeVal(elm, this, $tpDiv, settings);
      tpOver = false;
    });

    var showPicker = function() {
      if ($tpDiv.is(":visible")) {
        return false;
      }
      $("li", $tpDiv).removeClass("selected");

			if ($.browser.msie && $.browser.version == '6.0')
				$(".hide-ie6").hide();
				
      $tpDiv.show();

      updatePicker();

      return true;
    };

	 var updatePicker = function() {
		 var time = elm.value ? timeStringToDate(elm.value, settings, null, elm) : 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(0, 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) {
		 $("li", $tpDiv).removeClass("selected");
       $matchedTime.addClass("selected");
       // Scroll to matched time.
       $tpDiv[0].scrollTop = $matchedTime[0].offsetTop;
     }
	 }
    // Attach to click as well as focus so timePicker can be shown again when
    // clicking on the input when it already has focus.
    $(elm).focus(showPicker).click(showPicker);
    // Hide timepicker on blur
    $(elm).blur(function() {
			var data = cleanUp($(elm).val());
			
			if ((data != null) && ((data[1].length == 2) && (data[2].length == 2)))
				$(elm).val(data[1] + ':' + data[2]);
      if (!tpOver) {
				if ($.browser.msie && $.browser.version == '6.0')
					$(".hide-ie6").show();
        $tpDiv.hide();
      }
    });

    var event = ($.browser.opera || $.browser.mozilla) ? 'keypress' : 'keydown';
    $(elm)[event](function(e) {
      var $selected;
      keyDown = true;
      var top = $tpDiv[0].scrollTop;
      switch (e.keyCode) {
        case 38: // Up arrow.
          // Just show picker if it's hidden.
          if (showPicker()) {
            return false;
          };
          $selected = $("li.selected", $tpList);
          var prev = $selected.prev().addClass("selected")[0];
          if (prev) {
            $selected.removeClass("selected");
            // Scroll item into view.
            if (prev.offsetTop < top) {
              $tpDiv[0].scrollTop = top - prev.offsetHeight;
            }
          }
          return false;
          break;
        case 40: // Down arrow, similar in behaviour to up arrow.
          if (showPicker()) {
            return false;
          };
          $selected = $("li.selected", $tpList);
          var next = $selected.next().addClass("selected")[0];
          if (next) {
            $selected.removeClass("selected");
            if (next.offsetTop + next.offsetHeight > top + $tpDiv[0].offsetHeight) {
              $tpDiv[0].scrollTop = top + next.offsetHeight;
            }
          }
          return false;
          break;
        case 13: // Enter
          if ($tpDiv.is(":visible")) {
            var sel = $("li.selected", $tpList)[0];
            setTimeVal(elm, sel, $tpDiv, settings);
          }
          return false;
          break;
        case 27: // Esc
					if ($.browser.msie && $.browser.version == '6.0')
						$(".hide-ie6").show();
          $tpDiv.hide();
          return false;
          break;
      }
      return true;
    });
    $(elm).keyup(function(e) {
			if (!(((e.keyCode == 8) && (elm.value.length == 2)) || (e.keyCode == 40) || (e.keyCode == 38)))
				updatePicker();
      keyDown = false;
    });
    // Helper function to get an inputs current time as Date object.
    // Returns a Date object.
    this.getTime = function() {
      return timeStringToDate(elm.value, settings, null, elm);
    };
    // 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, 24, 00, 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
		if ($.browser.msie && $.browser.version == '6.0')
			$(".hide-ie6").show();
    $tpDiv.hide();
  }

  function formatTime(time, settings) {
    var hours = time.getHours();
    var minutes = time.getMinutes();
    return formatNumber(hours) + settings.separator + formatNumber(minutes);
  }

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

  function timeToDate(input, settings, nextDay) {
    return (typeof input == 'object') ? normaliseTime(input) : timeStringToDate(input, settings, nextDay);
  }
	
	if (!oldTime)
		var oldTime = [];

  function timeStringToDate(input, settings, nextDay, elm) {
    if (input) {
			
			var oldLength = $(elm).val().length;
			var data = cleanUp(input);
			
			if ((data != null) && ((data[1].length == 2) && (oldLength == 2)))
				var addSeparator = 1;
			else
				var addSeparator = 0;

			if (data != null) {			
				var hours = data[1];
				var minutes = data[2];
				
				oldTime[$(elm).attr("id")] = hours + ':' + minutes;
				
				if (addSeparator == 1)
					$(elm).val(hours + ':');

			} else {
				if (oldTime[$(elm).attr("id")] != null) {
					var array = oldTime[$(elm).attr("id")].split(settings.separator);
		      var hours = parseFloat(array[0]);
		      var minutes = parseFloat(array[1]);
				}
			}
      var time = new Date(0, 0, 0, parseInt(hours), parseInt(minutes), 0);
      return normaliseTime(time,nextDay);
    }
    return null;
  }

  function cleanUp(input) {
		var re = new RegExp(/^([0-9]{1})[:.,\s]{1}([0-9]{1,2})$/);
		var m = re.exec(input);
	
		if (m == null) {
			re = new RegExp(/^([0-9]{1,2})[:]{0,1}$/);
			m = re.exec(input);
			if ((m != null) && (m[1].length == 2))
				addSeparator = 1;
		}
	
		if (m == null) {
			re = new RegExp(/^([0-9]{2})[:.,\s]{1}([0-9]{1,2})$/);
			m = re.exec(input);
		}
	
		if (m == null) {
			re = new RegExp(/^([0-9]{2})[:.,\s]{0,1}([0-9]{2})$/);
			m = re.exec(input);
		}
		
		if (m != null) {
			if (m[1].toString().length == 1)
				m[1] = '0' + m[1];
			else
				m[1] = m[1];
		
			if (!m[2])
				m[2] = '00';
			else {
				if (m[2].toString().length == 1)
					m[2] = '0' + m[2];
				else
					m[2] = m[2];
			}
		}
		return m;
	}

  /* Normalise time object to a common date. */
  function normaliseTime(time,nextDay) {
    time.setFullYear(2001);
    time.setMonth(0);
    if (nextDay)
      time.setDate(1);
    else
      time.setDate(0);
    return time;
  }

})(jQuery);
