Period_picker = (function($) { 

function Period_picker(el, opts) {
  if (typeof(opts) != "object") opts = {};
  $.extend(this, Period_picker.DEFAULT_OPTS, opts);
  
  this.input = $(el);
  this.bindMethodsToObj("show", "hide", "reset", "strToDate", "dateToStr", "strpad", "selectMonth", "selectPeriod", "printSelection", "selectFullMonth", "manualInputListener", "insideSelector", "hideIfClickOutside");
  
  this.build();
  
 this.hide();
};

Period_picker.DEFAULT_OPTS = {
  month_names: ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"],
  short_day_names: ["S", "M", "T", "W", "T", "F", "S"],
  start_of_week: 1,
  date_min:'',
  date_max:''
 };
 
Period_picker.prototype = {
  build: function() {
	this.input.css('display','none');
	
	var reg = new RegExp(/^(\d{4}\/\d{1,2}\/\d{1,2}) (\d{4}\/\d{1,2}\/\d{1,2})$/);
	
	if(this.input.attr('value')!='' && this.input.attr('value').match(reg)){
		var matches = this.input.attr('value').match(reg)		
		this.currentMonth = this.strToDate(matches[1]);

		var input0 = matches[1];
		var input1 = matches[2];
	}else{
		this.currentMonth = new Date();

		var input0 = "";
		var input1 = "";
	}
	
	this.wrapper = this.input.wrap('<div class="period_picker_wrapper">');
	this.pannel = $('<div class="period_picker_pannel"><table><tr><td class="move_left">&laquo;</td><td class="period_picker_firstMonth"></td><td class="period_picker_secondMonth"></td><td class="move_right">&raquo;</td><td class="period_picker_inputs"><input type="text" class="period_picker_input_text highlight" name="'+this.input.attr('name')+'[]" value="'+(input0)+'" /><input type="text" class="period_picker_input_text" name="'+this.input.attr('name')+'[]" value="'+(input1)+'" /></td></tr></table>');
	this.label = $('<div class="period_picker_label"><span class="period_picker_label"></span><a class="reset">&times;</a>');
	
	$('a', this.label).click(this.reset);
	
	this.wrapper.after(this.label, this.pannel);
	
	this.updateTitle();
	
	$('.move_left', this.pannel).click(this.bindToObj(function() {this.moveMonthBy(-1);}));
	$('.move_right', this.pannel).click(this.bindToObj(function() {this.moveMonthBy(1);}));
	
	$('.period_picker_input_text', this.pannel).keyup(this.manualInputListener);
	$('.period_picker_input_text', this.pannel).focus(this.inputFocus);
	
	this.selectMonth(this.currentMonth, 'firstMonth');
  },
  
  show:function(){
	$('span:first', this.label).unbind('click');
	$('span:first', this.label).bind('click', this.hide);
	$('span:first', this.label).css('background-position','97% -27px');
	
	$([window, document.body]).click(this.hideIfClickOutside);
	
	this.pannel.slideDown();
  },
  
  hide:function(){
	$('span:first', this.label).unbind('click');
	$('span:first', this.label).bind('click', this.show);
	$('span:first', this.label).css('background-position','97% 9px');
	
	$([window, document.body]).unbind("click", this.hideIfClickOutside);
	
	this.updateTitle();
	this.pannel.slideUp();
  },
  
  updateTitle:function(){
	var reg = new RegExp(/^(\d{4}\/\d{1,2}\/\d{1,2})$/);
		
	if($('input:eq(0)', this.pannel).val().match(reg))
		$('span:first', this.label).html($('input:eq(0)', this.pannel).val());
	else
		$('span:first', this.label).html('YYYY/mm/dd');
	
	if($('input:eq(1)', this.pannel).val().match(reg))
		$('span:first', this.label).html($('span:first', this.label).html()+" - "+$('input:eq(1)', this.pannel).val());
	else
		$('span:first', this.label).html($('span:first', this.label).html()+" - YYYY/mm/dd");
  },
  
  reset:function(){
	  $('.period_picker_input_text', this.pannel).val(''); 
	  this.updateTitle();
	  this.printSelection();
  },
  
  selectMonth: function(date, dest) {
    var newMonth = new Date(date.getFullYear(), date.getMonth(), 1);

	  if(dest=='firstMonth'){
		this.currentMonth = newMonth;
      }
      var rangeStart = this.rangeStart(date), rangeEnd = this.rangeEnd(date);
      var numDays = this.daysBetween(rangeStart, rangeEnd);
      var dayCells = "<tr><th colspan='7' class='monthName'>"+this.month_names[date.getMonth()]+" "+date.getFullYear()+"</th></tr><tr>";
      
	  $(this.adjustDays(this.short_day_names)).each(function() {
		dayCells += "<th>" + this + "</th>";
      });
	  
	  dayCells += "</tr>";
	  
      for (var i = 0; i <= numDays; i++) {
        var currentDay = new Date(rangeStart.getFullYear(), rangeStart.getMonth(), rangeStart.getDate() + i, 12, 00);
        
        if (this.isFirstDayOfWeek(currentDay)) dayCells += "<tr>";

        if (currentDay.getMonth() == date.getMonth() && (this.date_min=='' || this.daysBetween(currentDay,this.strToDate(this.date_min))<=0) && (this.date_max=='' || this.daysBetween(currentDay,this.strToDate(this.date_max))>=0)) {
          dayCells += '<td class="selectable_day" date="' + this.dateToStr(currentDay) + '">' + currentDay.getDate() + '</td>';
        } else {
          dayCells += '<td class="unselected_month" date="' + this.dateToStr(currentDay) + '">' + currentDay.getDate() + '</td>';
        };
        
        if (this.isLastDayOfWeek(currentDay)) dayCells += "</tr>";
      }
	  
      $('.period_picker_'+dest, this.pannel).html("<table>"+dayCells+"</table>");
      
      $("td[date=" + this.dateToStr(new Date()) + "]", this.tbody).addClass("today");
      
      $("td.selectable_day", this.tbody).mouseover(function() { $(this).addClass("hover") });
      $("td.selectable_day", this.tbody).mouseout(function() { $(this).removeClass("hover") });
	
	
	
	if(dest=='firstMonth'){
		var newMonth = new Date(this.currentMonth.getFullYear(), this.currentMonth.getMonth()+1, 1);
		this.selectMonth(newMonth, 'secondMonth');
	}else{
		$('td td', this.pannel).unbind('click');
		$('td td', this.pannel).click(this.selectPeriod);
		$('th.monthName',this.pannel).click(this.selectFullMonth);
		this.printSelection();
	}
  },
  
  moveMonthBy:function(move){
	if(move<0){
		var newMonth = new Date(this.currentMonth.getFullYear(), this.currentMonth.getMonth() + move+1, -1);
		this.selectMonth(newMonth, 'firstMonth');
    }else{
		var newMonth = new Date(this.currentMonth.getFullYear(), this.currentMonth.getMonth() + move, 1);
		this.selectMonth(newMonth, 'firstMonth');
    }
  },
  
  selectPeriod:function(event){
	var el = event.target;
	
	if((this.date_min=='' || this.daysBetween(this.strToDate($(el).attr('date')),this.strToDate(this.date_min))<=0) && (this.date_max=='' || this.daysBetween(this.strToDate($(el).attr('date')), this.strToDate(this.date_max))>=0)){	
		if($('input:eq(0)', this.pannel).hasClass('highlight')){
			$('input:eq(1)', this.pannel).val('');
			$('input:eq(0)', this.pannel).val($(el).attr('date'));
			
			$('input:eq(0)', this.pannel).removeClass('highlight');
			$('input:eq(1)', this.pannel).addClass('highlight');
		}else{
			if(this.daysBetween(this.strToDate($('input:eq(0)', this.pannel).val()), this.strToDate($(el).attr('date')))>=0){
				$('input:eq(1)', this.pannel).val($(el).attr('date'));
			}else{
				$('input:eq(1)', this.pannel).val($('input:eq(0)', this.pannel).val());
				$('input:eq(0)', this.pannel).val($(el).attr('date'));
			}
			
			$('input:eq(1)', this.pannel).removeClass('highlight');
			$('input:eq(0)', this.pannel).addClass('highlight');
		}
	}
	this.printSelection();
  },
  
  selectFullMonth:function(event){
	  $('input:eq(0)', this.pannel).val($('td.selectable_day:first', event.target.parentNode.parentNode).attr('date'));
	  $('input:eq(1)', this.pannel).val($('td.selectable_day:last', event.target.parentNode.parentNode).attr('date'));
	  this.printSelection();
  },
  
  printSelection:function(){
	$('.selected', this.pannel).removeClass('selected');
	$('.selected_fade', this.pannel).removeClass('selected_fade');
	
	if($('input:eq(0)', this.pannel).val()!='' && $('input:eq(1)', this.pannel).val()!=''){
		var start = (this.strToDate($('input:eq(0)', this.pannel).val())<this.strToDate($('td td:first', this.pannel).attr('date'))?this.strToDate($('td td:first', this.pannel).attr('date')):this.strToDate($('input:eq(0)', this.pannel).val()));
		var end = (this.strToDate($('input:eq(1)', this.pannel).val())>this.strToDate($('td td:last', this.pannel).attr('date'))?this.strToDate($('td td:last', this.pannel).attr('date')):this.strToDate($('input:eq(1)', this.pannel).val()));

		for(var i=0; i<=this.daysBetween(start, end); i++){
			$('td.selectable_day[date='+this.dateToStr(new Date(start.getFullYear(), start.getMonth(), start.getDate()+i))+']', this.pannel).addClass('selected');
			$('td.unselected_month[date='+this.dateToStr(new Date(start.getFullYear(), start.getMonth(), start.getDate()+i))+']', this.pannel).addClass('selected_fade');
		}
		
	}else if($('input:eq(0)', this.pannel).val()!=''){
		$('td.selectable_day[date='+$('input:eq(0)', this.pannel).val()+']', this.pannel).addClass('selected');
		$('td.unselected_month[date='+$('input:eq(0)', this.pannel).val()+']', this.pannel).addClass('selected_fade');
	}
  },
  
  manualInputListener:function(event){
	var reg = new RegExp(/^(\d{4})\/(\d{1,2})\/(\d{1,2})$/);
	
    if (matches = $(event.target).val().match(reg) || $(event.target).val()=='') {
		this.printSelection();
	}
  },
  
  inputFocus:function(event){
	$('input', this.pannel).removeClass('highlight');
	$(event.target).addClass('highlight');
  },
  
  daysBetween: function(start, end) {
    start = Date.UTC(start.getFullYear(), start.getMonth(), start.getDate());
    end = Date.UTC(end.getFullYear(), end.getMonth(), end.getDate());
    return (end - start) / 86400000;
  },
  
  changeDayTo: function(dayOfWeek, date, direction) {
    var difference = direction * (Math.abs(date.getDay() - dayOfWeek - (direction * 7)) % 7);
    return new Date(date.getFullYear(), date.getMonth(), date.getDate() + difference);
  },
  
  rangeStart: function(date) {
    return this.changeDayTo(this.start_of_week, new Date(date.getFullYear(), date.getMonth()), -1);
  },
  
  rangeEnd: function(date) {
    return this.changeDayTo((this.start_of_week - 1) % 7, new Date(date.getFullYear(), date.getMonth() + 1, 0), 1);
  },
  
  isFirstDayOfWeek: function(date) {
    return date.getDay() == this.start_of_week;
  },
  
  isLastDayOfWeek: function(date) {
    return date.getDay() == (this.start_of_week - 1) % 7;
  },
  
  dateToStr:function(date){
	return this.strpad(date.getFullYear()) + "/" + this.strpad(date.getMonth()+1) + "/" + date.getDate();
  },
  
  strToDate: function(string) {
    var matches;
	var reg = new RegExp(/^(\d{4})\/(\d{1,2})\/(\d{1,2})$/);
	
    if (matches = string.match(reg)) {
      if(matches[1]==0 && matches[3]==0 && matches[2]==0)
    	return new Date();
      else
        return new Date(matches[1], matches[2]-1, matches[3]);
    } else {
      return new Date();
    };
  },
  
  strpad: function(num){
	if(parseInt(num)<10)	return "0"+parseInt(num);
	else	return parseInt(num);
  },
  
  hideIfClickOutside: function(event) {
    if (!this.insideSelector(event)) {
      this.hide();
    };
  },
  
  insideSelector: function(event) {
    var offset = this.pannel.offset();
	
	offset.right = offset.left + this.pannel.outerWidth();
    offset.bottom = offset.top + this.pannel.outerHeight();
	offset.top -= 20;
	
    return event.pageY < offset.bottom &&
           event.pageY > offset.top &&
           event.pageX < offset.right &&
           event.pageX > offset.left;
  },
  
  adjustDays: function(days) {
    var newDays = [];
    for (var i = 0; i < days.length; i++) {
      newDays[i] = days[(i + this.start_of_week) % 7];
    };
    return newDays;
  },
  
  bindMethodsToObj: function() {
    for (var i = 0; i < arguments.length; i++) {
      this[arguments[i]] = this.bindToObj(this[arguments[i]]);
    };
  },
  
  bindToObj: function(fn) {
    var self = this;
    return function() { return fn.apply(self, arguments) };
  }
  
};

$.fn.Period_picker = function(opts) {
  return this.each(function() { new Period_picker(this, opts); });
};
$.Period_picker = { initialize: function(opts) {
  $("input.period_picker").Period_picker(opts);
} };

return Period_picker;
})(jQuery); 

$($.Period_picker.initialize);

