/**
 *  This file contains Calendar utilities
 *  :organization: Logilab
 *  :copyright: 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 *  :contact: https://www.logilab.fr/ -- mailto:contact@logilab.fr
 */

// IMPORTANT NOTE: the variables DAYNAMES AND MONTHNAMES will be added
//                 by cubicweb automatically
// dynamically computed (and cached)
var _CAL_HEADER = null;

TODAY = new Date();

/**
 * .. class:: Calendar
 *
 *   Calendar (graphical) widget
 *
 *   public methods are :
 *
 *   __init__ :
 *    :attr:`containerId`: the DOM node's ID where the calendar will be displayed
 *    :attr:`inputId`: which input needs to be updated when a date is selected
 *    :attr:`year`, :attr:`month`: year and month to be displayed
 *    :attr:`cssclass`: CSS class of the calendar widget (default is 'commandCal')
 *
 *   show() / hide():
 *    show or hide the calendar widget
 *
 *   toggle():
 *    show (resp. hide) the calendar if it's hidden (resp. displayed)
 *
 *   displayNextMonth(): (resp. displayPreviousMonth())
 *    update the calendar to display next (resp. previous) month
 */
Calendar = function (containerId, inputId, year, month, cssclass) {
  this.containerId = containerId;
  this.inputId = inputId;
  this.year = year;
  this.month = month - 1; // Javascript's counter starts at 0 for january
  this.cssclass = cssclass || "popupCalendar";
  this.visible = false;
  this.domtable = null;

  this.cellprops = {
    onclick: function () {
      dateSelected(this, containerId);
    },
    onmouseover: function () {
      this.style.fontWeight = "bold";
    },
    onmouseout: function () {
      this.style.fontWeight = "normal";
    },
  };

  this.todayprops = jQuery.extend({}, this.cellprops, {
    class: "today",
  });

  this._rowdisplay = function (row) {
    var _td = function (elt) {
      return TD(this.cellprops, elt);
    };
    return TR(null, $.map(row, _td));
  };

  this._makecell = function (cellinfo) {
    return TD(cellinfo[0], cellinfo[1]);
  };

  /**
   * .. function:: Calendar._uppercaseFirst(s)
   *
   *    utility function (the only use for now is inside the calendar)
   */
  this._uppercaseFirst = function (s) {
    return s.charAt(0).toUpperCase();
  };

  /**
   * .. function:: Calendar._domForRows(rows)
   *
   *    accepts the cells data and builds the corresponding TR nodes
   *
   * * `rows`, a list of list of couples (daynum, cssprops)
   */
  this._domForRows = function (rows) {
    var lines = [];
    for (i = 0; i < rows.length; i++) {
      lines.push(TR(null, $.map(rows[i], this._makecell)));
    }
    return lines;
  };

  /**
   * .. function:: Calendar._headdisplay(row)
   *
   *    builds the calendar headers
   */
  this._headdisplay = function (row) {
    if (_CAL_HEADER) {
      return _CAL_HEADER;
    }
    var self = this;
    var _th = function (day) {
      return TH(null, self._uppercaseFirst(day));
    };
    return TR(null, $.map(DAYNAMES, _th));
  };

  this._getrows = function () {
    var rows = [];
    var firstday = new Date(this.year, this.month, 1);
    var stopdate = firstday.nextMonth();
    var curdate = firstday.sub(firstday.getRealDay());
    while (curdate.getTime() < stopdate) {
      var row = [];
      for (var i = 0; i < 7; i++) {
        if (curdate.getMonth() == this.month) {
          props = curdate.equals(TODAY) ? this.todayprops : this.cellprops;
          row.push([props, curdate.getDate()]);
        } else {
          row.push([this.cellprops, ""]);
        }
        curdate.iadd(1);
      }
      rows.push(row);
    }
    return rows;
  };

  this._makecal = function () {
    var rows = this._getrows();
    var monthname = MONTHNAMES[this.month] + " " + this.year;
    var prevlink =
      "javascript: togglePreviousMonth('" + this.containerId + "');";
    var nextlink = "javascript: toggleNextMonth('" + this.containerId + "');";
    this.domtable = TABLE(
      {
        class: this.cssclass,
      },
      THEAD(
        null,
        TR(
          null,
          TH(
            null,
            A(
              {
                href: prevlink,
              },
              "<<"
            )
          ),
          // IE 6/7 requires colSpan instead of colspan
          TH(
            {
              colSpan: 5,
              colspan: 5,
              style: "text-align: center;",
            },
            monthname
          ),
          TH(
            null,
            A(
              {
                href: nextlink,
              },
              ">>"
            )
          )
        )
      ),
      TBODY(null, this._headdisplay(), this._domForRows(rows))
    );
    return this.domtable;
  };

  this._updateDiv = function () {
    if (!this.domtable) {
      this._makecal();
    }
    cw.jqNode(this.containerId).empty().append(this.domtable);
    // replaceChildNodes($(this.containerId), this.domtable);
  };

  this.displayNextMonth = function () {
    this.domtable = null;
    if (this.month == 11) {
      this.year++;
    }
    this.month = (this.month + 1) % 12;
    this._updateDiv();
  };

  this.displayPreviousMonth = function () {
    this.domtable = null;
    if (this.month == 0) {
      this.year--;
    }
    this.month = (this.month + 11) % 12;
    this._updateDiv();
  };

  this.show = function () {
    if (!this.visible) {
      var container = cw.jqNode(this.containerId);
      if (!this.domtable) {
        this._makecal();
      }
      container.empty().append(this.domtable);
      toggleVisibility(container);
      this.visible = true;
    }
  };

  this.hide = function (event) {
    var self;
    if (event) {
      self = event.data.self;
    } else {
      self = this;
    }
    if (self.visible) {
      toggleVisibility(self.containerId);
      self.visible = false;
    }
  };

  this.toggle = function () {
    if (this.visible) {
      this.hide();
    } else {
      this.show();
    }
  };

  // call hide() when the user explicitly sets the focus on the matching input
  cw.jqNode(inputId).bind(
    "focus",
    {
      self: this,
    },
    this.hide
  ); // connect(inputId, 'onfocus', this, 'hide');
};

