/* ***** BEGIN LICENSE BLOCK *****
 * Licensed under Version: MPL 1.1/GPL 2.0/LGPL 2.1
 * Full Terms at http://bclary.com/lib/js/license/mpl-tri-license.txt
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is Jeremy Hoppe code.
 *
 * The Initial Developer of the Original Code is
 * Jeremy Hoppe.
 * Portions created by the Initial Developer are Copyright (C) 2002
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s): Jeremy Hoppe <jeremyah83@yahoo.com>
 *
 * ***** END LICENSE BLOCK ***** */


_classes.registerClass('xbPopupCalendar', 'xbCalendar');


// Creates a popup calendar that can be shown at any x,y position
// or adjacent to an element.

// Messages for link titles and status-line info
xbPopupCalendar.nextMonthMsg = 'Navigate to the next month.';
xbPopupCalendar.nextYearMsg  = 'Navigate to the next year.';
xbPopupCalendar.prevMonthMsg = 'Navigate to the previous month.';
xbPopupCalendar.prevYearMsg  = 'Navigate to the previous year.';


function xbPopupCalendar(id, date, classprefix, dayNameFormat, firstDayOfWeek, windowRef)
{
  _classes.defineClass('xbPopupCalendar', _prototype_func);

  this.init(id, date, classprefix, dayNameFormat, firstDayOfWeek, windowRef);

  function _prototype_func()
  {
    xbPopupCalendar.prototype.init = init;
    function init(id, date, classprefix, dayNameFormat, firstDayOfWeek, windowRef)
    {
      // super()
      this.parentMethod('init', id, date, classprefix, dayNameFormat, firstDayOfWeek, windowRef);

      // Properties
      this.div    = null;
      this.navBar = null;
      this.nextMonthNav = null;
      this.nextYearNav  = null;
      this.prevMonthNav = null;
      this.prevYearNav  = null;
      this.visible      = false;

      // Internal state properties
      this._fireChooseEvent = false;

      // Events
      this.ondatechoose = null;

      // Build popup <div> element
      var doc     = this.window.document;
      var d       = this.div = doc.createElement('div');
      d.className = this.classprefix + 'popup';

      d.id = (this.name + '_div');
      d.appendChild(this.table);  // Insert table into div
      var s = d.style;
      s.visibility = 'hidden';
      s.position   = 'absolute';
      s.left       = '-100px';
      s.top        = '-100px';
      s.zIndex     = 0;

      // Create navigation div
      this.navBar   = doc.createElement('div');
      var nb        = this.navBar;
      nb.className  = this.classprefix + 'navBar';
      this.div.appendChild(nb);

      // Create navigation links. Most of the grunt work
      // is done in _createNavLink() and _prepareNavigationObject()
      this.prevYearNav = this._createNavLink('<<');
      this._prepareNavigationObject(this.prevYearNav, 'prevYearNav', xbPopupCalendar.prevYearMsg);

      this.prevMonthNav = this._createNavLink('<');
      this._prepareNavigationObject(this.prevMonthNav, 'prevMonthNav', xbPopupCalendar.prevMonthMsg);

      this.nextMonthNav = this._createNavLink('>');
      this._prepareNavigationObject(this.nextMonthNav, 'nextMonthNav', xbPopupCalendar.nextMonthMsg);

      this.nextYearNav = this._createNavLink('>>');
      this._prepareNavigationObject(this.nextYearNav, 'nextYearNav', xbPopupCalendar.nextYearMsg);
    }

    // Frees memory used by this instance
    xbPopupCalendar.prototype.destroy = destroy;
    function destroy()
    {
      this.div = null;
      this.navBar = null;
      this.nextMonthNav = null;
      this.nextYearNav  = null;
      this.prevMonthNav = null;
      this.prevYearNav  = null;
      this.parentMethod('destroy');
    }

    /* Returns the popup <div> element containing the calendar
     * Overrides getNode() in xbCalendar
     */
    xbPopupCalendar.prototype.getNode = getNode;
    function getNode()
    {
      return this.div;
    }

    // Hides the popup div
    xbPopupCalendar.prototype.hide = hide;
    function hide()
    {
      if (!this.visible)
        return;

      var s = this.div.style;
      s.visibility = 'hidden';
      s.zIndex     = 0;
      this.visible = false;
    }

    // Returns true if the popup calendar is visible
    xbPopupCalendar.prototype.isVisible = isVisible;
    function isVisible()
    {
      return this.visible;
    }

    // Overrides setDate() in xbCalendar to fire ondatechoose event
    xbPopupCalendar.prototype.setDate = setDate;
    function setDate(dateObj)
    {
      var d = this.date;

      // This will be true if this method is called
      // as a result of the user choosing a date
      if (this._fireChooseEvent == true)
      {
        // Return if dateObj is outside of the range of valid dates.
        if ((dateObj < this.minDate) || (dateObj > this.maxDate))
        {
          // Reset flags
          this._fireChooseEvent  = false;
          this._needsLinkRefocus = false;
          return;
        }

        var rv = true;

        // Fire event
        if (typeof(this.ondatechoose) == 'function')
        {
          rv = this.ondatechoose( new Date(dateObj.toUTCString()) );
        }

        if (rv == false)
          return;

        // Reset flags for next time
        this._fireChooseEvent  = false;
        this._needsLinkRefocus = false; // We don't want refocusing if the user has chosen a date

        this.date = dateObj;
        this.refresh(false);
        this.hide();
      }
      else
      {
        this.parentMethod('setDate', dateObj);
      }
    }

    // Show the popup at the specified x,y coordinates
    xbPopupCalendar.prototype.show = show;
    function show(x, y)
    {
      if (this.visible)
        return;
      var s = this.div.style;
      s.left       = x + 'px';
      s.top        = y + 'px';
      s.zIndex     = 1000;
      s.visibility = 'visible';
      this.visible = true;
    }

    // Show the popup adjacent to the specified element
    xbPopupCalendar.prototype.showAdjacentTo = showAdjacentTo;
    function showAdjacentTo(elem, dir, offsetX, offsetY)
    {
      if (this.visible)
        return;

      var o = _getOffsets(elem);
      var d = this.div;
      var s = d.style;

      if (typeof(offsetX) != 'number')
        offsetX = 0;

      if (typeof(offsetY) != 'number')
        offsetY = 0;

      var x;
      var y;

      switch(dir.toLowerCase())
      {
        case 'above':
          x = o.left;
          y = (o.top - d.offsetHeight);
          break;

        case 'below':
          x = o.left;
          y = (o.top + o.height);
          break;

        case 'left':
          x = (o.left - d.offsetWidth);
          y = o.top;
          break;

        case 'right':
        default:
          x = o.left + o.width;
          y = o.top;
      }

      s.left       = (x + parseInt(offsetX)) + 'px';
      s.top        = (y + parseInt(offsetY)) + 'px';
      s.zIndex     = 100;
      s.visibility = 'visible';
      this.visible = true;
    }

    // Returns an Object containing an element's offsets
    function _getOffsets(e)
    {
      var o = {
        height: e.offsetHeight,
        width: e.offsetWidth
      };

      var x = e.offsetLeft;
      var y = e.offsetTop;
      var p = e.offsetParent;

      while(p && (p.nodeType != 9))
      {
        x += p.offsetLeft;
        y += p.offsetTop;
        p = p.offsetParent;
      }
      o.left = x;
      o.top = y;
      return o;
    }

    /* = = = = = = = = = = = = = = = = =
     * nextMonthNav get/set
     * = = = = = = = = = = = = = = = = */

    xbPopupCalendar.prototype.getNextMonthNav = getNextMonthNav;
    function getNextMonthNav()
    {
      return this.nextMonthNav;
    }

    xbPopupCalendar.prototype.setNextMonthNav = setNextMonthNav;
    function setNextMonthNav(node)
    {
      this._prepareNavigationObject(node, 'nextMonthNav', xbPopupCalendar.nextMonthMsg);
      var n = this.navBar;
      n.removeChild(this.nextMonthNav);
      this.nextMonthNav = n.appendChild(node);
    }

    /* = = = = = = = = = = = = = = = = =
     * nextYearNav get/set
     * = = = = = = = = = = = = = = = = */

    xbPopupCalendar.prototype.getNextYearNav = getNextYearNav;
    function getNextYearNav()
    {
      return this.nextYearNav;
    }

    xbPopupCalendar.prototype.setNextYearNav = setNextYearNav;
    function setNextYearNav(node)
    {
      this._prepareNavigationObject(node, 'nextYearNav', xbPopupCalendar.nextYearMsg);
      var n = this.navBar;
      n.removeChild(this.nextYearNav);
      this.nextYearNav = n.appendChild(node);
    }

    /* = = = = = = = = = = = = = = = = =
     * prevMonthNav get/set
     * = = = = = = = = = = = = = = = = */

    xbPopupCalendar.prototype.getPrevMonthNav = getPrevMonthNav;
    function getPrevMonthNav()
    {
      return this.prevMonthNav;
    }

    xbPopupCalendar.prototype.setPrevMonthNav = setPrevMonthNav;
    function setPrevMonthNav(node)
    {
      this._prepareNavigationObject(node, 'prevMonthNav', xbPopupCalendar.prevMonthMsg);
      var n = this.navBar;
      n.removeChild(this.prevMonthNav);
      this.prevMonthNav = n.appendChild(node);
    }

    /* = = = = = = = = = = = = = = = = =
     * prevYearNav get/set
     * = = = = = = = = = = = = = = = = */

    xbPopupCalendar.prototype.getPrevYearNav = getPrevYearNav;
    function getPrevYearNav()
    {
      return this.prevYearNav;
    }

    xbPopupCalendar.prototype.setPrevYearNav = setPrevYearNav;
    function setPrevYearNav(node)
    {
      this._prepareNavigationObject(node, 'prevYearNav', xbPopupCalendar.prevYearMsg);
      var n = this.navBar;
      n.removeChild(this.prevYearNav);
      this.prevYearNav = n.appendChild(node);
    }

    /* = = = = = = = = = = = = = = = = =
     * Private methods
     * = = = = = = = = = = = = = = = = */

    // Creates and returns a link for the navigation bar.
    xbPopupCalendar.prototype._createNavLink = _createNavLink;
    function _createNavLink(label)
    {
      var doc = this.window.document;
      var a = doc.createElement('a');
      this.navBar.appendChild(a);

      a.className = this.classprefix + 'navLink';
      a.href = '#';

      a.appendChild( doc.createTextNode(label) );
      return a;
    }

    // Overrides _linkEvent() in xbCalendar to implement support for
    // the ondatechoose event.
    xbPopupCalendar.prototype._linkEvent = _linkEvent;
    function _linkEvent(evt)
    {
      var elem = evt.target;

      if (elem.nodeType == 3)
        elem = elem.parentNode;

      var id = elem.id;

      // Get global identifier for this instance
      var instanceId = id.substring(0, id.indexOf('_'));

      // Get actual instance. Instances are always stored
      // in the window that the script itself resides in,
      // regardless of which frame the calendar is in.
      var inst = window[instanceId];

      // Fire ondatechoose? (actual event firing happens in setDate() )
      if (evt.type == 'click')
        inst._fireChooseEvent = true;

      inst.parentMethod('_linkEvent', evt);
    }

    // Invoked when the one of the following events occurs on
    // navigation objects: click, mouseover, mouseout
    xbPopupCalendar.prototype._navigateEvent = _navigateEvent;
    function _navigateEvent(evt)
    {
      var windowRef = evt.view;
      var evtType   = evt.type;

      // Get event target
      var elem = evt.target;

      if (elem.nodeType == 3)
        elem = elem.parentNode;

      // If the event occurred on a contained element
      // (such as an img within a link), find the actual navigation
      // object, which has important info stored in it's ID.
      if (!elem.id || (elem.id && elem.id.indexOf('xbCalendar') == -1))
      {
        while(elem && (elem.nodeType != 9) && (elem.id.indexOf('xbCalendar') == -1))
          elem = elem.parentNode;
      }

      var id         = elem.id;
      var index      = id.indexOf('_');
      var instanceId = id.substring(0, index);
      var linkId     = id.substring(index + 1);

      // Get pointer to this instance
      var inst = window[instanceId];

      // Stop event propagation
      if (evt.stopPropagation)
        evt.stopPropagation();
      else
        evt.cancelBubble = true;

      // Prevent event's default action
      if (evt.preventDefault)
        evt.preventDefault();
      else
        evt.returnValue = (evtType == 'click') ? false : true;


      if (evtType == 'click')
      {
        switch(linkId)
        {
          case 'prevYearNav':
            inst.prevYear();
            break;

          case 'prevMonthNav':
            inst.prevMonth();
            break;

          case 'nextMonthNav':
            inst.nextMonth();
            break;

          case 'nextYearNav':
            inst.nextYear();
            break;
        }
      }

      else if (evtType == 'mouseover')
      {
        if (inst.showStatusInfo)
        {
          var m = linkId.match(/^(prev|next)(Month|Year)(Nav)$/);
          windowRef.status = xbPopupCalendar[m[1] + m[2] + 'Msg'];
        }
        else windowRef.status = '';
      }

      else if (evtType == 'mouseout')
      {
        windowRef.status = (windowRef.defaultStatus) ? windowRef.defaultStatus : '';
      }
    }

    // This does all of the work involved in making a node into a navigation object
    xbPopupCalendar.prototype._prepareNavigationObject = _prepareNavigationObject;
    function _prepareNavigationObject(object, id, title)
    {
      // Handle the case where someone may do something like:
      //   var n = cal.getNextYearNode();
      //   n.firstChild.nodeValue = 'Next Year';
      //   cal.setNextYearNode(n); // unecessary in this case, btw
      // in which case we can skip the stuff in this method

      if (object.id && (object.id == (this.name + '_' + id) ) )
        return;

      object.id = (this.name + '_' + id);

      if (this.useTitles)
        object.title = title;

      var handler = this._createEventHandler( this._navigateEvent, this.window );
      object.onclick     = handler;
      object.onmouseover = handler;
      object.onmouseout  = handler;
    }
  }
}

