// ----------------------------------------------------------------------------
// prototype_plus.js
// Prototype+ JavaScript Library
// Version 1.5.2
// Copyright (C) 2006-2008 Taku Watabe
// Time-stamp: <2008-03-18T21:49:07+09:00>
// ----------------------------------------------------------------------------
// ----------------------------------------------------------------------------
// Event 拡張
// ----------------------------------------------------------------------------
// キー 対 コード対応
Event.key2code = {
    // 特殊キー
    esc : 27,
    // 編集キー
    backspace : 8, enter : 13, space : 32, del : 46,
    // 方向キー
    left : 37, up : 38, right : 39, down : 40
};

// コード 対 キー対応
Event.code2key = {
    // 特殊キー
    27 : "esc",
    // 編集キー
    8 : "backspace", 13 : "enter", 32 : "space", 46 : "del",
    // 方向キー
    37 : "left", 38 : "up", 39 : "right", 40 : "down"
};

// イベントを発生させたキーコード（文字コード）を得る
//
// Arguments:
//   event: コードを得たいイベントのオブジェクト
//
// Warnings:
//   - opera はイベントリスナに keypress を指定しても type を keydown にしてし
//     まう
//   - 実質使用できるのは keydown / keyup のみ
Event.getCode = function(event) {
    var code = "0";
    if (event.type == "keypress") {
        if (event.charCode !== undefined && event.charCode !== 0) {
            code = event.charCode;
        } else if (event.charCode === undefined &&
                   event.keyCode !== undefined) {
            code = event.keyCode;
        }
    } else if (event.type == "keydown" || event.type == "keyup") {
        code = event.keyCode;
    }
    return code;
};

// イベントを発生させたキー文字列を得る
//
// Arguments:
//   event: キー文字列を得たいイベントのオブジェクト
//
// Warnings:
//   - アルファベット文字は常に小文字で返される
Event.getKey = function(event) {
    var code = Event.getCode(event);
    return (Event.code2key[code] === undefined) ?
        String.fromCharCode(code).toLowerCase() : Event.code2key[code];
};

// イベントを発生させた要素を得る
//
// Arguments:
//   event: 要素を得たいイベントのオブジェクト
Event.getElement = function(event) {
    return (event.target     !== undefined) ? event.target     :
           (event.srcElement !== undefined) ? event.srcElement : null;
};


// ----------------------------------------------------------------------------
// Object 拡張
// ----------------------------------------------------------------------------
// 継承の実現
//
// Arguments:
//   target: 継承先オブジェクト
//   parent: 継承元オブジェクト
//
// Thanks to:
//   最速インターフェース研究会
//     http://la.ma.la/blog/diary_200507302354.htm
//   檜山正幸のキマイラ飼育記
//     http://d.hatena.ne.jp/m-hiyama/20050922/1127353315
//     http://d.hatena.ne.jp/m-hiyama/20051017/1129510043
Function.inherit = function(target, parent) {
    // __proto__ を用いた方法
    if (target.__proto__           !== undefined &&
        target.prototype.__proto__ !== undefined) {
        // __proto__ を用いてプロトタイプチェーンへ直接接続する
        target.prototype.__proto__ = parent.prototype;
        target.__proto__ = parent;
        // 同じプロトタイプチェーンに接続されているなら成功とみなす
        if (parent.prototype.isPrototypeOf &&
            parent.prototype.isPrototypeOf(target.prototype)) {
            return target;
        }
    }
    // 念のため、余分に付与された __proto__ は delete しない
    // ReadOnly で上書き不可の場合があったらマズいため

    // __proto__ が使えない場合（ないし失敗した場合）
    // new による方法
    target.prototype = new parent;
    try {
        // parent の constructor になったままなので元に戻す
        target.prototype.constructor = target;
    } catch (e) {
        throw Error("can't substitution 'constructor': "+e);
    }
    return target;
};
Function.prototype.inherit = function(parent) {
    return Function.inherit.apply(this, [this, parent]);
};

