/**
 * Class constructor
 * Create a calendar slider inside the container element.
 * You must use show() method after creating calendar
 * to set slider in a y/m calendar
 * Use select() method to celect one or more dates from calendars
 *
 * @param {Element} container: slider container element or id
 */
TCalendar = function (container) {
  this._inner = null;
  this._footer = null;
  this._slider = null;
  this._iframe = null;
  this._months = [];
  this._bindings = [];
  this._container = TDOM.getElement(container);
  this._activeInput = null;
  this._overCalendar = false;
  this._selectRange = false;
  this._setupCalendar();

  this.current = { year:0, month:0, count:0, startday:0 };
  this.selected = { from:null, to:null };
  this.rangeSelect = false;

  // If IE 6 or lower create a div inside container and set an iframe
  // at the bottom of it to prevent selects and lists to pop up in front of the calendar
  if (TUserAgent.IE && TUserAgent.VERSION < 7) {
		this._iframe = document.createElement("iframe");
		this._iframe.src = "javascript:false;";
		TDOM.setStyle(this._iframe, "opacity", "0");
		this._container.appendChild(this._iframe);
  }
};

TCalendar.inherits(TEventDispatcher);
TCalendar.ownAttribute = 'calendar_bind_id';

/**
 * Recreate calendar using this.current values
 * and make selections on those calendars using
 * this.selected
 */
TCalendar.prototype.update = function() {
  var width = 0, height = 0;
  var date = new Date(this.current.year, this.current.month-Math.floor((this.current.count-1)/2)-1, 1);
  this._months = [];
  this._slider.style.left = '0px';
  while (this._slider.childNodes.length > 0) {
    this._slider.removeChild(this._slider.childNodes[0]);
  }
  for (var i=0; i < this.current.count; i++) {
    var cal = this._createCalendar(date.getFullYear(),date.getMonth()+1,width);
    this._months.push({cal:cal, offset:width});
    width += cal.size.width;
    height = cal.size.height > height ? cal.size.height : height;
    date.setMonth(date.getMonth()+1);
  }
  this._inner.style.width = width + 'px';
  this._container.style.width = width + 'px';
  this._footer.style.paddingTop = height + 'px';
  if (this._iframe) {
    this._iframe.style.width = (width+3) + 'px';
    this._iframe.style.height = height + 'px';
  }
}

/**
 * Show count months starting at y/m
 *
 * @param {Integer} y: calendar year
 * @param {Integer} m: calendar month
 * @param {Integer} [count=1] = calendar count
 */
TCalendar.prototype.show = function(y,m,count,startday) {
  y = parseInt(y); m = parseInt(m); count = count || 1;
  if (!isNaN(y) && !isNaN(m)) {
    var date = new Date(y, m-1, 1);
    m = date.getMonth()+1;
    y = date.getFullYear();
    if (y != this.current.year || m != this.current.month || this.current.count != count) {
      this.current.year = y;
      this.current.month = m;
      this.current.count = count;
      this.current.startday = startday;
      this.update();
    }
  }
}

/**
 * Select a single date or range and set current
 * displayed calendar to the month of that date or
 * the month of the beginning of the range
 *
 * @param {String | Date} from: string (dd/mm/yyyy) or date object of the selection start
 * @param {String | Date} [to]: string (dd/mm/yyyy) or date object of the selection end
 */
TCalendar.prototype.select = function(from,to) {
  to = to || from;
  if (typeof from == 'string') {
    from = this._parseDate(from);
  }
  if (typeof to == 'string') {
    to = this._parseDate(to);
  }
  this.selected.from = from;
  this.selected.to = to;
  for (var i=0; i < this._months.length; i++) {
    this._months[i].cal.select(from, to);
  }
}

