/*
Copyright (c) 2008, KIMI. All rights reserved.
Code licensed under the BSD License:
http://ocal.jp/javascripts/bsd-license.txt

部分的にYUIのコードを含みます
Copyright (c) 2006, Yahoo! Inc. All rights reserved.
Code licensed under the BSD License:
http://developer.yahoo.net/yui/license.txt
*/

/** @fileOverview カレンダーを指定された要素に描きます */

var OCAL={"version": "0.2.0", "url": "http://ocal.jp"};

/**
 * 吹き出し
 * @class OCAL.ToolTip
 * @param {Object} json 設定
 */
OCAL.ToolTip=function(json){
  this.cfg={};
  /**
   * 吹き出しように{用意してある,作成する}エレメントのID
   * @type {String}
   */
  this.cfg.id='ocal_tooltip';
  /**
   * 吹き出す時のマウスとの距離
   * @type {Number}
   */
  this.cfg.margin=20;
  if(json) for(var key in json) this.cfg[key] = json[key];

  var el=document.getElementById(this.cfg.id);
  if(!el){
    el=document.createElement('DIV');
    el.id=this.cfg.id;
    el.style.display='none';
    el.innerHTML =
    '<b class="rtop">'+
    '<b class="r1"></b><b class="r2"></b><b class="r3"></b><b class="r4"></b>'+
    '</b>'+
    '<dl><dt>&nbsp;</dt><dd>&nbsp;</dd></dl>'+
    '<b class="rbottom">'+
    '<b class="r4"></b><b class="r3"></b><b class="r2"></b><b class="r1"></b>'+
    '</b>';
    document.body.appendChild(el);
  }
};

OCAL.ToolTip.prototype={
  /**
   * 吹き出す場所を設定
   * @param {MouseEvent} [e] マウスイベント
   */
  position:function(e){
    e=OCAL.getEvent(e);
    var el=document.getElementById(this.cfg.id);

    var mouseXY = OCAL.getMouseXY(e);
    var scrollXY = OCAL.getScrollXY();
    var windowWH = OCAL.getWindowWH();
    el.style.left = (windowWH[0]/2 > mouseXY[0] - scrollXY[0])?
      mouseXY[0] + this.cfg.margin + 'px' :
      mouseXY[0] - this.cfg.margin - el.offsetWidth + 'px';
    el.style.top = (windowWH[1]/2 > mouseXY[1] - scrollXY[1])?
      mouseXY[1] + this.cfg.margin + 'px' :
      mouseXY[1] - this.cfg.margin - el.offsetHeight + 'px';
  },
  /**
   * タイトルの設定
   * @param {String} title DTのinnerHTML
   * @param {String} desc DDのinnerHTML
   */
  set:function(title, desc){
    var el=document.getElementById(this.cfg.id);
    var dt = el.getElementsByTagName('DT');
    if(dt)dt[0].innerHTML = title || '&nbsp;';
    var dd = el.getElementsByTagName('DD');
    if(dd)dd[0].innerHTML = desc || '&nbsp;';
  },
  /**
   * 表示
   * @param {MouseEvent} [e] マウスイベント
   */
  show:function(e){
    document.getElementById(this.cfg.id).style.display='block';
    this.position(e);
  },
  /**
   * 隠す
   */
  hide:function(){
    this.lock=false;
    document.getElementById(this.cfg.id).style.display='none';
  }
/*
  delayhide:function(delay){
    var scope = this;
    setTimeout( function(){
      if(scope.lock)return;
      scope.hide();
    }, delay || 1000);
  }
*/
};

/**
 * iframeタグによるポップアップ
 * @class OCAL.Iframe
 * @param {Object} json 設定
 */
OCAL.Iframe=function(json){
  this.cfg={};
  /**
   * ポップアップように{用意してある,作成する}エレメントのID
   * @type {String}
   */
  this.cfg.id='ocal_iframe';
  /**
   * ポップアップの横幅
   * @type {Number}
   */
  this.cfg.width=500;
  /**
   * ポップアップの高さ
   * @type {Number}
   */
  this.cfg.height=300;
  if(json) for(var key in json) this.cfg[key] = json[key];

  var el=document.getElementById(this.cfg.id);
  if(!el){
    el=document.createElement('DIV');
    el.id=this.cfg.id;
    el.style.display='none';
    el.width=this.cfg.width;
    el.height=this.cfg.height;

    var html='<a href="javascript:void(0)" onclick="'+
      'document.getElementById(\''+this.cfg.id+'\').style.display=\'none\'"'+
      '>閉じる[×]</a>'+
      '<iframe border="0" frameborder="0">iframeタグを使用しています</iframe>';
    el.innerHTML=html;
    document.body.appendChild(el);
  }
};

OCAL.Iframe.prototype={
  /**
   * ポップアップする場所を設定
   */
  position:function(){
    var el=document.getElementById(this.cfg.id);
    var xy = OCAL.getScrollXY();
    var wh = OCAL.getWindowWH();
    var x = (wh[0]/2) - (el.width/2) + xy[0];
    var y = (wh[1]/2) - (el.height/2) + xy[1];
    el.style.top = ((y<0)? 0 : y)+'px';
    el.style.left= ((x<0)? 0 : x)+'px';
    el.style.position='absolute';
    el.style.width=this.cfg.width+'px';
    el.style.height=this.cfg.height+'px';
  },
  /**
   * iframeのsrcを設定
   * @param {String} uri iframeのsrc
   */
  set:function(uri){
    var el=document.getElementById(this.cfg.id);
    var iframe=el.getElementsByTagName('IFRAME')[0];
    iframe.src=uri;
  },
  /**
   * 表示
   */
  show:function(){
    document.getElementById(this.cfg.id).style.display='block';
    this.position();
  },
  /**
   * 隠す
   */
  hide:function(){
    document.getElementById(this.cfg.id).style.display='none';
  }
};

/**
 * クラス継承する
 * @static
 * @param {Class} subc サブクラス
 * @param {Class} superc スーパークラス
 * @param {Object} overrides subc.prototypeを上書きするもの
 */
OCAL.extend=function(subc, superc, overrides) {
  if (!superc||!subc) {
    throw new Error("引数が不正です");
  }
  var F=function(){};
  F.prototype=superc.prototype;
  subc.prototype=new F();
  subc.prototype.constructor=subc;
  subc.superclass=superc.prototype;
  if (superc.prototype.constructor == Object.prototype.constructor) {
    superc.prototype.constructor=superc;
  }

  if (overrides) {
    for (var i in overrides) {
      subc.prototype[i]=overrides[i];
    }
  }
};

/**
 * Ajaxする
 * @static
 * @param {String} uri リクエストするURI
 * @param {Function} fnSuccess リクエスト成功時のコールバック関数
 * @param {Function} fnFailure リクエスト失敗時のコールバック関数
 * @param {Function} fnTimeout リクエストタイムアウト時のコールバック関数
 * @param {Boolean} async 非同期通信を行う:<code>true</code>
 */
