
//////////////////////////////////////////////////
// MethodChain

function MethodChain() {}
MethodChain.prototype.chains = new Array();
MethodChain.prototype.next = function() {
  var f = this.chains.shift();
  if (f == null) {
    this.clear();
  } else {
    var self2 = this;
    setTimeout(function() { f(self2); }, 1);
  }
};
MethodChain.prototype.set = function(f) {
  this.chains.push(f);
};
MethodChain.prototype.clear = function() { this.chains = new Array(); }

//////////////////////////////////////////////////
// queue

function Queue() {}
Queue.prototype.ring = new Array();
Queue.prototype.data = {};
Queue.prototype.push = function(addkey, addvalue) {
  if (this.ring.length >= 16) {
    var delkey = this.ring.shift();
    delete this.data[delkey];
  }
  this.ring.push(addkey);
  this.data[addkey] = addvalue;
};
Queue.prototype.find = function(key) {
  if (this.data.hasOwnProperty(key)) {
    return this.data[key];
  }
  
  return null;
};

//予約日object
ReserveType = function(){};
ReserveType.prototype.reserves = new Object();
ReserveType.prototype.get = function(year, month, day) {
  if (this.reserves[year] == undefined) {
    return '';
  }
  if (this.reserves[year][month] == undefined) {
    return '';
  }
  if (this.reserves[year][month][day] == undefined) {
    return '';
  }
  return this.reserves[year][month][day];
};
ReserveType.prototype.load = function(year, month, xml) {
  var values = xml.getElementsByTagName('value');
  
  if (this.reserves[year] == undefined) {
    this.reserves[year] = new Object();
  }
  if (this.reserves[year][month] == undefined) {
    this.reserves[year][month] = new Object();
  }

  for(var i=0; i< values.length; i++) {
    var value = parseInt(values[i].childNodes[0].nodeValue);
    var day = parseInt(values[i].getAttribute('day'));
    
    var isStop = values[i].getAttribute('stop') == 1;
    var isReady = values[i].getAttribute('ready') == 1;
    
    var type = '';
    if(isStop) {
      type = 'soldout';
    } else if(isReady) {
      type = 'ready';
    } else if(value > 0){
      type = 'active';
    }else {
      type = 'soldout';
    }
    
    this.reserves[year][month][day] = type;
  }
}

//////////////////////////////////////////////////
// JsSelectCalendar

function JsSelectCalendar() {}

JsSelectCalendar.prototype.mlabel = ['日', '月', '火', '水', '木', '金', '土'];
JsSelectCalendar.prototype.varName = '';

JsSelectCalendar.prototype.loadEleId = 'calendar-load';

JsSelectCalendar.prototype.bodyElementItem = new Array();
JsSelectCalendar.prototype.bodyRendered = new Array();

//カレンダーの表示範囲
JsSelectCalendar.prototype.startLimitDate = null;
JsSelectCalendar.prototype.limitDate = null;

JsSelectCalendar.prototype.holidays = new Object();
JsSelectCalendar.prototype.reserveTypes = new ReserveType();

//表示するカレンダーの数,親となるId
JsSelectCalendar.prototype.page = 2;
JsSelectCalendar.prototype.bodyElement = ['calendar_left', 'calendar_right'];
//行の数 -1なら空行を作らない
JsSelectCalendar.prototype.rowCnt = 6;

//詳細ページのidと予約情報取得へのリンク
JsSelectCalendar.prototype.basePath = '';
JsSelectCalendar.prototype.page_id = 0;
JsSelectCalendar.prototype.reserve_link = 'hoge.php';

JsSelectCalendar.prototype.xhrThreads = 0;

// 再表示
JsSelectCalendar.prototype.rebuild = function(year, month) {  
  this.build(year, month);
};

