//=============================================================================
//  panorama.js - shift_jis - ver2.00 - 2006-08-29
//
//-----------------------------------------------------------------------------
// 端数を切り捨てる
Number.prototype.rDown = function() {
  return (this >= 0) ? Math.round(this-0.5) : Math.round(this+0.4);
};
// オブジェクトがdestructorメソッドを持つならばそれを実行する
Object.prototype.del = function() {
  if (this.destructor) { this.destructor.apply(this, arguments); }
  return undefined;
};


//-----------------------------------------------------------------------------
// パノラマ操作オブジェクトを生成する
// object Panorama(element)
function Panorama(parentElem) {
  if ((arguments.length == 1) || (typeof(elem) == "object")) {
  } else {
    throw("構文エラー：Panorama(element)");
  }

  this.parentElem = parentElem;        // 内側に枠(frameElem)を作るための親エレメント
  this.frameElem  = null;              // 親エレメント(parentElem)の内側に作られ、imgを内側に持つ枠エレメント
  this.imgWidth   = 0;                 // 画像幅
  this.scSpeed    = 0;                 // スクロール速度
  this.printFunc  = null;              // スクロール速度の出力関数
  this.timerId    = undefined;         // setIntervalの戻り値
  this.isReverse  = false;             // 画像の並び順  AB=false / BA=ture
  this.imgLeftPos = [0, 0];            // 画像の座標(毎回DOM取得すれば不要になるが実行効率上設置)
  this.abst       = new IPanorama();   // 実装へのインターフェース

  this.INTERVAL   = 10;                // setIntervalの第2引数
  this.lastCode   = null;              // 最後のスクロール(*Sc)系メソッドの結果が格納される

  // 指定した画像情報をもとにdiv枠を生成し、画像を表示する
  // 戻り値：作成(もしくは変更)した枠エレメント
  // element show(string, uint, uint, uint[, boolean])
  this.show = function(path, width, height, maxDivWidth, isAfter) {
    if ((arguments.length == 4) &&
        (typeof(path) == "string") &&
        (typeof(width) == "number") && (width >= 0) &&
        (typeof(height) == "number") && (height >= 0) &&
        (typeof(maxDivWidth) == "number") && (maxDivWidth >= 0)) {
      width       = width.rDown();
      height      = height.rDown();
      maxDivWidth = maxDivWidth.rDown();
      isAfter     = false;
      return this.abst.show.call(this, path, width, height, maxDivWidth, isAfter);
    } else if ((arguments.length == 5) &&
               (typeof(path) == "string") &&
               (typeof(width) == "number") && (width >= 0) &&
               (typeof(height) == "number") && (height >= 0) &&
               (typeof(maxDivWidth) == "number") && (maxDivWidth >= 0) &&
               (typeof(isAfter) == "boolean")) {
      width       = width.rDown();
      height      = height.rDown();
      maxDivWidth = maxDivWidth.rDown();
      return this.abst.show.apply(this, arguments);
    } else {
      throw("構文エラー：show(string, number>=0, number>=0, number>=0, [, boolean])");
    }
  };

  // 親エレメントのheight値を画像の高さと同じにする ※意図的にshowとは分離させています
  // 戻り値：変更後の親エレメントのheight値("px"は付いてこない)
  // int fitParentHeight()
  this.fitParentHeight = function() {
    if (arguments.length === 0) {
      return this.abst.fitParentHeight.apply(this, arguments);
    } else {
      throw("構文エラー：fitParentHeight()");
    }
  };

  // パノラマ画像のtitle属性を設定する
  // 戻り値：実行以前のtitle属性の文字列
  // string setTitle(string)
  this.setTitle = function(text) {
    if (arguments.length === 0) {
      text = "";
      return this.abst.setTitle.call(this, text);
    } else if ((arguments.length === 1) &&
               (typeof(text) == "string")) {
      return this.abst.setTitle.apply(this, arguments);
    } else {
      throw("構文エラー：setTitle(string)");
    }
  };

  // スクロール速度の出力関数を登録する
  // undefined registPrintFunc(function)
  this.registPrintFunc = function(func) {
    if ((arguments.length == 1) && (typeof(func) == "function")) {
      this.abst.registPrintFunc.apply(this, arguments);
    } else {
      throw("構文エラー：registPrintFunc(function)");
    }
  };

  // スクロール速度の出力関数を削除する
  // undefined = unregistPrintFunc()
  this.unregistPrintFunc = function() {
    if (arguments.length === 0) {
      this.abst.unregistPrintFunc.apply(this, arguments);
    } else {
      throw("構文エラー：unregistPrintFunc()");
    }
  };

  // スクロール速度を右側に傾ける(加算する)
  // lastCode：実際に変更したら真、変更しなかったら偽
  // undefined addRightSc([uint])
  this.addRightSc = function(num) {
    if (arguments.length === 0) {
      num = 1;
      this.lastCode = this.abst.addRightSc.call(this, num);
    } else if ((arguments.length === 1) &&
               (typeof(num) == "number") && (num >= 0)) {
      num = num.rDown();
      this.lastCode = this.abst.addRightSc.apply(this, arguments);
    } else {
      throw("構文エラー：addRightSc([number>=0])");
    }
  };

  // スクロール速度を左側に傾ける(減算する)
  // lastCode：実際に変更したら真、変更しなかったら偽
  // undefined addLeftSc([uint])
  this.addLeftSc = function(num) {
    if (arguments.length === 0) {
      num = 1;
      this.lastCode = this.abst.addLeftSc.call(this, num);
    } else if ((arguments.length === 1) &&
               (typeof(num) == "number") && (num >= 0)) {
      num = num.rDown();
      this.lastCode = this.abst.addLeftSc.apply(this, arguments);
    } else {
      throw("構文エラー：addLeftSc([number>=0])");
    }
  };

  // スクロール速度を指定値にする
  // lastCode：実際に変更したら真、変更しなかったら偽
  // undefined setSc([int])
  this.setSc = function(num) {
    if (arguments.length === 0) {
      this.lastCode = this.abst.setSc_0.apply(this, arguments);
    } else if ((arguments.length === 1) && (typeof(num) == "number")) {
      num = num.rDown();
      this.lastCode = this.abst.setSc_1.apply(this, arguments);
    } else {
      throw("構文エラー：setSc([number])");
    }
  };

  // スクロールを停止する
  // lastCode：実際に変更したら真、変更しなかったら偽
  // undefined stopSc()
  this.stopSc = function() {
    if (arguments.length === 0) {
      this.lastCode = this.abst.stopSc.apply(this, arguments);
    } else {
      throw("構文エラー：stopSc()");
    }
  };

  // デストラクタ(生呼びしてもいいし、delから呼んでも構わん
  // undefined destructor()
  this.destructor = function() {
    if (arguments.length === 0) {
      return this.abst.destructor.apply(this, arguments);
    } else {
      throw("構文エラー：destructor()");
    }
  };

  // setIntervalから呼び出されるスクロール処理関数
  this.onTimerProc = function() {
    this.abst.onTimerProc.apply(this, arguments);
  };
}