OCAL.ajax=function(uri, fnSuccess, fnFailure, fnTimeout, async){
  var xhr;
  try{
    xhr=new XMLHttpRequest();
  }catch(e){
    try{
      xhr=new ActiveXObject("Msxml2.XMLHTTP");
    }catch(e){
      try{
        xhr=new ActiveXObject("Microsoft.XMLHTTP");
      }catch(e){
      }
    }
  }
  if(!xhr){fnFailure();return;}

  var tid = window.setTimeout(function(){xhr.abort();fnTimeout();}, 5000);

  xhr.open('GET', uri, async);

  xhr.onreadystatechange=function(){
    if(xhr && xhr.readyState==4){
      var stat = xhr.status;
      if(stat){
        if(200<=stat && stat<300)fnSuccess(xhr.responseText);
        else fnFailure();
      }
      clearTimeout(tid);
    }
  };
  xhr.send(null);
  if(!async){
    fnSuccess(xhr.responseText);
    clearTimeout(tid);
  }
};

/**
 * Adds a DOM event directly without the caching, cleanup, scope adj, etc
 *
 * @param {HTMLElement} el      the element to bind the handler to
 * @param {String}      type    the type of event handler
 * @param {Function}    fn      the callback to invoke
 * @param {Boolen}      capture capture or bubble phase
 * @static
 */
OCAL.addEvent=function(el, type, fn, capture){
  if(el){
    if(type.toLowerCase() == 'mousewheel'){
      if(navigator.userAgent.match('Gecko'))
        type='DOMMouseScroll';
    }
    if(el.addEventListener){
      el.addEventListener(type, fn, capture);
    }else if(window.attachEvent){
      el.attachEvent(("on" + type), fn);
    }
  }
};

/**
 * Finds the event in the window object, the caller's arguments, or
 * in the arguments of another method in the callstack.  This is
 * executed automatically for events registered through the event
 * manager, so the implementer should not normally need to execute
 * this function at all.
 * @param {Event} e the event parameter from the handler
 * @return {Event} the event
 * @static
 */
OCAL.getEvent=function(e){
  var ev = e || window.event;
  if(!ev){
    var c = OCAL.getEvent.caller;
    while(c){
      ev = c.arguments[0];
      if (ev && Event == ev.constructor)
        break;
      c = c.caller;
    }
  }
  return ev;
};

/**
 * Returns the event's target element
 * @param {Event} ev the event
 * @return {HTMLElement} the event's target
 * @static
 */
OCAL.getTarget=function(e){
  var el = e.target || e.srcElement;
  return (el && 3 == el.nodeType)? el.parentNode: el;
};

/**
 * ページ全体の width, height を返す
 * @return {Array} [width, height]
 * @static
 */
OCAL.getBodyWH=function(){
  return [
    document.documentElement.scrollWidth||
    document.body.scrollWidth||
    0,
    document.documentElement.scrollHeight||
    document.body.scrollHeight||
    0
  ];
};

/**
 * ウィンドウの width, height を返す
 * @return {Array} [width, height]
 * @static
 */
OCAL.getWindowWH=function(){
  return [
    window.innerWidth||
    document.documentElement.clientWidth||
    document.body.clientWidth||
    0,
    window.innerHeight||
    document.documentElement.clientHeight||
    document.body.clientHeight||
    0
  ];
};

/**
 * @param {HTMLElement} [doc] スクロールしている距離を返す
 * @return {Array} [w, y]
 * @static
 */
OCAL.getScrollXY=function(doc){
  doc = doc || document;
  return [
    doc.documentElement.scrollLeft||
    doc.body.scrollLeft||
    0,
    doc.documentElement.scrollTop||
    doc.body.scrollTop||
    0
  ];
};

/**
 * マウスの位置を返す
 * @param {Event} e イベント
 * @return {Array} [w, y]
 * @static
 */
OCAL.getMouseXY=function(e){
  if(document.all){
    var scroll = OCAL.getScrollXY();
    return [event.clientX + scroll[0], event.clientY + scroll[1]];
  }else{
    return [e.pageX, e.pageY];
  }
};

/**
 * エレメントの位置を返す
 * @param {HTMLElement} el エレメント
 * @return {Array} [x, y]
 * @static
 */
OCAL.getXY=function(el){
  if(document.documentElement.getBoundingClientRect){
    var box = el.getBoundingClientRect();
    var pos = OCAL.getScrollXY(el.ownerDocument);
    pos[0] += box.left-2; // IEはずれる？
    pos[1] += box.top-2; // IEはずれる？
    return pos;
  }else{
    var pos = [el.offsetLeft, el.offsetTop];
    var parentNode = el.offsetParent;
    var isSafari = navigator.userAgent.indexOf('Safari') != -1;
    var accountForBody = (isSafari && el.style.position == 'absolute' &&
                          el.offsetParent == el.ownerDocument.body);

    if(parentNode != el){
      while(parentNode){
        pos[0] += parentNode.offsetLeft;
        pos[1] += parentNode.offsetTop;
        if(!accountForBody && isSafari && el.style.position == 'absolute')
          accountForBody = true;
        parentNode = parentNode.offsetParent;
      }
    }

    if(accountForBody){
      pos[0] -= el.ownerDocument.body.offsetLeft;
      pos[1] -= el.ownerDocument.body.offsetTop;
    }
    parentNode = el.parentNode;

    while(parentNode.tagName && parentNode.tagName.toLowerCase() != 'body'
                             && parentNode.tagName.toLowerCase() != 'html'){
      if(parentNode.style.display == 'inline'){
        pos[0] -= parentNode.scrollLeft;
        pos[1] -= parentNode.scrollTop;
      }
      parentNode = parentNode.parentNode;
    }

    return pos;
  }
};
/**
 * エレメントの位置を返す
 * @param {HTMLElement} el エレメント
 * @return {Object} top, right, bottom, left をキーに持つ
 * @static
 */
OCAL.getRegion=function(el){
  var xy = OCAL.getXY(el);
  return {'top': xy[1], 'right': xy[0] + el.offsetWidth, 'bottom': xy[1] + el.offsetHeight, 'left': xy[0]};
};
/**
 * @param {HTMLElement} el エレメント
 * @param {MouseEvent} e マウスイベント
 * @return {Boolean} エレメント内にマウスがあるか？
 */
OCAL.inside=function(el,e){
  var xy = OCAL.getMouseXY(e);
  var region = OCAL.getRegion(el);
  return (region.top < xy[1] && region.bottom > xy[1] &&
          region.left < xy[0] && region.right > xy[0])
};
/**
 * 基底クラス
 * @class OCAL.Base
 * @param {HTMLElement} el カレンダーを描くエレメント
 * @param {Object} json 設定
 */