// 表示
JsSelectCalendar.prototype.build = function( year, month) {
  var self2 = this;
  
  // 要素作成
  var loadEle = document.getElementById(this.loadEleId);
  loadEle.innerHTML = '読み込み中です...';
  
  for(var i=0;i<this.page; i++) {
    this.bodyElementItem[i] = document.getElementById(this.bodyElement[i]);
  }
  
  // 逐次実行
  var chain = new MethodChain();
  for(var i=0; i < this.page; i++) {
    var d = this.addMonth(year, month, i);
    // 祝日
    chain.set( (function(year2, month2) {
      return function(chain) {
        self2.holidayLoader(chain, year2, month2);
      }; })(d.getFullYear(), d.getMonth() + 1)
    );
    // 予約
    chain.set( (function(year2, month2) {
      return function(chain) {
        self2.reserveLoader(chain, year2, month2);
      }; })(d.getFullYear(), d.getMonth() + 1)
    );
  }
  
  
  chain.set(function(chain) {
    self2.buildMain(chain, year, month);
  });
  chain.set(function(chain) {
    loadEle.innerHTML = '';
    chain.next();
  });

  chain.next();
};

JsSelectCalendar.prototype.clearBodyAll = function() {
  for(var i=0;i<this.page;i++) {
    this.clearBody(i);
  }
}

JsSelectCalendar.prototype.clearBody = function(i) {
  if (this.bodyRendered[i] != null) {
    this.bodyElementItem[i].removeChild(this.bodyRendered[i]);
    this.bodyRendered[i] = null;
  }
}

JsSelectCalendar.prototype.buildMain = function(chain, year, month) {
  //ページの数だけカレンダを作る
  for(var i=0 ; i < this.page; i++) {
    var d = this.addMonth(year, month, i);
    
    var pager = this.createPager(year, month, i);
    
    var bodyEl = this.createCalendar(d.getFullYear(), d.getMonth() + 1,  pager);
    this.clearBody(i);
    this.bodyElementItem[i].appendChild(bodyEl);
    this.bodyRendered[i] = bodyEl;
  }
  
  chain.next();
};

/* カレンダー本体作成 */

/*
 * ページャー作成
 */
JsSelectCalendar.prototype.createPager = function(year, month, dispPageCnt) {
  
  var pagerFunc = function(){return null;}; 
  switch(dispPageCnt) {
    case 0           : pagerFunc = this.createPrevPager; break;
    case this.page-1 : pagerFunc = this.createNextPager; break;
    default : break;
  }
  
  var ele = pagerFunc.call(this, year, month);
  if(ele == null) {
    ele  = document.createTextNode(''); 
  }
  
  return ele;
};

JsSelectCalendar.prototype.createNextPager = function(year, month) {
  var d, y, m; // 日付一時データ
  var self = this;
  
  // next
  d = this.addMonth(year, month, this.page);  // nextの表示はページ数後
  var dn = this.addMonth(year, month, 1);     // 表示は翌月(+1);
  var yn  = d.getFullYear();
  var mn = d.getMonth() + 1;
  var a_next = document.createElement('a');
  a_next.href = '#';
  a_next.onclick = function(event) {
    self.rebuild(dn.getFullYear(), dn.getMonth() + 1);
    if (window.event) {
      window.event.cancelBubble = true;
    } else {
      event.stopPropagation();
    }
    return false;
  };
  a_next.innerHTML = yn + '年' + mn + '月';
  
  var show_next = true;
  if (this.limitDate != null) {
    var dlimit = new Date(this.limitDate + " 00:00:00");
    dlimit = this.addMonth(dlimit.getFullYear(), dlimit.getMonth()+1, -(this.page - 1));
    if (dlimit.getTime() >= dn.getTime()) {
    } else {
      show_next = false;
    }
  }
  if (show_next) {
    var span = document.createElement('span');

    span.appendChild(a_next);
    span.appendChild(document.createTextNode('\xA0'));
    span.appendChild(document.createTextNode('\xBB'));
    
    return span;
  }
  
  return null;
}
JsSelectCalendar.prototype.createPrevPager = function(year, month) {
  
  var self = this;
  //  d = this.addMonth(year, month, start - 1);
  var d_prev = this.addMonth(year, month, -1);
  var yp  = d_prev.getFullYear();
  var mp = d_prev.getMonth() + 1;
  var a_prev = document.createElement('a');
  a_prev.href = '#';
  a_prev.onclick = function(event) {
    self.rebuild(yp, mp);
    if (window.event) {
      window.event.cancelBubble = true;
    } else {
      event.stopPropagation();
    }
    return false;
  };
  a_prev.innerHTML = yp + '年' + mp + '月';

  var show_prev = true;
  if (this.startLimitDate != null) {
    var dstart = new Date(this.startLimitDate  + " 00:00:00");
    if (dstart.getTime() <= d_prev.getTime()) {
    } else {
      show_prev = false;
    }
  }
  
  if(show_prev) {
    var span = document.createElement('span');
    
    span.appendChild(document.createTextNode('\xAB'));
    span.appendChild(document.createTextNode('\xA0'));
    span.appendChild(a_prev);
  
    return span;
  }
  
  return null;
}