//-----------------------------------------------------------------------------
// パノラマ操作の実装クラス
function IPanorama() {

  // パノラマ画像関係のエレメントを生成
  this.show = function(path, width, height, maxDivWidth, isAfter) {
    var cElem;
    var isNew     = (this.frameElem === null);
    this.imgWidth = width;
    // frame操作
    if (isNew) {
      cElem                = document.createElement("div");    // 新規作成
      cElem.style.overflow = "hidden";
      cElem.style.position = "absolute";
      this.frameElem       = this.parentElem.insertBefore(cElem, (isAfter) ? null : this.parentElem.firstChild);
    } else {
      this.stopSc();                                           // 書き換えにつき停止と再初期化
      this.isReverse  = false;
      this.imgLeftPos = [0, 0];
      this.frameElem.childNodes[0].style.left = this.frameElem.childNodes[1].style.left = "0px";
    }
    this.frameElem.style.width  = ((maxDivWidth < width*0.9) ? maxDivWidth : (width*0.9)) +"px";
    this.frameElem.style.height = height +"px";
    // img操作
    for (var i=0 ; i<2 ; i++) {
      if (isNew) {
        cElem                = document.createElement("img");  // 新規作成
        cElem.style.position = "absolute";
        this.frameElem.appendChild(cElem);
      }
      this.frameElem.childNodes[i].alt    = "Panorama Image ("+ path +")";
      this.frameElem.childNodes[i].src    = path;
      this.frameElem.childNodes[i].width  = width;
      this.frameElem.childNodes[i].height = height;
    }
    return this.frameElem;
  };

  // 親エレメントのheight値を画像の高さと同じにする
  this.fitParentHeight = function() {
    var n = parseInt(this.frameElem.style.height, 10);
    this.parentElem.style.height = n+"px";
    return n;
  };

  // パノラマ画像にtitle属性を設定
  this.setTitle = function(text) {
    var old = this.frameElem.childNodes[0].title;
    this.frameElem.childNodes[0].title = this.frameElem.childNodes[1].title = text;
    return old;
  };

  // 出力関数の登録
  this.registPrintFunc = function(func) {
    this.printFunc = func;
  };

  // 出力関数の解除
  this.unregistPrintFunc = function() {
    if (this.printFunc) { this.printFunc = null; }
  };

  // スクロール速度加算
  this.addRightSc = function(num) {
    return this.setSc(this.scSpeed +num);
  };

  // スクロール速度減算
  this.addLeftSc = function(num) {
    return this.setSc(this.scSpeed -num);
  };

  // スクロール速度を変更しない
  this.setSc_0 = function() {
    if (this.printFunc !== null) { this.printFunc(this.scSpeed); }  // 速度出力関数を実行
    return false;
  };

  // スクロール速度を実際に変更
  this.setSc_1 = function(num) {
    if (Math.abs(num) >= (this.imgWidth/10) || (num == this.scSpeed)) { return false; }
    if (this.scSpeed === 0) {    // タイマを登録
      var pthis = this;          // pthisはPanoramaクラスのインスタンスを指す
      this.timerId = setInterval(function() { pthis.onTimerProc(); }, this.INTERVAL);
    }
    if (num === 0) { this.timerId = clearInterval(this.timerId); }  // タイマを削除
    this.scSpeed = num;                                             // 速度を変更
    if (this.printFunc !== null) { this.printFunc(this.scSpeed); }  // 速度出力関数を実行
    return true;
  };

  // スクロール速度を0にする
  this.stopSc = function() {
    return this.setSc(0);
  };

  // 完全に削除する
  this.destructor = function() {
    this.stopSc();
    this.parentElem.style.height = "";
    this.parentElem.removeChild(this.frameElem);
  };

  // setIntervalから呼び出されるスクロール処理関数
  this.onTimerProc = function() {
    if (this.scSpeed > 0) {
      if ((this.isReverse === false) && (this.imgLeftPos[0] >= this.imgWidth /-10)) {
        this.imgLeftPos[1] -= this.imgWidth;
        this.isReverse      = true;
      } else if ((this.isReverse === true) && (this.imgLeftPos[1] >= this.imgWidth /-10)) {
        this.imgLeftPos[0] -= this.imgWidth;
        this.isReverse      = false;
      }
    } else {
      if ((this.isReverse === false) && (this.imgLeftPos[1] <= this.imgWidth /-10)) {
        this.imgLeftPos[0] += this.imgWidth;
        this.isReverse      = true;
      } else if ((this.isReverse === true) && (this.imgLeftPos[0] <= this.imgWidth /-10)) {
        this.imgLeftPos[1] += this.imgWidth;
        this.isReverse      = false;
      }
    }
    this.imgLeftPos[0] += this.scSpeed;
    this.imgLeftPos[1] += this.scSpeed;
    this.frameElem.childNodes[0].style.left = this.imgLeftPos[0] +"px";
    this.frameElem.childNodes[1].style.left = this.imgLeftPos[1] +"px";
  };
}


//=============================================================================



/*
  ●実装の特徴
  極端に厳しい型チェック(小数のみエラーにせず端数を切り捨てる)
  インターフェースのPanoramaと実装のIPanorama
    引数省略でデフォルト値になるものはPanorama上で初期値を与え単一の実装へ *.call(this, [])
    引数省略で処理が変わるものはIPanorama上で複数の実装を持つ

  ●メソッドの依存関係
    ※     A -> B
    AはBを利用する
  show       -> stopSc
  addRightSc -> setSc
  addLeftSc  -> setSc
  stopSc     -> setSc
  destructor -> stopSc
  setSc      -> onTimerProc
  setSc      -> printFunc

*/