/**
 * Relocate calendar around a rec depending on pos.
 * Pos values are as follow:
 *   tl -> TOP LEFT         bl -> BOTTOM LEFT
 *   tm -> TOP MIDDLE       bm -> BOTTOM MIDDLE
 *   tr -> TOP RIGHT        br -> BOTTOM RIGHT
 *
 *   lt -> LEFT TOP         rt -> RIGHT TOP
 *   lm -> LEFT MIDDLE      rm -> RIGHT MIDDLE
 *   lb -> LEFT RIGHT       rb -> RIGHT BOTTOM
 *
 * @param {TRect} rec: rectable around wich relocate calendar
 * @param {Position} pos: see notes above
 *
 * @todo: Implement otrer positions than bm
 */
TCalendar.prototype.position = function(rec, pos) {
  var rec2 = TDOM.getBounds(this._container);
  this._container.style.top = rec.top + rec.height + 1 + 'px';
  this._container.style.left = rec.left - Math.round((rec2.width - rec.width)/2) + 'px';
  if (TUserAgent.OPERA) {
    var rec2 = TDOM.getBounds(this._container);
    this._container.style.top = rec.top + rec.height + 1 + 'px';
    this._container.style.left = rec.left - Math.round((rec2.width - rec.width)/2) + 'px';
  }
}

/**
 * Slide calendar one month forward
 */
TCalendar.prototype.showNextMonth = function(e) {
  var last = this._months[this._months.length-1], first = this._months.shift();
  var m = (last.cal.m%12)+1, y = last.cal.y+(m==1?1:0), left = last.offset+last.cal.size.width;
  var cal = this._createCalendar(y,m,left);
  this._months.push({cal:cal, offset:left});
  this.current.year = y;
  this.current.month = m;

  this._slider.style.left = parseInt(this._slider.style.left) - cal.size.width + 'px';
  this._slider.removeChild(first.cal.container);
}

/**
 * Slide calendar 1 month backward
 */
TCalendar.prototype.showPrevMonth = function(e) {
  var first = this._months[0], last = this._months.pop();
  var m = first.cal.m-1<1?12:first.cal.m-1, y = first.cal.y-(m==12?1:0), left = first.offset-first.cal.size.width;
  var cal = this._createCalendar(y,m,left);
  if (first.offset-cal.size.width != left) { left = first.offset-cal.size.width; }
  cal.container.style.left = left + 'px';
  this._months.unshift({cal:cal, offset:left});
  this.current.year = y;
  this.current.month = m;

  this._slider.style.left = parseInt(this._slider.style.left) + cal.size.width + 'px';
  this._slider.removeChild(last.cal.container);
}

/**
 * Hide the calendar
 * Actually hide the calendar container
 */
TCalendar.prototype.hide = function() {
  this._activeInput = null;
  this._overCalendar = false;
  this._container.style.display = 'none';
}

/**
 * Bind calendar to 1 or 2 text inputs.
 * Binding a calendar to 2 inputs allow from/to date range
 * selection while binding to 1 allow single date selection.
 *
 * Setting position != null allow calendar relocation around
 * inputs bounds. (See position() method to view allowed positions)
 *
 * @param {Text Input} input1: bind caledar to 1 input
 * @param {Text Input} input2: bind caledar to 2 input
 * @param {Integer} pages: number of calendar to display for this configuuration
 * @param {String} position: position to relocate calendar
 * @param {Integer} startday: first day of the week
 */
TCalendar.prototype.bind = function(input1, input2, button, pages, position, startday) {
  pages = pages || 1;
  startday = startday || 0;
  input1 = TDOM.getElement(input1);
  input2 = TDOM.getElement(input2);
  button = TDOM.getElement(button);
  if (!input1 || input1.nodeName.toLowerCase() != 'input') {
    alert("You can only bind calendar to INPUT elements of type TEXT");
    return;
  }
  input1[TCalendar.ownAttribute] = this._bindings.length;
  TEvents.listen(input1, 'blur', this._hideBinded, this);
  TEvents.listen(input1, 'focus', this._showBinded, this);
  if (input2) {
    input2[TCalendar.ownAttribute] = this._bindings.length;
    TEvents.listen(input2, 'blur', this._hideBinded, this);
    TEvents.listen(input2, 'focus', this._showBinded, this);
  }
  if (button) {
    button[TCalendar.ownAttribute] = this._bindings.length;
	TEvents.listen(button,'click', this._showBinded, this);
  }
  this._bindings.push([input1,input2,pages,position,startday,button]);
}