OCAL.Base=function(el, json){
  this.vcalendar;
  this.el=el;

  this.cfg={};
  /**
   * 選択状態にする日にち(デフォルト本日)
   * @type {Date}
   */
  this.cfg.TODAY=Date.paddingZeroTime();
  /**
   * カレンダーのはじめのDateを保持
   * TODAYの月初の週初めを設定する
   * @private
   */
  this.cfg.D=new Date(this.cfg.TODAY);
  //this.cfg.D.setDate(1);
  this.cfg.D.weekStart();

  this.cfg.MODE='TABLE';

  /**
   * 表示する週数
   * @type {Number}
   */
  this.cfg.WEEK=5;
  /**
   * 曜日名の配列(デフォルト["日", "月", "火", "水", "木", "金", "土"])
   * @type {Array} 
   */
  this.cfg.WDAY=["日", "月", "火", "水", "木", "金", "土"];
  /**
   * 先月リンクのinnerHTML
   * @type {String}
   */
  this.cfg.LASTMONTH='&laquo;先月';
  /**
   * 来月リンクのinnerHTML
   * @type {String}
   */
  this.cfg.NEXTMONTH='来月&raquo;';
  /**
   * 先週リンクのinnerHTML
   * @type {String}
   */
  this.cfg.LASTWEEK='&lt;先週';
  /**
   * 来週リンクのinnerHTML
   * @type {String}
   */
  this.cfg.NEXTWEEK='来週&gt;';
  if(json) for(var key in json)this.cfg[key]=json[key];
};

