//////////////////////////////////
//
//    JS IM Library
//        by Colspan (Miyoshi)
//         http://colspan.net/
//        License : MIT license
//
// depend browser.js,keycode.js,selectionSupport.js

function baseObject() {
  return function(){this.initialize.apply(this, arguments);}
}

var JS_IM = baseObject();
JS_IM.prototype = {
  version : "20080724",
  author : "Colspan",
  isEnabled : false,// 有効か無効か
  methodObj : null,// JS_IM_Methodの形式のIMオブジェクト
  imeBox : null,// 乗っ取るelement( textarea or input )
  workingPhase : null,// 動作フェーズ
  inlineInputting : false,// インライン入力を有効にするか
  initialize : function(formObj,methodObj){
    var _this = this;
    this.imeBox = formObj;
    this.methodObj = new JS_IM_Method( methodObj );
    this.methodObj.JS_IM_Obj = this;
    var keyProcess = function(e){
      if( !browser.isIe && !event ) var event = e;
      var status = _this.process(event);
      return !status;
    }

    this.imeBox.onblur = this.imeBox.onmousedown = function(){
      if( _this.isEnabled ) _this.accept();
    }

    ///// キー入力関連イベント取得 /////
    /*
      ** 連打対策 **
      Windows環境ではonkeydownによって長押しによる連打が可能
      Linux版Firefoxではonkeypressによって長押しによる連打が可能
    */
    this.imeBox.onkeydown = function(e){
      if( browser.isIe ){
        return keyProcess(e);
      }
      else{ // Not IE
        // do nothing
      }
    }
    this.imeBox.onkeypress = function(e){
      if( browser.isIe ){
        //do nothing
      }
      else{ // Not IE
        return keyProcess(e);
      }
    }

    this.imeBox.onkeyup = function(e){ // Firefox on Linuxにおける問題を解決
      if( _this.isEnabled ) return false;
      return true;
    }

    // GUI 初期化
    if( this.methodObj.params.listBox || ! this.methodObj.params.inlineInsertion ){
      this.GUI = new JS_IM_GUI( this );
    }
    if( this.methodObj.params.listBox ){
      this.GUI.list.init();
    }
    if( this.methodObj.params.inlineInsertion ){
      // do nothing
    }
    else{
      this.GUI.buffer.init();
    }

    // 起動
    this.enable();

  },
  selectAll : function(){
    this.imeAccept();
    this.imeBox.focus();
    this.imeBox.select();
  },
  clear : function(){
    this.combiningStr = ""
    this.imeBox.focus();
    this.imeBox.value = "";
  },
  enable : function(){
    this.isEnabled = true;
    this.combiningStr = "";
    this.originalBackgroundColor = this.imeBox.style.background;
    this.imeBox.style.background = "#FFF0F0";
    this.imeBox.focus();
  },
  disable : function(){
    this.isEnabled = false;
    this.combiningStr = "";
    this.imeBox.style.background = this.originalBackgroundColor;
    this.imeBox.focus();
  },
  // On Offを切り替える
  toggle : function (){
    if( this.isEnabled ) this.disable();
    else this.enable();
  },
  // 処理文字列を確定する
  accept :  function(){
    if( ! this.isEnabled ) return;
    if( this.methodObj.inlineBuffer != "" ){
      if( this.methodObj.params.inlineInsertion ) for( i=0; i<this.methodObj.inlineBuffer.length; i++ ) selectionSupport.backSpace( this.imeBox );
      var outputStr = this.methodObj.accept();
      selectionSupport.insert( this.imeBox, outputStr );
    }
  },
  ///// IME処理関数 process /////
  /*
    返り値 : boolean
      IME処理を行った場合 : true
      IME処理を行わなかった場合 : false
  */
  process : function (e){
    var inputCode,inputChar;
    if( browser.isIe ) e = event; // IEの場合

    // 起動制御
    if( this.methodObj == null ) return false; // methodが指定されていないとき

    // 入力文字取得
    if( browser.isIe ){ // onkeydownからやってくる
      inputCode = e.keyCode;//( browser.isIe ? e.keyCode : e.charCode ); // IEだとkeyCode
      if( inputCode >= 65 && inputCode <= 90 ){
        inputChar = String.fromCharCode(inputCode);
        inputChar = e.shiftKey ? inputChar.toUpperCase() : inputChar.toLowerCase(); // IE対策
      }
      else if( inputCode >= 188 && inputCode <= 191 ){
        inputCode -= e.shiftKey ? 128 : 144;
        inputChar = String.fromCharCode(inputCode);
      }
      else inputChar = null;
    }
    else{// Not IE (mainly Firefox) onkeypressからやってくる
      if( e.keyCode == 0 && e.charCode != 32 ){
        inputCode = e.charCode;
        inputChar = String.fromCharCode(inputCode);
      }
      else{
        inputCode = e.keyCode ? e.keyCode : e.charCode;
        inputChar = null;
      }
    }

    // Methodに渡すキー情報オブジェクト
    var keyStatus = {
      ctrlKey : e.ctrlKey,
      altKey : e.altKey,
      metaKey : e.metaKey,
      shiftKey : e.shiftKey,
      keyCode : e.keyCode,
      charCode : e.charCode,
      inputChar : inputChar,
      inputCode : inputCode
    }

    // キーコードによる制御
    if( e.keyCode == 8 ){ // BackSpace
      return this.backspace();
    }
    else if( e.keyCode == 16 || e.keyCode == 17 ){ // Shift || Ctrl
      return false;
    }
    else if( e.keyCode == 224 ){ // Meta
      this.accept();
      return false;
    }
    else if( keyStatus.ctrlKey || keyStatus.altKey || keyStatus.metaKey ){ // 特殊キーが押されているとき
      if( keyCode.isAlphabet( inputCode ) ){
        //特殊キーと文字キーの組み合わせでは確定
        this.accept();
        return false;
      }
    }
    else if( keyStatus.inputCode == 32 && keyStatus.shiftKey ){ // Shift + Space なら toggle する
      this.accept();
      this.toggle();
      return true;
    }

    if( ! this.isEnabled ) return false;  // IMEが無効になっている場合

    // インライン入力 + 結合処理
    var lastInlineBufferLength = this.methodObj.inlineBuffer.length; // 結合前の文字数を記憶
    var outputStr = this.methodObj.process( keyStatus ); // 結合処理
    if( outputStr != null ){ // JS_IM_Methodから確定文字(空文字を含む)が返された
      if( this.methodObj.params.inlineInsertion ) for( i=0; i<lastInlineBufferLength; i++ ) selectionSupport.backSpace( this.imeBox ); // テキストボックスからインライン文字列を取り除く
      selectionSupport.insert( this.imeBox, outputStr ); // テキストボックスに確定文字を挿入
      if( this.methodObj.params.inlineInsertion ) selectionSupport.insert( this.imeBox, this.methodObj.inlineBuffer ); // テキストボックスにインライン文字列を挿入
      return true;
    }
    else{ // バッファ内容を確定
      this.accept();
      return false;
    }

  },
  backspace : function(){ // 後退処理
    // 起動制御
    if( this.methodObj == null ) return false; // methodが指定されていないとき
    if( !this.isEnabled ) return false;  // IMEが無効になっている場合

    var lastInlineBufferLength = this.methodObj.inlineBuffer.length;
    var returnValue = this.methodObj.backspace();
    if( this.methodObj.params.inlineInsertion ){
      for( i=0; i<lastInlineBufferLength; i++ ) selectionSupport.backSpace( this.imeBox ); // テキストボックスからインライン文字列を取り除く
      selectionSupport.insert( this.imeBox, this.methodObj.inlineBuffer );//テキストボックスに挿入
    }
    return returnValue;
  }

}