/**
 * Fired by binded inputs receive focus
 */
TCalendar.prototype._showBinded = function(e) {
  if (this._overCalendar) { return; }
  this._activeInput = e.currentTarget;
  var bind = this._bindings[e.currentTarget[TCalendar.ownAttribute]];
  if (bind) {
	if (bind[5]) {
		var rec1 = TDOM.getBounds(bind[5]);
		var rec2 = rec1;	
	} else {
		var rec1 = TDOM.getBounds(bind[0]);
		var rec2 = bind[1] ? TDOM.getBounds(bind[1]) : rec1;
	}
    var date1 = this._parseDate(bind[0].value);
    var date2 = bind[1] ? this._parseDate(bind[1].value) : date1;

    var top = rec1.top < rec2.top ? rec1.top : rec2.top;
    var left = rec1.left < rec2.left ? rec1.left : rec2.left;
    var width = rec1.left < rec2.left ? rec2.left+rec2.width-rec1.left : rec1.left+rec1.width-rec2.left;
    var height = rec1.top < rec2.top ? rec2.top+rec2.height-rec1.top : rec1.top+rec1.height-rec2.top;

    this.select(date1,date2);
    if (e.currentTarget == bind[1]) {
      this.show(date2.getFullYear(), date2.getMonth()+1, bind[2], bind[4]);
    } else {
      this.show(date1.getFullYear(), date1.getMonth()+1, bind[2], bind[4]);
    }
    this.position({ top:top, left:left, width:width, height:height }, bind[3]);
    this._container.style.display = 'block';
    this.rangeSelect = bind[1] != null;
    this._selectRange = e.currentTarget == bind[1];
    TDOM.addClass(bind[0], 'capturing');
    TDOM.addClass(bind[1], 'capturing');
  }
}

/**
 * Fired by binded input to hide calendar on input exit
 */
TCalendar.prototype._hideBinded = function(e) {
  if (!this._overCalendar) {
    if (this._activeInput) {
      var bind = this._bindings[this._activeInput[TCalendar.ownAttribute]];
      TDOM.removeClass(bind[0], 'capturing');
      TDOM.removeClass(bind[1], 'capturing');
    }
    this.hide();
  } else {
    this._activeInput.focus();  // Not working on FF, use container focus event instead
    e.preventDefault();
    e.stopPropagation();
    return false;
  }
}

/**
 * Handler for date change event on calendars
 */
TCalendar.prototype._dateChange = function(e,y,m,d) {
  var date = this._parseDate(d+'/'+m+'/'+y)
  if (this._selectRange && this.rangeSelect) {
    this._selectRange = false;
    if (date < this.selected.from) {
      this.select(date, this.selected.from);
    } else {
      this.select(this.selected.from, date);
    }
  } else {
    this._selectRange = true;
    this.select(date, date);
  }
  var fy = this.selected.from.getFullYear();
  var fm = this.selected.from.getMonth()+1;
  var fd = this.selected.from.getDate();
  var ty = this.selected.to.getFullYear();
  var tm = this.selected.to.getMonth()+1;
  var td = this.selected.to.getDate();
  if (this._activeInput) {
    var bind = this._bindings[this._activeInput[TCalendar.ownAttribute]];
    if (bind) {
      bind[0].value = fd.toString().padLeft(2,'0')+'/'+fm.toString().padLeft(2,'0')+'/'+fy;
      if (bind[1]) {
        bind[1].value = td.toString().padLeft(2,'0')+'/'+tm.toString().padLeft(2,'0')+'/'+ty;
      }
    }
  }
  this.dispatch('change',fy,fm,fd,ty,tm,td,bind[0],bind[1],bind[5]);
}