JsSelectCalendar.prototype.createWeek = function() {
  var tr = document.createElement('tr');
  for(var i=0;i<this.mlabel.length;i++) {
    var td = document.createElement('th');
    td.appendChild(document.createTextNode(this.mlabel[i]));
    if (i == 0) {
      td.id= 'red';
    } else if (i == 6) {
      td.className = 'blue';
    }
    tr.appendChild(td);
  }
  
  return tr;
}

//ページごとのカレンダーを作る
JsSelectCalendar.prototype.createCalendar = function(year, month, pager) {
  var c = document.createElement('table');
  c.cellSpacing = 0;
  c.cellPadding = 0;
  c.border = 0;
  
  var thead = this.createCalendarHeader(year, month, pager);
  var tbody = this.createCalendarBody(year, month);
  
  c.appendChild(thead);
  c.appendChild(tbody);
  
  return c;
}

//ヘッダを作成
JsSelectCalendar.prototype.createCalendarHeader = function(year, month, pager) {
  var thead = document.createElement('thead');
  
  //タイトル作成
  var tr = document.createElement('tr');
  var title = document.createElement('th');
  title.className = 'month'
  title.colSpan = 7;
  
  title.appendChild(document.createTextNode(year + '年' + month + '月'));
  title.appendChild(pager);
  
  tr.appendChild(title);
  thead.appendChild(tr);
  
  //週作成
  thead.appendChild(this.createWeek());
  
  
  return thead;
};

//body
JsSelectCalendar.prototype.createCalendarBody = function(year, month) {
  var tbody = document.createElement('tbody');
  
  var rowCnt = 1;
  var theadr = document.createElement('tr');
  var  tr = document.createElement('tr');
  
  var dd = new Date(year, month-1, 1, 0, 0, 0, 0);
  var day = 1 - dd.getDay();
  
  
  while(true) {
    dd = new Date(year, month-1, day, 0, 0, 0, 0);
    
    var th = document.createElement('th');
    var td = document.createElement('td');
    
    //日付項目にクラス名を追加する
    this.addDateClassName(th, dd.getDay(), year, month, day);
    
    //今月か
    if (dd.getMonth() != (month - 1)) {
      // 空
      th.innerHTML = '&nbsp;';
      td.innerHTML = '&nbsp;';
      td.className = 'no_date';
    }else {
      th.appendChild(document.createTextNode(day));
      this.addReservedButton(td, year, month, day);
    }
    
    theadr.appendChild(th);
    tr.appendChild(td);
    day = day + 1;
    
    // 改行
    if (dd.getDay() == 6) {
      
      tbody.appendChild(theadr);
      tbody.appendChild(tr);
      
      //次の行
      theadr = document.createElement('tr');
      tr = document.createElement('tr');
      
      //行指定が-1なら翌月になったら終了
      if(this.rowCnt == -1) {
        dd2 = new Date(year, month-1, day, 0, 0, 0, 0);
        if ((day > 28) && (dd2.getMonth() != (month - 1))) {
          break;
        }
      }
      
      //指定行超えたら終了
      if(rowCnt++ >= this.rowCnt) break;
    }
  }
  return tbody;
};

JsSelectCalendar.prototype.addMonth = function(year, month, add) {
  var dd = new Date(year, month-1+add, 1, 0, 0, 0, 0);
  return dd;
};