// 現オブジェクトのインスタンス名を得る
//
// Warnings:
//   - constructor の toString() 結果に依存するので、移植性に欠けるかも
Function.prototype.getObjectName = function() {
    this.constructor.toString().match(/function +([^\s\(]+)/);
    return RegExp.$1;
};

// 現オブジェクトのインスタンス名を付与してメッセージを dump
// Gecko ONLY
//
// Arguments:
//   message: dump させるメッセージ
//            前置詞 "Message: " が付与される
//
// Warnings:
//   - たぶん Gecko 専用
Function.prototype.dumpwn = function(message) {
    // "wn" は "with name" の意
    if (typeof dump != "function") { return; }
    var objName = "Object:  "+this.getObjectName()+"\n";
    message     = "Message: "+message+"\n";
    dump(objName+message);
};

// 現オブジェクトの全プロパティを dumpwn
// Gecko ONLY
//
// Arguments:
//   obj:  表示対象のオブジェクト
//   type: 真ならプロパティの値も表示
//
// Warnings:
//   - Object.prototype.dumpwn に依存
//   - 勿論 [DontEnum] なものは出てこない
Function.dumpwnProps = function(obj, type) {
    var message = "Dump object ALL property:\n";
    for (var i in obj) {
        message += "  " + i + (type ? " = "+obj[i] : "") + "\n";
    }
    this.dumpwn(message);
};
Function.prototype.dumpwnProps = function(type) {
    return Object.dumpwnProps.apply(this, [this, type]);
};


// ----------------------------------------------------------------------------
// Array 拡張
// ----------------------------------------------------------------------------
// 指定したインデックス間の要素を抜き出して返す
//
// Arguments:
//   firstIndex: 抜出す先頭のインデックス
//   lastIndex:  抜出す末尾のインデックス（省略可能）
//               省略時は元配列の末尾
// Warnings:
//   - 呼出側オブジェクトの値は変更されない
Array.prototype.selectItem = function(firstIndex, lastIndex) {
    var ret = Array.apply(Array, this); // 配列をコピー

    // 範囲外なら ret を返す
    if (undefined === firstIndex   ||
        firstIndex < 0             ||
        this.length-1 < firstIndex ||
        (undefined !== lastIndex && (lastIndex < 0 || this.length-1 < lastIndex))) {
        return ret;
    }

    if (undefined === lastIndex) {
        ret.reverse();
        ret.length -= firstIndex;
        ret.reverse();
    } else if (0 == firstIndex && undefined !== lastIndex) {
        ret.length = lastIndex;
    } else if (lastIndex >= firstIndex) {
        delete ret; // 一応念のため
        ret = [];   // 新規生成
        for (var i = firstIndex, r = 0; i < lastIndex+1; i++) {
            ret[r] = this[i];
            r++; // 副作用怖いから……
        }
    } else {
        // 何もしない
    }

    return ret;
};

// 指定したインデックスを正規化して返す
//
// Arguments:
//   index: 正規化したいインデックス
Array.prototype.getIndex = function(index) {
    if (undefined === index || isNaN(index)) { // 未指定 -> index を返す
        return this._index;
    } else if (0 == index) {                   //      0 -> 何もしない
        return 0;
    } else if (0 < index) {                    // 正の値 -> 余剰を使用
        return index % this.length;
    } else {                                   // 負の値 -> 余剰と減算の複合
        return this.length - (Math.abs(index) % this.length);
    }
};

// 指定したインデックスを正規化し、参照先の要素を返す
//
// Arguments:
//   index: 得たい要素のインデックス値
Array.prototype.getItem = function(index) {
    return this[this.getIndex(index)];
};

// 指定したインデックスを正規化し、参照先の要素を返し、インデックスを保存する
//
// Arguments:
//   index: 得たい要素のインデックス値
Array.prototype.setIndex = function(index) {
    this._index = this.getIndex(index);
    return this.getItem(index);
};

// その他の雑多なプロパティ群
Array.prototype.goNext = function() { return this.setIndex(this._index+1); };
Array.prototype.goBack = function() { return this.setIndex(this._index-1); };
Array.prototype.initIndex = function() { this._index = 0; }


// ----------------------------------------------------------------------------
// Element 拡張
// ----------------------------------------------------------------------------
// prototype.js Element オブジェクト用プロパティ追加法
Element.addMethods({
    // 自ノードの並列次位置に node を挿入
    addNextSibling : function(element, node) {
        element = $(element);
        element.parentNode.insertBefore(node, element.nextSibling);
    },

    // 自ノードの並列前位置に node を挿入
    addPreviousSibling : function(element, node) {
        element = $(element);
        element.parentNode.insertBefore(node, element);
    }
});


// ----------------------------------------------------------------------------
// TimerExecuter
// PeriodicalExecuter の改造版
//
// 追加仕様:
//   - start メソッドを明示的に呼ぶまで開始しない
//   - インターバル指定は ms 単位
//   - 初回起動時、タイマに関係なく1回だけコールバック関数を起動
//
// Arguments:
//   callback: コールバック関数
//   interval: インターバル（単位 ms）
// ----------------------------------------------------------------------------
function TimerExecuter(callback, interval, isRepeat) {
    this.callback  = callback; // コールバック関数
    this.interval  = interval; // インターバル間隔 (ms)
    this.isExecute = false;    // 動作中フラグ
    this.isPause   = false;    // 一時停止中フラグ
    this.isRepeat  = true;     // リピートフラグ
    this.id        = null;     // インターバル ID

    this.setRepeat(isRepeat);
}

// リピート
TimerExecuter.prototype.setRepeat = function(isRepeat) {
    if (undefined === isRepeat) { return; } // 未定義なら何もしない
    this.isRepeat = isRepeat ? true : false;
};

// インターバルイベント開始
TimerExecuter.prototype.start = function(/* arguments */) {
    if (null !== this.id ||        // 動作中
        this.isExecute   ||        // 動作中
        this.isPause) { return; }  // 一時停止中
    this.event.apply(this, arguments); // 初回起動
    this.id = this.isRepeat ?          // リピートありならインターバルセット
      setInterval(this.event.bind(this, arguments), this.interval) : 0;
};

// インターバルイベント停止
TimerExecuter.prototype.stop = function() {
    if (null === this.id) { return }; // 未動作 or リピートなし
    clearInterval(this.id);
    this.id = null;
},

// インターバルイベント一時停止
// 実際には event の動作をやめさせるだけで、インターバル動作は続いている
TimerExecuter.prototype.pause = function() {
    this.isPause = this.isPause ? false : true;
};

// イベントメソッド（コールバック関数の呼出）
TimerExecuter.prototype.event = function(/* arguments */) {
    if (this.isExecute ||         // 動作中
        this.isPause) { return; } // 一時停止中
    try {
        this.isExecute = true;                // 動作中にして
        this.callback.apply(this, arguments); // コールバック関数起動
    } finally {
        this.isExecute = false;
    }
};