/**
 * Create a new month calendar
 */
TCalendar.prototype._createCalendar = function (y,m,pos) {
	var div = document.createElement("div");
  div.style.position = 'absolute';
  div.style.top = '0px';
  div.style.left = pos + 'px';
  this._slider.appendChild(div);
  cal = new TMonthCalendar(div, y, m, this.current.startday);
  cal.addEventListener('change',this._dateChange,this);
  cal.select(this.selected.from, this.selected.to);
  return cal;
}

/**
 * Create calendar gui inside this._container and add
 * needed events handlers
 * Calendar Slider has the following elements:
 *   1- a wrapper div in where calendars are filled
 *   2- two buttons to navigate through calendars
 *   3- a footer div to place extra functions
 */
TCalendar.prototype._setupCalendar = function() {
  TDOM.addClass(this._container, 'calendar');
  TEvents.listen(this._container,'focus',this._setFocusOut,this);
  TEvents.listen(this._container,'mouseup',this._setFocusOut,this);
  TEvents.listen(this._container,'mouseover',this._setMouseOver,this);
  TEvents.listen(this._container,'mouseout',this._setMouseOut,this);

  this._inner = document.createElement('div');
  this._inner.style.position = 'relative';
  this._inner.style.overflow = 'hidden';
  TDOM.addClass(this._inner, 'inner');
  this._container.appendChild(this._inner);

  this._slider = document.createElement('div');
  this._slider.style.position = 'absolute';
  this._slider.style.top = '0px';
  this._slider.style.left = '0px';
  TDOM.addClass(this._slider, 'slider');
  this._inner.appendChild(this._slider);

  var div = document.createElement('div');
  TDOM.addClass(div, 'next');
  TEvents.listen(div, 'click', this.showNextMonth, this);
  this._container.appendChild(div);

  div = document.createElement('div');
  TDOM.addClass(div, 'prev');
  TEvents.listen(div, 'click', this.showPrevMonth, this);
  this._container.appendChild(div);

  this._footer = document.createElement('div');
  TDOM.addClass(this._footer, 'footer');
  this._inner.appendChild(this._footer);

  var btn = document.createElement('a');
  btn.innerHTML = 'cerrar';
  TDOM.addClass(btn, 'close');
  TEvents.listen(btn, 'click', this.hide, this);
  this._footer.appendChild(btn);
}

/**
 * Parse an string date (dd/mm/yyy) and return
 * a Date object.
 * If string fail to parse actual date is returned
 */
TCalendar.prototype._parseDate = function(str) {
  var date = new Date();
  str = str.split('/');
  str[0] = parseInt(str[0],10);
  str[1] = parseInt(str[1],10);
  str[2] = parseInt(str[2],10);
  // Falta validar aņo y restringir fechas
  valid = !isNaN(str[0]) && !isNaN(str[1]) && !isNaN(str[2]);
  valid = valid && str[1] <= 12 && str[1] > 0 && str[0] > 0;
  if (valid) {
    date.setDate(1);
    date.setFullYear(str[2]);
    date.setMonth(str[1]);  // 1 month ahead
    date.setDate(0);        // get total days in month
    str[0] = str[0] > date.getDate() ? date.getDate() : str[0];
    date.setDate(str[0]);
  }
  return date;
}

TCalendar.prototype._setFocusOut = function(e) {
  if (this._activeInput) {
    this._activeInput.focus();      // Needed to maintain focus on input in FF
  }
}

TCalendar.prototype._setMouseOver = function(e) {
  this._overCalendar = true;
}

TCalendar.prototype._setMouseOut = function(e) {
  if (e.currentTarget == this._container) {
    this._overCalendar = false;
  }
}