JsSelectCalendar.prototype.addDateClassName = function(th, dayOfWeerk, year, month, day) {
     if(dayOfWeerk == 0 ) {
      th.className = 'sunday';
    } else if (dayOfWeerk == 6) {
      th.className = 'saturday';
    } 
    
    if(this.isHoliday(year, month, day)) {
      th.className = 'holyday';
    }
};

//日付表示
JsSelectCalendar.prototype.addReservedButton = function(td, year, month, day) {

  var type = this.reserveTypes.get(year, month, day);
  td.className = type;
  switch(type) {
    case 'active' : 
      var a;
      a = document.createElement('a');
      a.appendChild(document.createTextNode('○'));
      a.href = this.reserve_link + '?id=' + this.page_id + '&year=' + year + '&month=' + month + '&day=' + day;
      td.appendChild(a);
      break;
    case 'soldout' : 
      var a;
      a = document.createTextNode('×');
      td.appendChild(a);
      break;
    default :
      td.innerHTML = '&nbsp;'
      break;
  }
};

//////////////
// loader

// 祝日情報
JsSelectCalendar.prototype.holidayLoader = function(chain, year, month) {
  var self2 = this;
  if (this.holidays[year] != undefined) {
    if (this.holidays[year][month] != undefined) {
      chain.next();
      return; // skip
    }
  }
  
  if (this.holidays[year] == undefined) {
    this.holidays[year] = new Object();
  }
  if (this.holidays[year][month] == undefined) {
    this.holidays[year][month] = new Object();
  }
  
  var xhr = this.getXHR();
  this.connect(xhr,
    this.basePath + '?page=holiday_xml&year=' + year + '&month=' + month,
    function(xhr) {
      var xml = xhr.responseXML;
      var values = xml.getElementsByTagName('value');
      for(var i=0;i<values.length;i++) {
        var value = parseInt(values[i].childNodes[0].nodeValue);
        self2.holidays[year][month][value] = 1;
      }
      
      chain.next();
    }
  );
};
// 予約日情報
JsSelectCalendar.prototype.reserveLoader = function(chain, year, month) {
  var self = this;
  var xhr = this.getXHR();
  this.connect(xhr,
    this.basePath + '?page=reserve_xml&page_id=' + this.page_id + '&year=' + year + '&month=' + month,
    function(xhr) {
      self.reserveTypes.load(year, month, xhr.responseXML);
      chain.next();
    }
  );
};


// 祝日かどうか
JsSelectCalendar.prototype.isHoliday = function(year, month, day) {
  if (this.holidays[year] == undefined) {
    return false;
  }
  if (this.holidays[year][month] == undefined) {
    return false;
  }
  if (this.holidays[year][month][day] == undefined) {
    return false;
  }
  
  return true;
};


//////////////
// utility

// XHR
JsSelectCalendar.prototype.getXHR = function() {
  var xhr = false;
  if (window.ActiveXObject) {
    try {
      xhr = new ActiveXObject("Microsoft.XMLHTTP");
    } catch (e) {
      xhr = false;
    }
  }
  if (!xhr && window.XMLHttpRequest) {
    xhr = new XMLHttpRequest();
  }
  
  return xhr;
};

JsSelectCalendar.prototype.xhrCache = new Queue();
JsSelectCalendar.prototype.connect = function(xhr, url, callback) {
  var self2 = this;
  var cache = this.xhrCache.find(url);
  if (cache != null) {
    callback(cache);
  } else {
    xhr.open("GET", url, true);
    xhr.onreadystatechange = function() {
      if (xhr.readyState == 4) {
        if (xhr.status == 200) {
          self2.xhrCache.push(url, xhr);
          callback(xhr);
        }
      }
    }
    xhr.send(null);
  }
};
    
//////////////
// public

// 通常表示
JsSelectCalendar.prototype.show = function(id, year, month) {
  
  //ページ数が、定義済み表示Idより多ければ、定義済みIdを優先する。
  if( this.page > this.bodyElement.length ) {
    this.page =  this.bodyElement.length;
  }
  
  //ページャの範囲を指定する
  if(this.startLimitDate == null) {
    this.startLimitDate = year +'/'+month + '/1';
  }
  
  this.build(year, month);
};