function setClassName( targetElement, className ){
  targetElement.className = className;
  if( targetElement.setAttribute ) targetElement.setAttribute( "class", className );
}
function getPosition( targetElement ){
  var top = 0, left = 0;
  do {
    top += targetElement.offsetTop  || 0;
    left += targetElement.offsetLeft || 0;
    targetElement = targetElement.offsetParent;
  } while (targetElement);
  return { top:top,left:left };
}

var JS_IM_GUI = baseObject();
JS_IM_GUI.prototype = {
  version : "20080623",
  author : "Colspan",
  JS_IM_Obj : null,
  initialize : function( JS_IM_Obj ){
    this.buffer.JS_IM_Obj = this.list.JS_IM_Obj = this.JS_IM_Obj = JS_IM_Obj;
  },
    buffer : {
      JS_IM_Obj : null,
      elem : null,
      init : function(){
        var bufferBoxElem = document.createElement("div");
        var body = document.getElementsByTagName("body");
        setClassName(bufferBoxElem, "jsim_bufferbox");

        var imeBoxPosition = getPosition( this.JS_IM_Obj.imeBox );
//        var imeBoxDimension = Element.getDimensions( this.JS_IM_Obj.imeBox );
        bufferBoxElem.style.top = imeBoxPosition.top + "px";
        bufferBoxElem.style.left = imeBoxPosition.left + this.JS_IM_Obj.imeBox.offsetWidth + "px";

        body[0].appendChild( bufferBoxElem );
        this.elem = bufferBoxElem;
      },
      update : function( bufferStr ){
        this.elem.innerHTML = bufferStr;
        this.flush();
      },
      flush : function(){
        this.elem.style.visibility = "visible";
      },
      hide : function(){
        this.elem.style.visibility = "hidden";
      }
    },
    list : {
      JS_IM_Obj : null,
      elem : null,
      candidateElems : null,
      position : 0,
      init : function(){
        var listBoxElem = document.createElement("ul");
        setClassName( listBoxElem, "jsim_listbox" );

        var imeBoxPosition = getPosition( this.JS_IM_Obj.imeBox );
        listBoxElem.style.top = imeBoxPosition.top + 30 + "px";
        listBoxElem.style.left = imeBoxPosition.left + this.JS_IM_Obj.imeBox.offsetWidth + "px";

        var body = document.getElementsByTagName("body");
        body[0].appendChild( listBoxElem );
        this.elem = listBoxElem;
        this.candidateElems = new Array();
      },
      next : function(){
        this.position += 1 + this.candidateElems.length;
        this.position %= this.candidateElems.length;
        this.flush(); // 背景色再描画
      },
      prev : function(){
        this.position += -1 + this.candidateElems.length;
        this.position %= this.candidateElems.length;
        this.flush(); // 背景色再描画
      },
      update : function(listArray){ // 配列書き換え
        var i,length;
        length = this.candidateElems.length;
        for(i=0;i<length;i++){
          this.elem.removeChild( this.candidateElems[i] );
        }
        this.candidateElems = new Array();
        if( typeof listArray == 'string' ){
          var candidate = document.createElement("li");
          candidate.innerHTML = listArray;
          this.elem.appendChild( candidate );
          this.candidateElems[i] = candidate;
        }
        else{
          length = listArray.length;
          for(i=0;i<length;i++){
            var candidate = document.createElement("li");
            candidate.innerHTML = listArray[i];
            this.elem.appendChild( candidate );
            this.candidateElems[i] = candidate;
          }
        }
        this.position = 0;
        this.flush();
      },
      flush : function(){ // リスト描画
        this.elem.style.visibility = "visible";
        var length = this.candidateElems.length;
        var pos = this.position;
        var color,background;
        for(i=0;i<length;i++) if( i == pos ) {
          setClassName(this.candidateElems[i], "selected" );
        }
        else{
          this.candidateElems[i].className = '';
        }

        // 描画位置設定
        var bufferElem = this.JS_IM_Obj.GUI.buffer.elem;
        if( bufferElem ){
          var bufferElemPosition = getPosition( bufferElem );
          this.elem.style.left = bufferElemPosition.left + "px";
          this.elem.style.top = bufferElemPosition.top + bufferElem.offsetHeight + "px";
        }
      },
      hide : function(){
    //        this.update( new Array() );
        this.elem.style.visibility = "hidden";
      },
      setPosition : function( num ){
        this.position = num;
        this.flush();
      }
    }
};