OCAL.Base.prototype={
  /**
   * 引数のfunctionのスコープを自分にしてOCAL.ajaxを呼ぶ
   */
  ajax:function(uri, fnSuccess, fnFailure, fnTimeout, sync){
    var scope=this;
    var S=function(res){
      return fnSuccess.call(scope, res);
    };
    var F=function(){
      return fnFailure.call(scope);
    };
    var T=function(){
      return fnTimeout.call(scope);
    };
    OCAL.ajax(uri, S, F, T, sync||false);
  },
  /**
   * 引数のfunctionのスコープを自分にしてOCAL.addEventを呼ぶ
   */
  addEvent:function(obj, type, fn, capture){
    var scope=this;
    var F=function(e){
      return fn.call(scope, OCAL.getEvent(e));
    };
    OCAL.addEvent(obj, type, F, capture)
  },
  /**
   * 表示しているカレンダーに関するlinkを返す
   * @return {Object}
   * <pre>{
   *   'calendar':  カレンダーURL,
   *   'icalendar': iCalendar URL,
   *   'atom':      feed URL,
   *   'qrcode':    QRコード URL
   * }</pre>
   */
  link:function(){
    var calid = this.vcalendar.x_ocal_vcalendar_id;
    var userid = this.vcalendar.x_ocal_user_id;

    var o={};
    if(calid && userid){
      o.calendar=OCAL.url + '/cal/index/' + userid + '/' + calid;
      if(navigator.userAgent.match('Mac'))
        o.icalendar=OCAL.url.replace('http', 'webcal')  + '/ws/get/ical/v1/' + userid + '-' + calid + '.ics';
      else
        o.icalendar=OCAL.url + '/ws/get/ical/v1/' + userid + '-' + calid + '.ics';
      o.atom=OCAL.url + '/ws/get/atom/v1/' + userid + '-' + calid;
      o.qrcode=OCAL.url + '/vcalendars/qrcode/' + userid + '/' + calid;
    }
    return o;
  },
  /**
   * Veventに設定されたURL。それがなければ引数の予定を表示するocal.jp上のURL。
   * それも作成できなければ<code>NULL</code>
   * @param {Vevent} vevent 予定
   * @return {String} URL
   */
  evtlink:function(vevent){
    if(vevent.url && vevent.url.match(/^https?:\/\//))return vevent.url;

    var calid = this.vcalendar.x_ocal_vcalendar_id;
    var userid = this.vcalendar.x_ocal_user_id;
    var evtid = vevent.x_ocal_vevent_id;

    if(calid && userid && evtid)
      return OCAL.url + '/cal/evt/' + userid + '/' + calid + '/' + evtid;
  },
  /**
   * @param {Event} e クリックイベント
   * @return {Date} クリックした日付/取れない場合は<code>NULL</code>
   */
  getTargetDate:function(e){
    var el = OCAL.getTarget(e);
    if(el.id){
      var a = el.id.split(/_/);
      if(a[a.length-1].match(/(\d+)$/)){
        var d = Date.paddingZeroTime(this.cfg.D);
        d.add( parseInt(RegExp.$1, 10) );
        return d;
      }
    }
    return null;
  },
  /**
   * iCalendar.callback (JSONP)が使われた場合はそっちを、
   * そうでなければcfg.ICSからカレンダーオブジェクトを取得し、
   * vcalendar プロパティーに設定する
   * @param {Boolean} reload 強制再読み込み
   * @param {Function} fn AJAXの成功時に実行する関数
   */
  getCalendar:function(reload, fn){
    if(this.vcalendar && !reload)return;
    if(this.cfg.ICS || this.cfg.JSONP){
      var message=function(s){
        var div = document.createElement('DIV');
        div.style.position='absolute';
        div.style.top='50%';
        div.style.textAlign='center';
        div.style.width='100%';
        div.style.fontWeight='bold';
        div.style.fontStyle='italic';
        div.style.zIndex=9999;
        div.innerHTML=s;
        this.el.appendChild(div);
      };
      this.ajax(this.cfg.ICS || this.cfg.JSONP,
        function(res){
          if(this.cfg.ICS){
            this.vcalendar = iCalendar.parse(res);
          }else if(this.cfg.JSONP){
            eval(res);
            this.vcalendar = window[iCalendar.GLOBAL];
          }
          if(fn)fn.call(this);
        },
        function(){
          message.call(this,'ファイルが見つかりません');
        },
        function(){
          message.call(this,'タイムアウトしました<br/>現在はカレンダーを表示できません');
        },
        false
      );
    }else if(iCalendar && window[iCalendar.GLOBAL]){
      this.vcalendar = window[iCalendar.GLOBAL];
    }else{
      alert('カレンダーを指定してください');
    }
  },
  /**
   * マスの高さを設定する
   */
  setCalHeight:function(e){
    var calregion={};
    if(this.el.style.height)
      calregion = OCAL.getRegion(this.el);
    else{
      var wh = OCAL.getWindowWH();
      calregion.bottom=wh[1];
    }
    if(this.cfg.MODE == 'TABLE'){
      var tblregion = OCAL.getRegion(this.$('w'));
      var h = calregion.bottom - tblregion.bottom;
      if(navigator.userAgent.match('Gecko'))h-=this.cfg.WEEK;
      h/=this.cfg.WEEK;
      for(var i=0; i<this.cfg.WEEK; i++)
        this.$('week'+i).style.height = h + 'px';
    }else{
      var lstregion = OCAL.getRegion(this.$('list'));
      var h = calregion.bottom - lstregion.top;
      this.$('list').style.height = h + 'px';
    }
  },
  /**
   * cfg.D と cfg.WEEK から今月を返す
   * @return {Array} [thisyear, thismonth]
   */
  getThisYM:function(){
    var thisyear = this.cfg.D.getFullYear(), thismonth = this.cfg.D.getMonth();
    if(this.cfg.D.eom() - this.cfg.D.getDate() + 1 < this.cfg.WEEK*7/2){
      thismonth++;
      if(thismonth==12){
        thismonth=0;
        thisyear++;
      }
    }
    return [thisyear, thismonth];
  },
  /**
   * カレンダー名にmouseoverした時の処理
   * @private
   */
  _cal:function(e){
    var calname = this.vcalendar.x_wr_calname;
    var caldesc = this.vcalendar.x_wr_caldesc;
    if(calname && caldesc && calname!='' && caldesc!=''){
      calname += '(' + this.vcalendar.vevents.length + ')';
      this.tooltip.set(calname, caldesc);
      this.tooltip.show();
    }
  },
  /**
   * 予定にmouseoverした時の処理
   * @private
   */
  _mouseover:function(e){
    var el = OCAL.getTarget(e);
    var uid = el.getAttribute('uid');
    var vevent = this.vcalendar.getVevent(uid);
    if(!vevent)return;
    var title=vevent.summary.E();
    var desc='';
    if(vevent.description)desc+='<p>'+vevent.description+'</p>';
    desc+='<table><tbody><tr><th nowrap>場所</th><td>';
    if(vevent.location)desc+=vevent.location;
    desc+='</td></tr><tr><th nowrap>時間</th><td>';
    if(!vevent.rrule.freq){
      desc+=vevent.dtstart.toYYYYMMDD('/') + '&nbsp;';
    }
    if(vevent.isAllday()){
      var howlong = vevent.howlong();
      desc+=(howlong==0)? '終日':(howlong+1)+'日間';
    }else{
      desc+=vevent.dtstart.toHHMM()+'～'+vevent.dtend.toHHMM();
    }
    desc+='</td></tr>';
    if(vevent.rrule.freq){
      desc+='<tr><th nowrap>繰り返し</th><td>';
      if(vevent.rrule.freq == 'DAILY'){
        desc+='毎日';
      }else if(vevent.rrule.freq == 'WEEKLY'){
        desc+='毎週';
        if(vevent.rrule.byday){
          desc+=vevent.byday_to_s();
        }
      }else if(vevent.rrule.freq == 'MONTHLY'){
        desc+='毎月';
        if(vevent.rrule.byday){
          desc+=vevent.byday_to_s();
        }else if(vevent.rrule.bymonthday){
          for(var i=0, z=vevent.rrule.bymonthday.length; i<z; i++){
            desc+=(vevent.rrule.bymonthday[i]=='-1')?
              '月末':vevent.rrule.bymonthday[i];
            if(i+1 != z) desc+=',';
          }
        }else{
          desc+=vevent.dtstart.getDate()+'日';
        }
      }else if(vevent.rrule.freq == 'YEARLY'){
        desc+='毎年';
        desc+=(vevent.dtstart.getMonth()+1)+'月';
        if(vevent.rrule.byday){
          desc+=vevent.byday_to_s();
        }else if(vevent.rrule.bymonthday){
          for(var i=0, z=vevent.rrule.bymonthday.length; i<z; i++){
            desc+=(vevent.rrule.bymonthday[i]=='-1')?
              '月末':vevent.rrule.bymonthday[i];
            if(i+1 != z) desc+=',';
          }
        }else{
          desc+=vevent.dtstart.getDate()+'日';
        }
      }else{
        desc+=vevent.dtstart.toMMDD();
      }
      if(vevent.rrule.until){
        desc+='，期限:'+vevent.rrule.until.toYYYYMMDD();
      }
      if(vevent.rrule.count){
        desc+='，回数:'+vevent.rrule.count;
      }
      if(vevent.rrule.interval && vevent.rrule.interval != '1'){
        desc+='，間隔:'+vevent.rrule.interval;
      }
      desc+='</td></tr>';
    }
    desc+='</tbody></table>';

    this.tooltip.set(title, desc);
    this.tooltip.show(e);
  },
  /**
   * 予定でmousemoveした時の処理
   * @private
   */
  _mousemove:function(e){
    this.tooltip.position(e);
  },
  /**
   * 予定でmouseoutした時の処理
   * @private
   */
  _mouseout:function(e){
    this.tooltip.hide();
  },
  /**
   * 予定をclickした時の処理
   * @private
   */
  _click:function(e){
    this.tooltip.hide();
    var el=OCAL.getTarget(e);
    var uid = el.getAttribute('uid');
    if(uid){
      var vevent = this.vcalendar.getVevent(uid);
      if(!vevent.x_ocal_vevent_id)return;
      this.iframe.set(OCAL.url + '/vevents/ajaxform?vevent_id='+
        vevent.x_ocal_vevent_id+'&r='+encodeURI(location));
    }else{
      if(!this.vcalendar.x_ocal_vcalendar_id)return;
      this.iframe.set(OCAL.url + '/vevents/ajaxform?vcalendar_id='+
        this.vcalendar.x_ocal_vcalendar_id+'&r='+encodeURI(location));
    }
    this.iframe.show();
  }
};

/**
 * カレンダーを描く
 * @class OCAL.Cal
 * @extends OCAL.Base
 * @param {HTMLElement} el カレンダーを描くエレメント
 * @param {Object} json 設定
 */
OCAL.Cal=function(el, json){
  OCAL.Cal.superclass.constructor.call(this, el, json);

  this.iframe=new OCAL.Iframe(this.cfg.iframe);
  this.tooltip=new OCAL.ToolTip(this.cfg.tooltip);

  this.cc();
  this.getCalendar();
  this.makeCanvas();
  this.drawDate();
  this.drawCalname();
  this.setCalHeight();
  this.drawEvent();
  this.setupEvent();
};


OCAL.extend(OCAL.Cal, OCAL.Base, /** @scope OCAL.Cal.prototype */{
  /**
   * 引数に'ocal_' をつけたID のエレメントを返す
   * @param {String} s エレメントID
   * @return {HTMLElement} エレメント
   */
  $:function(s){
    return document.getElementById('ocal_'+s);
  },
  /**
   * カレンダー自体を描く。特別なことがない限り一度だけ実行すればよい。
   */
  makeCanvas:function(){
    var links=function(){
      return '<div id="ocal_links" class="ocal_links">&nbsp;</div>';
    };

    var navi=function(){
      return '<ul class="ocal_navi">'+
        '<li><a href="javascript:void(0)" id="ocal_switch">'+
          '予定リスト</a></li>'+
        '<li><a href="javascript:void(0)" id="ocal_lastmonth">'+
          this.cfg.LASTMONTH+'</a></li>'+
        '<li><a href="javascript:void(0)" id="ocal_lastweek">'+
          this.cfg.LASTWEEK+'</a></li>'+
        '<li><a href="javascript:void(0)" id="ocal_ym"></a></li>'+
        '<li><a href="javascript:void(0)" id="ocal_nextweek">'+
          this.cfg.NEXTWEEK+'</a></li>'+
        '<li><a href="javascript:void(0)" id="ocal_nextmonth">'+
          this.cfg.NEXTMONTH+'</a></li>'+
        '</ul>';
    };

    var table=function(){
      var html='<table id="ocal_table" class="ocal_table" width="100%" '+
        'cellpadding="0" cellspacing="0">'+
        '<thead><tr><th valign="bottom" width="2%" id="ocal_w">&nbsp;</th>';
        for(var i=0; i<7; i++)
          html+='<th width="14%" class="ocal_w'+i+'" valign="bottom">'+
            this.cfg.WDAY[i]+'</th>';
        html+='</tr></thead><tbody>';
        for(var i=0; i<this.cfg.WEEK; i++){
          html+='<tr>';
          html+='<th id="ocal_week'+i+'" class="ocal_week" width="2%"></th>';
          for(var j=0; j<7; j++)
            html+='<td uid="" width="14%" valign="top" id="ocal_'+(i*7+j)+'">'+
              '<div id="ocal_day'+(i*7+j)+'" class="ocal_w'+j+'"></div></td>';
          html+='</tr>';
        }
      html+='</tbody></table>';
      return html;
    };

    var list=function(){
      return '<div id="ocal_list" class="ocal_list" style="display:none;">'+
        '<ul></ul></div>';
    };

    var poweredby=function(){
      return '<div class="ocal_poweredby">'+
        'Powered by <a href="'+OCAL.url+'" target="ocaljp">OCAL.JP</a> '+
        '<a href="'+OCAL.url+'/docs/cal/operation.html" target="ocaljp">'+
        '操作方法</a></div>';
    };

    var html='<div id="ocal_cal_block">';
    html+=navi.call(this);
    html+=links.call(this);
    html+=table.call(this);
    html+=list.call(this);
    html+=poweredby.call(this);
    html+='</div>';

    this.el.innerHTML = html;

    this.addEvent(this.$('links'), 'mouseover', this._cal);
    this.addEvent(this.$('links'), 'mousemove', this._mousemove);
    this.addEvent(this.$('links'), 'mouseout', this._mouseout);
    this.addEvent(this.$('switch'), 'click', function(e){
      if(this.cfg.MODE == 'TABLE'){
        this.$('switch').innerHTML='予定表';
        this.cfg.MODE = 'LIST';
        this.$('cal_rootevt').style.display='none';
        this.$('table').style.display='none';
        this.$('list').style.display='block';
      }else{
        this.$('switch').innerHTML='予定リスト';
        this.cfg.MODE = 'TABLE';
        this.$('cal_rootevt').style.display='block';
        this.$('list').style.display='none';
        try{
          this.$('table').style.display='table';
        }catch(e){
          this.$('table').style.display='block';//IE6
        }
      }
      this.setCalHeight();
      this.drawEvent();
    });
    this.addEvent(this.$('lastmonth'), 'click', function(e){
      this.lastmonth();
      this.drawEvent();
    });
    this.addEvent(this.$('lastweek'), 'click', function(e){
      this.lastweek();
      this.drawEvent();
    });
    this.addEvent(this.$('ym'), 'click', function(e){
      this.today();
      this.drawEvent();
    });
    this.addEvent(this.$('nextweek'), 'click', function(e){
      this.nextweek();
      this.drawEvent();
    });
    this.addEvent(this.$('nextmonth'), 'click', function(e){
      this.nextmonth();
      this.drawEvent();
    });

    if(!this.$('cal_rootevt')){
      var div = document.createElement('DIV');
      div.id='ocal_cal_rootevt';
      document.body.appendChild(div);
    }
  },
  /**
   * OCAL.Cal.makeCanvasで描かれたカレンダーに日付やclassNameを設定する
   */
  drawDate:function(){
    var a=this.getThisYM();

    this.$('ym').innerHTML=a[0]+'/'+(a[1]+1);

    var today = Date.sub(this.cfg.D, this.cfg.TODAY, 1);
    var bgOnly = (this.$('day0').innerHTML == this.cfg.D.toYYYYMMDD('/'));
    for(var d=new Date(this.cfg.D), i=0, z=this.cfg.WEEK*7; i<z; i++, d.add(1)){
      if(!bgOnly){
        if(i%7==0) this.$('day'+i).innerHTML = d.toYYYYMMDD('/');
        else this.$('day'+i).innerHTML = d.toMMDD('/');
      }
      var el = this.$(i), m=d.getMonth();
      if(i == today)
        el.className = 'ocal_today';
      else if(a[1] == m && el.className != 'ocal_thismonth')
        el.className = 'ocal_thismonth';
      else if(a[1] != m && el.className != 'ocal_notthismonth')
        el.className = 'ocal_notthismonth';
    }
    if(!bgOnly)
      for(var d=new Date(this.cfg.D), i=0; i<this.cfg.WEEK; i++, d.add(7))
        this.$('week'+i).innerHTML = d.weekNumber() + '<br/>週';
  },
  /**
   * カレンダーのリンクを設定する
   */
  drawCalname:function(){
    var html='';
    var links = this.link();
    var calname = this.vcalendar.x_wr_calname;
    if(calname)
      calname += '(' + this.vcalendar.vevents.length + ')';
    else
      calname = '&nbsp;';
    if(links.calendar)
      html+='<a class="ocal_calendar" href="'+links.calendar+'" title="'+links.calendar+'">'+calname.E()+'</a>';
    else
      html+='<a class="ocal_calendar" href="javascript:void(0)">'+calname.E()+'</a>';
    if(links.icalendar)
      html+='<a class="ocal_icalendar" title="iCalendar" href="'+links.icalendar+'">&nbsp;</a>';
    if(links.atom)
      html+='<a class="ocal_atom" title="Atom Feed" href="'+links.atom+'">&nbsp;</a>';
    if(links.qrcode)
      html+='<a class="ocal_qrcode" title="QRコード" href="'+links.qrcode+'">&nbsp;</a>';
    this.$('links').innerHTML=html;
  },
  /**
   * (一度表示した予定の)キャッシュをクリアする
   */
  cc:function(){
    this.cache=new Object();
  },
  /**
   * カレンダーに各種イベントを登録する
   * <ul>
   * <li>マウスホイールの回転で日付を前後させる</li>
   * <li>上下左右キー押下で日付を前後させる</li>
   * <li>クリックで選択</li>
   * <li>ダブルクリックで予定登録</li>
   * <li>リサイズでカレンダーを再描画</li>
   * </ul>
   */
  setupEvent:function(){
    this.addEvent(this.el, 'mousewheel', function(e){
      if(this.cfg.MODE == 'LIST')return;
      this.tooltip.hide();
      var delta;
      if(e.wheelDelta){
        delta = e.wheelDelta/120;
        if(window.opera)
          delta = -delta;
      }else if(e.detail){
        delta = -e.detail/3;
      }
      (delta>0)?this.lastweek():this.nextweek();
      this.drawEvent();
    }, false);

    this.addEvent(document, 'keydown', function(e){
      var c = (e.charCode || e.keyCode || 0);
      if(37 <= c && c <= 40){
        switch(c){
        case 37://left
          this.yesterday();
          break;
        case 38://up
          if(e.shiftKey) this.lastweek();
          else if(e.ctrlKey) this.lastmonth();
          else this.lastweek(true);
          break;
        case 39://right
          this.tomorrow();
          break;
        case 40://down
          if(e.shiftKey) this.nextweek();
          else if(e.ctrlKey) this.nextmonth();
          else this.nextweek(true);
          break;
        }
        this.drawEvent();
/*
      }else if(c==13){//enter
        this._click(e);
*/
      }else if(c==27){//esc
        this.iframe.hide();
        this.tooltip.hide();
      }
    }, false);

    for(var i=0, z=this.cfg.WEEK*7; i<z; i++){
      this.addEvent(this.$(i), 'click', function(e){
        var d = this.getTargetDate(e);
        if(d){
          this.cfg.TODAY = d;
          this.drawDate();
        }
      }, false);

      if(!this.vcalendar.x_ocal_vcalendar_id)continue;

      this.addEvent(this.$(i), 'dblclick', function(e){
        var d = this.getTargetDate(e);
        if(d){
          this.iframe.set(OCAL.url + '/vevents/ajaxform?d='+d.toYYYYMMDD('-') +
            '&r='+encodeURI(location)+
            '&vcalendar_id='+this.vcalendar.x_ocal_vcalendar_id);
          this.iframe.show();
        }
      }, false);
    }

    this.addEvent(window, 'resize', function(e){
      this.cc();
      if(!this.el.style.height)this.setCalHeight();
      this.drawEvent();
    }, false);
  },
  /**
   * 昨日にする
   */
  yesterday:function(){
    this.cfg.TODAY.add(-1);
    if(Date.sub(this.cfg.TODAY, this.cfg.D, 1) > 0)
      this.cfg.D.add(-7);
    this.drawDate();
  },
  /**
   * 明日にする
   */
  tomorrow:function(){
    this.cfg.TODAY.add(1);
    if(Date.sub(this.cfg.D, this.cfg.TODAY, 2) >= this.cfg.WEEK)
      this.cfg.D.add(7);
    this.drawDate();
  },
  /**
   * 来週にする
   * @param {Boolean} fixedcanvas キャンバスを固定して選択日だけを来週にする
   */
  nextweek:function(fixedcanvas){
    this.cfg.TODAY.add(7);
    if(!fixedcanvas || Date.sub(this.cfg.D, this.cfg.TODAY, 2) >= this.cfg.WEEK)
      this.cfg.D.add(7);
    this.drawDate();
  },
  /**
   * 先週にする
   * @param {Boolean} fixedcanvas キャンバスを固定して選択日だけを先週にする
   */
  lastweek:function(fixedcanvas){
    this.cfg.TODAY.add(-7);
    if(!fixedcanvas || Date.sub(this.cfg.TODAY, this.cfg.D, 2) >= 1)
      this.cfg.D.add(-7);
    this.drawDate();
  },
  /**
   * 来月にする
   */
  nextmonth:function(){
    this.cfg.TODAY.nextMonth();
    this.cfg.D.setDate(1);
    this.cfg.D.setMonth(this.cfg.TODAY.getMonth());
    this.cfg.D.setFullYear(this.cfg.TODAY.getFullYear());
    this.cfg.D.weekStart();
    this.drawDate();
  },
  /**
   * 先月にする
   */
  lastmonth:function(){
    this.cfg.TODAY.lastMonth();
    this.cfg.D.setDate(1);
    this.cfg.D.setMonth(this.cfg.TODAY.getMonth());
    this.cfg.D.setFullYear(this.cfg.TODAY.getFullYear());
    this.cfg.D.weekStart();
    this.drawDate();
  },
  /**
   * this.cfg.TODAY を元に戻す
   */
  today:function(){
    this.cfg.TODAY=Date.paddingZeroTime();
    this.cfg.D=new Date(this.cfg.TODAY);
    this.cfg.D.setDate(1);
    this.cfg.D.weekStart();
    this.drawDate();
  },
  /**
   * 予定を配置する
   */
  drawEvent:function(){
    (this.cfg.MODE == 'LIST')? this.drawEventList() : this.drawEventTable();
  },
  drawEventList:function(){
    var buf=[];
    for(var i=0, z=this.cfg.WEEK*7; i<z; i++)buf[i]=new Array();

    var ul = this.$('list').getElementsByTagName('UL');
    if(ul)this.$('list').removeChild(ul[0]);

    var end = new Date(this.cfg.D);
    end.add(this.cfg.WEEK*7);
    var evt = this.vcalendar.find(this.cfg.D, end);
    for(var i=0, z=evt.length; i<z; i++){
      var vevent = evt[i].vevent;
      var s=Date.sub(this.cfg.D, evt[i].sdate, 1);
      var _s=(s<0)? 0: s;
      if(vevent.isAllday()){
        var e=s+vevent.howlong();
        var _e=(this.cfg.WEEK*7<e)? this.cfg.WEEK*7: e;
        for(var j=_s; j<=_e; j++)
          buf[j].push(vevent);
      }else{
        buf[_s].push(vevent);
      }
    }

    var ul = document.createElement('UL');
    var li = document.createElement('LI');
    li.className='first';
    li.innerHTML=this.cfg.D.toYYYYMMDD()+'('+this.cfg.D.getLocalDay()+') ～';
    ul.appendChild(li);

    var finded=false;
    for(var i=0, z=buf.length; i<z; i++){
      for(var j=0, len=buf[i].length; j<len; j++){
        var vevent = buf[i][j];
        var link = this.evtlink(vevent);
        var d=new Date(this.cfg.D);
        d.add(i);
        li = document.createElement('LI');
        var span1 = document.createElement('SPAN');
        span1.className='ocal_w'+d.getDay();
        span1.innerHTML = d.toYYYYMMDD() + '(' + d.getLocalDay() + ')';
        var span2 = document.createElement('SPAN');
        span2.className = vevent.isAllday()? 'ocal_wideevt': 'ocal_evt';
        span2.innerHTML = vevent.isAllday()? '終日': vevent.dtstart.toHHMM();
        var a = document.createElement('A');
        a.setAttribute('uid', vevent.uid.E());
        this.addEvent(a, 'mouseover', this._mouseover, false);
        this.addEvent(a, 'mouseout', this._mouseout, false);
        this.addEvent(a, 'mousemove', this._mousemove, false);
        a.innerHTML=vevent.summary.E();
        a.href=a.title=link;
        li.appendChild(span1); li.appendChild(span2), li.appendChild(a);
        ul.appendChild(li);
        finded=true;
      }
    }
    if(!finded){
      li = document.createElement('LI');
      li.innerHTML='この期間に予定はありません';
      ul.appendChild(li);
    }

    end.add(-1);

    li = document.createElement('LI');
    li.className='last';
    li.innerHTML='～ '+end.toYYYYMMDD()+'('+end.getLocalDay()+')';
    ul.appendChild(li);

    this.$('list').appendChild(ul);
  },
  drawEventTable:function(){
    document.body.removeChild(this.$('cal_rootevt'));//前の予定を消す
    if(this.cache[this.cfg.D.toYYYYMMDD()]){
      document.body.appendChild(this.cache[this.cfg.D.toYYYYMMDD()]);
      return;
    }

    var rootevt = document.createElement('DIV');
    rootevt.id = 'ocal_cal_rootevt';

    var wideevt=new Array(this.cfg.WEEK);
    for(var i=0; i<this.cfg.WEEK; i++) wideevt[i]=new Array();
    var evtbuf=new Array(this.cfg.WEEK*7);
    var margin=new Array(this.cfg.WEEK*7);

    var end = new Date(this.cfg.D);
    end.add(this.cfg.WEEK*7);
    var evt = this.vcalendar.find(this.cfg.D, end);

    var region = OCAL.getRegion(this.$('day0'));
    var margintop = region.bottom - region.top;
    region = OCAL.getRegion(this.$('0'));
    var height = (region.bottom - region.top - margintop)/5;
    for(var i=0, z=evt.length; i<z; i++){
      var vevent = evt[i].vevent;
      var s=Date.sub(this.cfg.D, evt[i].sdate, 1), e=s + vevent.howlong();
      var _s=(s<0)? 0: s;

      // 終日予定・複数日の予定
      if(vevent.isAllday() || s != e){
        for(var week=Math.floor(_s/7); week<this.cfg.WEEK; week++){
          var _e = (week*7+6 < e) ? week*7+6 : e;
          var sregion = OCAL.getRegion(this.$('day'+ _s));
          var eregion = OCAL.getRegion(this.$('day'+ _e));
          var m=0;
          for(var j=0, y=wideevt[week].length; j<y; j+=2)
            if( (wideevt[week][j] <= _s && _s <= wideevt[week][j+1]) ||
                (wideevt[week][j] <= _e && _e <= wideevt[week][j+1]) )
              m++;

          wideevt[week].push(_s);
          wideevt[week].push(_e);

          var el = document.createElement('A');
          el.setAttribute('uid', vevent.uid.E());
          el.className = 'ocal_wideevt';
          el.style.top = sregion.top + 'px';
          el.style.left = sregion.left + 'px';
          el.style.width = (eregion.right - sregion.left) + 'px';
          el.style.position = 'absolute';
          el.style.overflow = 'hidden';
          el.style.height=el.style.lineHeight=height+'px';
          el.style.fontSize=(height*0.85)+'px';
          el.style.marginTop = m*(height+1) + margintop + 'px';

          for(var j=_s; j<=_e; j++)
            if(margin[j] == null || margin[j]<m)
              margin[j]=m;

          var html = '';
          if(_s != s) html += '&laquo;';
          if(!vevent.isAllday())
            html += '[' + vevent.dtstart.toHHMM() + ']&nbsp;';
          html += vevent.summary.E();
          if(_e != e) html += '&raquo;'
          el.innerHTML = html;

          el.href = this.evtlink(vevent);
          el.title = this.evtlink(vevent);

          this.addEvent(el, 'mouseover', this._mouseover, false);
          this.addEvent(el, 'mouseout', this._mouseout, false);
          this.addEvent(el, 'mousemove', this._mousemove, false);

          rootevt.appendChild(el);

          _s = week*7+7;
          if(_s>e || _s>=this.cfg.WEEK*7) break;
        }
      }else{
        if(!evtbuf[_s])evtbuf[_s]=new Array();
        evtbuf[_s].push(
          {'key': vevent.dtstart.toHHMM(), 'evt': vevent}
        );
      }
    }

    for(var i=0, z=evtbuf.length; i<z; i++){
      if(!evtbuf[i])continue;
      evtbuf[i] = evtbuf[i].sort(function(a, b){
        if(a.key == b.key) return 0;
        return (a.key > b.key)? 1: -1;
      });

      var region = OCAL.getRegion(this.$('day'+i));
      var el = document.createElement('A');
      el.style.top = region.top + 'px';
      el.style.left = region.left + 'px';
      el.style.width = (region.right - region.left) + 'px';
      el.style.position = 'absolute';
      el.style.overflow = 'hidden';
      el.style.height=el.style.lineHeight=height+'px';
      el.style.fontSize = (height*0.85)+'px';
      el.className = 'ocal_evt';

      for(var j=0, y=evtbuf[i].length; j<y; j++){
        var vevent = evtbuf[i][j].evt;
        var a = el.cloneNode(false);
        a.href = this.evtlink(vevent);
        a.title = this.evtlink(vevent);
        a.setAttribute('uid', vevent.uid.E());
        if(margin[i] == null)
          a.style.marginTop = margintop + j*(height+1) + 'px';
        else
          a.style.marginTop = margintop + (margin[i]+j+1)*(height+1) + 'px';
        a.innerHTML = evtbuf[i][j].evt.dtstart.toHHMM()+
          '&nbsp;' + evtbuf[i][j].evt.summary.E();

        this.addEvent(a, 'mouseover', this._mouseover, false);
        this.addEvent(a, 'mouseout', this._mouseout, false);
        this.addEvent(a, 'mousemove', this._mousemove, false);

        rootevt.appendChild(a);
      }
    }

    document.body.appendChild(rootevt);
    if(!this.cache[this.cfg.D.toYYYYMMDD()])
      this.cache[this.cfg.D.toYYYYMMDD()]=rootevt;
  }
});

/**
 * 小さなカレンダーを描く
 * @class OCAL.SCal
 * @extends OCAL.Base
 * @param {HTMLElement} el カレンダーを描くエレメント
 * @param {Object} json 設定
 */
OCAL.SCal=function(el, json){
  OCAL.SCal.superclass.constructor.call(this, el, json);

  this.tooltip=new OCAL.ToolTip(this.cfg.tooltip);

  this.getCalendar();
  this.makeCanvas();
  this.drawDate();
  this.drawCalname();
  this.setCalHeight();
  this.drawEvent();
  this.setupEvent();
}

OCAL.extend(OCAL.SCal, OCAL.Base, /** @scope OCAL.SCal.prototype */{
  /**
   * 引数に'ocal_s_' をつけたID のエレメントを返す
   * @param {String} s エレメントID
   * @return {HTMLElement} エレメント
   */
  $:function(s){
    return document.getElementById('ocal_s_'+s);
  },
  /**
   * カレンダー自体を描く。特別なことがない限り一度だけ実行すればよい。
   */
  makeCanvas:function(){
    var links=function(){
      return '<div id="ocal_s_links" class="ocal_links">&nbsp;</div>';
    };

    var navi=function(){
      return '<ul class="ocal_navi">'+
        //'<li><a href="javascript:void(0)" id="ocal_s_lastmonth">'+
          //this.cfg.LASTMONTH+'</a></li>'+
        '<li><a href="javascript:void(0)" id="ocal_s_lastweek">'+
          this.cfg.LASTWEEK+'</a></li>'+
        '<li><a href="javascript:void(0)" id="ocal_s_ym"></a></li>'+
        '<li><a href="javascript:void(0)" id="ocal_s_nextweek">'+
          this.cfg.NEXTWEEK+'</a></li>'+
        //'<li><a href="javascript:void(0)" id="ocal_s_nextmonth">'+
          //this.cfg.NEXTMONTH+'</a></li>'+
        '</ul>';
    };

    var table=function(){
      var html='<table class="ocal_table" width="100%" '+
        'cellpadding="0" cellspacing="0">'+
        '<thead><tr height="1"><th width="2%" id="ocal_s_w">&nbsp;</th>';
      for(var i=0; i<7; i++)
        html+='<th width="14%" class="ocal_w'+i+'" valign="bottom">'+
          this.cfg.WDAY[i]+'</th>';
      html+='</tr></thead><tbody>';
      for(var i=0; i<this.cfg.WEEK; i++){
        html+='<tr>';
        html+='<th id="ocal_s_week'+i+'" class="ocal_week" width="2%"></th>';
        for(var j=0; j<7; j++)
          html+='<td width="14%" id="ocal_s_'+(i*7+j)+'"></td>';
        html+='</tr>';
      }
      html+='</tbody></table>';
      return html;
    };

    var poweredby=function(){
      return '<div class="ocal_poweredby" id="ocal_s_poweredby" '+
        'style="display:none;"><br/>'+links.call(this)+'<br/>'+
        'Powered by <a href="'+OCAL.url+'" target="ocaljp">OCAL.JP</a></div>';
    };

    var callink=function(){
      return '<div id="ocal_s_callink">&nbsp;</div>';
    };

    var html="<div id='ocal_s_cal_block'>";
    html+='<div id="ocal_s_button"></div>';
    html+=navi.call(this);
    html+=callink.call(this);
    html+=table.call(this);
    html+=poweredby.call(this);
    this.el.innerHTML = html + '</div>';

    var rootevt = document.createElement('DIV');
    rootevt.id='ocal_s_cal_rootevt';
    rootevt.style.position='absolute';
    document.body.appendChild(rootevt);
  },
  /**
   * OCAL.SCal.makeCanvasで描かれたカレンダーに日付やclassNameを設定する
   */
  drawDate:function(){
    var a=this.getThisYM();

    this.$('ym').innerHTML=a[0]+'/'+(a[1]+1);
    var today = Date.sub(this.cfg.D, this.cfg.TODAY, 1);
    for(var d=new Date(this.cfg.D), i=0, z=this.cfg.WEEK*7; i<z; i++, d.add(1)){
      var el = this.$(i), m=d.getMonth();
      el.innerHTML = d.getDate();
      if(i == today)
        el.className = 'ocal_today';
      else if(a[1] == m && el.className != 'ocal_thismonth')
        el.className = 'ocal_thismonth';
      else if(a[1] != m && el.className != 'ocal_notthismonth')
        el.className = 'ocal_notthismonth';
      el.setAttribute('uid', '');
    }
  },
  /**
   * 予定のある日に"ocal_hasevent" class を追加する
   */
  drawEvent:function(){
    var end = new Date(this.cfg.D);
    end.add(this.cfg.WEEK*7);

    var evt = this.vcalendar.find(this.cfg.D, end);
    for(var i=0, z=evt.length; i<z; i++){
      var d=new Date(evt[i].sdate);
      for(var j=0, y=evt[i].vevent.howlong(); j<=y; d.add(1), j++){
        if(d.between(this.cfg.D, end)){
          var sub = Date.sub(this.cfg.D, d, 1);
          var el = this.$(sub);
          el.className += ' ocal_hasevent';
          var attr = el.getAttribute('uid');
          if(attr)attr+="\t";else attr='';
          el.setAttribute('uid', attr + evt[i].vevent.uid);
        }
      }
    }
  },
  /**
   * カレンダーのリンクを設定する
   */
  drawCalname:function(){
    var html='';
    var links = this.link();
    var calname = this.vcalendar.x_wr_calname;
    if(calname)
      calname += '(' + this.vcalendar.vevents.length + ')';
    else
      calname = '&nbsp;';
    if(links.calendar)
      html+='<a class="ocal_calendar" href="'+links.calendar+'" title="'+links.calendar+'">'+calname.E()+'</a>';
    else
      html+='<a class="ocal_calendar" href="javascript:void(0)">'+calname.E()+'</a>';
    if(links.icalendar)
      html+='<a class="ocal_icalendar" title="iCalendar" href="'+links.icalendar+'">&nbsp;</a>';
    if(links.atom)
      html+='<a class="ocal_atom" title="Atom Feed" href="'+links.atom+'">&nbsp;</a>';
    if(links.qrcode)
      html+='<a class="ocal_qrcode" title="QRコード" href="'+links.qrcode+'">&nbsp;</a>';
    this.$('links').innerHTML=html;
    this.$('callink').innerHTML=html;
  },
  /**
   * 先月にする
   */
  lastmonth:function(){
    this.cfg.D.lastMonth();
    this.drawDate();
  },
  /**
   * 先週にする
   */
  lastweek:function(){
    this.cfg.D.add(-7);
    this.drawDate();
  },
  /**
   * 今日にする
   */
  today:function(){
    this.cfg.D.setDate(1);
    this.cfg.D.setMonth(this.cfg.TODAY.getMonth());
    this.cfg.D.setFullYear(this.cfg.TODAY.getFullYear());
    this.cfg.D.weekStart();
    this.drawDate();
  },
  /**
   * 来週にする
   */
  nextweek:function(){
    this.cfg.D.add(7);
    this.drawDate();
  },
  /**
   * 来月にする
   */
  nextmonth:function(){
    this.cfg.D.nextMonth();
    this.drawDate();
  },
  /**
   * カレンダーに各種イベントを登録する
   */
  setupEvent:function(){
    this.addEvent(this.$('button'), 'click', function(e){
      var el = this.$('poweredby');
      if(!el)return;
      if(el.style.display == 'none'){
        el.style.display = 'block';
      }else{
        el.style.display = 'none';
      }
    }, false);
    this.addEvent(this.$('lastweek'), 'click', function(e){
      this.lastweek();
      this.drawEvent();
    }, false);
    this.addEvent(this.$('ym'), 'click', function(e){
      this.today();
      this.drawEvent();
    }, false);
    this.addEvent(this.$('nextweek'), 'click', function(e){
      this.nextweek();
      this.drawEvent();
    }, false);
    this.addEvent(this.$('callink'), 'mouseover', this._cal, false);
    this.addEvent(this.$('callink'), 'mouseout', this._mouseout, false);
    for(var i=0, z=this.cfg.WEEK*7; i<z; i++){
      this.addEvent(this.$(i), 'click', function(e){
        var el=OCAL.getTarget(e);
        var uid=el.getAttribute('uid');
        if(!uid)return;
        var uids=uid.split(/\t/);
        if(uids.length==1){
          var vevent = this.vcalendar.getVevent(uids[0]);
          if(vevent)location.href=this.evtlink(vevent);
        }
      }, false);
      this.addEvent(this.$(i), 'mouseover', function(e){
        var el=OCAL.getTarget(e);
        var dt=this.getTargetDate(e);
        var uid=el.getAttribute('uid');
        if(!uid)return;
        var rootevt = this.$('cal_rootevt')
        while(rootevt.hasChildNodes()){
          rootevt.removeChild(rootevt.firstChild);
        }
        for(var uids=uid.split(/\t/), j=0, y=uids.length; j<y; j++){
          var vevent = this.vcalendar.getVevent(uids[j]);
          var a = document.createElement('A');
          a.href=this.evtlink(vevent);
          a.title=this.evtlink(vevent);
          a.setAttribute('uid', vevent.uid);
          a.innerHTML=vevent.summary.E();
          this.addEvent(a, 'mouseover', this._mouseover, false);
          this.addEvent(a, 'mousemove', this._mousemove, false);
          this.addEvent(a, 'mouseout', this._mouseout, false);
          rootevt.appendChild(a);
        }
        var region = OCAL.getRegion(el);
        rootevt.style.top=(region.bottom-2)+'px';
        rootevt.style.left=region.left+'px';
        rootevt.style.visibility='visible';
      }, false);
    }
    this.addEvent(document.body, 'click', function(e){
      this.$('cal_rootevt').style.visibility='hidden';
    }, false);
  }
});