/**
 * .. data:: Calendar.REGISTRY
 *
 *     keep track of each calendar created
 */
Calendar.REGISTRY = {};

/**
 * .. function:: toggleCalendar(containerId, inputId, year, month)
 *
 *    popup / hide calendar associated to `containerId`
 */
function toggleCalendar(containerId, inputId, year, month) {
  var cal = Calendar.REGISTRY[containerId];
  if (!cal) {
    cal = new Calendar(containerId, inputId, year, month);
    Calendar.REGISTRY[containerId] = cal;
  }
  /* hide other calendars */
  for (containerId in Calendar.REGISTRY) {
    var othercal = Calendar.REGISTRY[containerId];
    if (othercal !== cal) {
      othercal.hide();
    }
  }
  cal.toggle();
}

/**
 * .. function:: toggleNextMonth(containerId)
 *
 *    ask for next month to calendar displayed in `containerId`
 */
function toggleNextMonth(containerId) {
  var cal = Calendar.REGISTRY[containerId];
  cal.displayNextMonth();
}

/**
 * .. function:: togglePreviousMonth(containerId)
 *
 *    ask for previous month to calendar displayed in `containerId`
 */
function togglePreviousMonth(containerId) {
  var cal = Calendar.REGISTRY[containerId];
  cal.displayPreviousMonth();
}

/**
 * .. function:: dateSelected(cell, containerId)
 *
 *    callback called when the user clicked on a cell in the popup calendar
 */
function dateSelected(cell, containerId) {
  var cal = Calendar.REGISTRY[containerId];
  var input = cw.getNode(cal.inputId);
  // XXX: the use of innerHTML might cause problems, but it seems to be
  //      the only way understood by both IE and Mozilla. Otherwise,
  //      IE accepts innerText and mozilla accepts textContent
  var selectedDate = new Date(cal.year, cal.month, cell.innerHTML, 12);
  input.value = remoteExec(
    "format_date",
    cw.utils.toISOTimestamp(selectedDate)
  );
  cal.hide();
}

function whichElement(e) {
  var targ;
  if (!e) {
    e = window.event;
  }
  if (e.target) {
    targ = e.target;
  } else if (e.srcElement) {
    targ = e.srcElement;
  }
  if (targ.nodeType == 3) {
    // defeat Safari bug
    targ = targ.parentNode;
  }
  return targ;
}

function getPosition(element) {
  var left;
  var top;
  var offset;
  // TODO: deal scrollbar positions also!
  left = element.offsetLeft;
  top = element.offsetTop;

  if (element.offsetParent != null) {
    offset = getPosition(element.offsetParent);
    left = left + offset[0];
    top = top + offset[1];
  }
  return [left, top];
}

function getMouseInBlock(event) {
  var elt = event.target;
  var x = event.clientX;
  var y = event.clientY;
  var w = elt.clientWidth;
  var h = elt.clientHeight;
  var offset = getPosition(elt);

  x = (1.0 * (x - offset[0])) / w;
  y = (1.0 * (y - offset[1])) / h;
  return [x, y];
}
function getHourFromMouse(event, hmin, hmax) {
  var pos = getMouseInBlock(event);
  var y = pos[1];
  return Math.floor((hmax - hmin) * y + hmin);
}

function addCalendarItem(
  event,
  hmin,
  hmax,
  year,
  month,
  day,
  duration,
  baseurl
) {
  var hour = getHourFromMouse(event, hmin, hmax);

  if (0 <= hour && hour < 24) {
    baseurl +=
      "&start=" + year + "%2F" + month + "%2F" + day + "%20" + hour + ":00";
    baseurl +=
      "&stop=" +
      year +
      "%2F" +
      month +
      "%2F" +
      day +
      "%20" +
      (hour + duration) +
      ":00";

    stopPropagation(event);
    window.location.assign(baseurl);
    return false;
  }
  return true;
}

function stopPropagation(event) {
  event.cancelBubble = true;
  if (event.stopPropagation) event.stopPropagation();
}