var JS_IM_Method = baseObject();
JS_IM_Method.prototype = {
  methodName : "",
  version : "",
  language : "",
  author : "",
  inlineBuffer : "", // インライン入力作業文字列
  extension : { // 独自拡張領域
    methodObj : null
  },
  JS_IM_Obj : null, // 親オブジェクト
  phase : null,
  params : { // 親に渡す情報
    listBox : false,
    inlineInsertion : true
  },
  init : function(){// 初期化処理
  },
  callback : function(){// コールバック用
  },
  accept : function(){
    var outputStr = this.inlineBuffer;
    this.inlineBuffer = "";
    return outputStr;
  },
/*// process( keyStatus )
  入力値 キー入力情報
  返り値 outputStr
    結合処理ができる場合 文字列 (処理における確定文字がない場合は空文字を返す)
    結合処理ができない場合 null
*/
  process : function( keyStatus ){
    var outputStr = keyStatus.inputChar;
    return outputStr;
  },
  backspace : function(){
  },
  initialize : function(functionObj){
    var valueList = ["init","methodName","version","language","author","accept","process","backspace","extension","params","callback"];
    if( functionObj ){
      for( i=0;i<valueList.length;i++ ) if( functionObj[valueList[i]] ) this[valueList[i]] = functionObj[valueList[i]];
      this.init();
    }
    this.extension.methodObj = this;
  }
}

// JS_IM Method Sample
var JS_IM_toUpperCase = {
  methodName : "toUpperCase",
  version : "20080123",
  language : "English",
  author : "Colspan",
  process : function( keyStatus ){
    var outputStr = keyStatus.inputChar.toUpperCase();
    return outputStr;
  }
}

