/**********************************************************************
* Copyright (C) 2008 Kyoto University
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-
* 1301  USA
***********************************************************************/
/*
 * Classes for a workspace of Document Translation are defined in this file.
 */

var DocumentTranslationWorkspace = Class.create();
var TranslationBuffer = Class.create();
var MessageManager = Class.create();
var BackTranslationManager = Class.create();
var ButtonManager = Class.create();
var Editor = Class.create();

DocumentTranslationWorkspace.prototype = {
	CLIENT_CACHE_KEY_TEMPLATE: new Template('textcache-#{number}'),

	leftEditor: null,
	rightEditor: null,
	activeEditor: null,
	translatorsAndLanguagesSelection: null,
	messageManager: null, //* MessageManagerクラス
	backTranslationManager: null, //* BackTranslationManagerクラス
	translationBuffer: null, //* TranslationBufferクラス
	parallelTextSearchObj: null,
	largeDictionaryId: "",
	clientTextsCache: new Hash(), //* 翻訳文に関する情報を保持するためのハッシュ
	cancelTranslationFlag: false,
	interruptEventListener: null, //* 翻訳をキャンセルするためのイベントリスナを管理するための変数
	isInTranslating: false, //* 翻訳中かどうかを管理する

	//* コンストラクタ
	initialize: function(obj,parallelTextSearchObj){
		this.buttonManager = new ButtonManager(obj.buttonManager);
		this.messageManager = new MessageManager(obj.messagePaneId, this);
		this.backTranslationManager = new BackTranslationManager(this, obj.backtranslationManager);
		this.leftEditor = new Editor(obj.leftEditorId,'source', this, this.messageManager, this.backTranslationManager);
		this.rightEditor = new Editor(obj.rightEditorId,'target', this, this.messageManager, this.backTranslationManager);
		this.translationBuffer = new TranslationBuffer(this, this.messageManager, this.backTranslationManager);
		this.parallelTextSearchObj = parallelTextSearchObj;

		this.translatorsAndLanguagesSelection = new TranslatorsAndLanguagesSelection(this, this.messageManager, this.backTranslationManager);
		this.translatorsAndLanguagesSelection.setProperties(obj.selector);

		this.backTranslationManager.setEditors(this.leftEditor, this.rightEditor);

		this._initEvent();
		this._initSetting();
	},

	_initEvent: function(){
		document.observe("start:translation", this._translate.bindAsEventListener(this));
		document.observe("start:translation", this.setTranslating.bindAsEventListener(this, true));
		document.observe("finish:translation", this.setTranslating.bindAsEventListener(this, false));
		document.observe("expand:editors", this._expandEditors.bindAsEventListener(this));
		document.observe("reduce:editors", this._reduceEditors.bindAsEventListener(this));
	},

	//* Load Logした後にUI上に反映する
	setValues: function(isThirdLanguageEnabled, languages, tlanslatorIds){

		//* isThirdLanguageEnabled を反映
		if (this.isEnableThirdLanguage() !== isThirdLanguageEnabled) {
			if (BrowserIdentifier.isIE()) {
				if (isThirdLanguageEnabled) {
					this.getSelector().checkThirdLanguageCheckbox();
				}
				else {
					this.getSelector().uncheckThirdLanguageCheckbox();
				}
			}
			EventDispatcher.prototype.click($("check_box"));
		}

		//* languages, tlanslatorIds を反映
		for (var i = 0; i < 3; i++) {
			if (this.getSelector().languageMenuElements[i].value != languages[i]) {
				if (BrowserIdentifier.isFF()) { //* Firefox系はレンダリングで待つ必要
					new Ajax.Request('./php/ajax/document-translation/sleep.php', {
						method: 'post',
						parameters: 'microseconds=10',
						asynchronous: false,
						onComplete: function(){
						}
					});
				}
				//* i 番目の言語を変更する
				this.getSelector().languageMenuElements[i].value = languages[i];
				EventDispatcher.prototype.change(this.getSelector().languageMenuElements[i]);
			}

			//* i 番目の言語を変更する(i=2でない場合)
			if (i != 2 && this.getSelector().translatorMenuElements[i].value != tlanslatorIds[i]) {
				if (BrowserIdentifier.isFF()) { //* Firefox系はレンダリングで待つ必要
					new Ajax.Request('./php/ajax/document-translation/sleep.php', {
						method: 'post',
						parameters: 'microseconds=10',
						asynchronous: false,
						onComplete: function(){
						}
					});
				}
				this.getSelector().translatorMenuElements[i].value = tlanslatorIds[i];
				EventDispatcher.prototype.change(this.getSelector().translatorMenuElements[i]);
			}

		}

		// eip kawauchi add start 20100113
		// load logした後、テキスト内容をクリア
		this.leftEditor.setEditorHTML("");
		this.rightEditor.setEditorHTML("");
		// eip kawauchi add end 20100113

	},

	_initSetting: function(){
		for(var i=1;i<=7;i++){
			this.clearText( i);
			this.clearText( i);
		}
	},

	//* 両エディタを引き伸ばす
	_expandEditors: function(){
		this.leftEditor.expand();
		this.rightEditor.expand();
	},

	//* 両エディタを縮める
	_reduceEditors: function(){
		if(parseInt(this.leftEditor.editor.get('height')) < 100){
			alert("You can not reduce the editor area anymore.");
		}else {
			this.leftEditor.reduce();
			this.rightEditor.reduce();
		}
	},

	getEditors: function(){
		return [this.leftEditor, this.rightEditor];
	},

	//* エディタの番号の配列を返す
	getEditorIndex: function(){
		return this.leftEditor.EDITOR_INDEX_ARRAY;
	},

	//* 言語・翻訳機選択オブジェクトを返す
	getSelector: function(){
		return this.translatorsAndLanguagesSelection;
	},

	getLeftEditor: function(){
		return this.leftEditor;
	},

	getRightEditor: function(){
		return this.rightEditor;
	},

	//* 翻訳中かどうかの状態を制御
	//* isTranslating{boolean} 翻訳中かどうか
	setTranslating: function(ev){
		var isTranslating = $A(arguments)[1];
		this.isInTranslating = isTranslating;
	},

	//* 翻訳中かどうかを返す
	isTranslating: function(){
		return this.isInTranslating;
	},

	//* エディタをpropertyの値(source, target)から得る
	getEditorByProperty: function(prop){
		if (prop == 'source') {
			return this.getLeftEditor();
		}
		else {
			return this.getRightEditor();
		}
	},

	//* 2ホップ翻訳を可能にしたときの操作
	enableThirdLanguage: function(){
		//* 言語選択タブを有効化する
		this.getLeftEditor().enableButton(2);
		this.getRightEditor().enableButton(4);

		//* 右側のエディタの状態の保存・表示の変化
		this.setText(this.getRightEditor().getActiveTabIndex(), this.getRightEditor().getEditorHTML());
		this.getRightEditor().selectButton(4);
		this.getRightEditor().refreshEditorContent();

		//* 言語選択タブを表示
		this.leftEditor.showTab(4);
		this.rightEditor.showTab(4);

		//* 言語選択タブの最高尾タブを変更
		this.leftEditor.changeLastTabIndex(4);
		this.rightEditor.changeLastTabIndex(4);
	},

	//* 2ホップ翻訳を不可能にしたときの操作
	disableThirdLanguage: function(){
		if (this.getLeftEditor().getActiveTabIndex() == 2) { //* 左側のエディタの状態の保存・表示の変化
			this.setText(this.getLeftEditor().getActiveTabIndex(), this.getLeftEditor().getEditorHTML());
			this.getLeftEditor().selectButton(1);
			this.getLeftEditor().refreshEditorContent();
		}
		this.getLeftEditor().disableButton(2); //* 左側のエディタの言語選択タブを無効化する
		if (this.getRightEditor().getActiveTabIndex() == 4) { //* 右側のエディタの状態の保存・表示の変化
			this.setText(this.getRightEditor().getActiveTabIndex(), this.getRightEditor().getEditorHTML());
			this.getRightEditor().selectButton(2);
			this.getRightEditor().refreshEditorContent();
		}
		this.getRightEditor().disableButton(4); //* 右側のエディタの言語選択タブを無効化する

		//* 言語選択タブを非表示
		this.leftEditor.hideTab(4);
		this.rightEditor.hideTab(4);

		//* 言語選択タブの最高尾タブを変更
		this.leftEditor.changeLastTabIndex(2);
		this.rightEditor.changeLastTabIndex(2);
	},

	//* 3言語モードが働いているのかどうか
	isEnableThirdLanguage: function(){
		return this.getSelector().isEnableThirdLanguage();
	},

	//* いずれかのエディタがフォーマット状態にあるかどうかを調べる．
	isFormatting: function(){
		var editors = this.getEditors();
		for (var i = 0; i < editors.size(); i++) {
			if (editors[i].isFormatting()) {
				return true;
			}
		}
		return false;
	},

	//* 翻訳中かどうかを返す
	getHiliteModeEditor: function(){
		var editors = this.getEditors();
		for (var i = 0; i < editors.size(); i++) {
			if (editors[i].isHiliteMode()) {
				return editors[i];
			}
		}
		return null;
	},

	//* アクティブなエディタを得る
	getActiveEditor: function(){
		if (this.activeEditor == null) {
			this.activeEditor = this.leftEditor;
		}
		return this.activeEditor;
	},

	//* アクティブなエディタを設定する
	setActiveEditor: function(editor){
		if(editor instanceof Editor){
			this.getActiveEditor().deactivate();
			this.activeEditor = editor;
			editor.activate();
		}
	},

	//* エディタがアクティブかどうかを調べる
	isActiveEditor: function(editor){
		if (editor instanceof Editor) {
			return editor == this.activeEditor;
		}
		return null;
	},

	//* テキストをクライアント上に保存する
	setText: function(number, text){
		this.clientTextsCache.set(this.CLIENT_CACHE_KEY_TEMPLATE.evaluate({number: number}), text);
	},

	//* クライアント上に保存されたテキストを保存する
	getText: function(number){
		return this.clientTextsCache.get(this.CLIENT_CACHE_KEY_TEMPLATE.evaluate({number: number}));
	},

	//* クライアント上に保存するテキストをクリアする
	clearText: function(number){
		this.setText(number, '');
	},

	//* クライアント上に保存するテキストがあるか調べる
	hasText: function(number){
		return this.getText(number) != '';
	},

	//* データをローカルファイルから読み込む
	//* @param {String} address	ローカルファイルの絶対パス
	load: function(address){
		var fileName = address.substring(address.lastIndexOf('\\')+1 );
		waitWorkspaceFile(temporaryFileNameCode+'/'+fileName);
	},

	setLargeDictionaryId: function(dictId){
		this.largeDictionaryId = dictId;
	},

	//* 翻訳前に行うべき操作を記述
	//* activeEditor{Editor} 左右のアクティブな方のエディタを指定
	_doBeforeTranslate: function(activeEditor){
		this.setText(activeEditor.getActiveTabIndex(), activeEditor.getEditorHTML());
		for(var i=activeEditor.getActiveTabIndex()+1;i<= 7;i++){
			this.clearText(i);
		}
	},

	//* （有効な状態の）翻訳ボタンをクリックしたときに呼び出されるメソッド
	//* 翻訳を開始して良いかのチェックを行った後，翻訳すべき文番号の探索のループメソッドに投げる
	_translate: function(){
		//* 状態チェック（エディタが空でないかどうか？）
		var activeEditor = this.getActiveEditor();
		if (activeEditor.getEditorHTML().match("^(<br>|<BR>|\s)*$")) {
			this.messageManager.addErrorMessage('Please input some text.', true);
			document.fire("finish:translation");
			return;
		}

		//* エディタの中身を整理
		activeEditor.editor._fixNodes();

		//* ユーザ辞書の内容を直列化
		var seriarizedDictionary = userDictionary.seriarizeDictionaryAsJSON();

		var callobj = {
			original: activeEditor.getEditorHTML(),
			largedictionary: this.largeDictionaryId,
			dictionary: seriarizedDictionary,
			number: activeEditor.getActiveTabIndex() - 1
		};

		this._doBeforeTranslate(activeEditor);
		langs = this.getSelector().getLanguages();
		translator_ids = this.getSelector().getTranslatorIds();
		for (var i = 0; i < langs.length; i++) {
			callobj['langs[' + i + ']'] = langs[i];
		}
		for (var i = 0; i < translator_ids.length; i++) {
			callobj['translator_ids[' + i + ']'] = translator_ids[i];
		}
		var formText = $H(callobj).toQueryString();
		var controller = this;

		//* 翻訳バッファの初期設定
		this.translationBuffer.setNewTranslation();
		var editorNumbers;
		if (callobj.number == 0) {
			if (langs.length == 2) {
				editorNumbers = [1, 2];
			}
			else
				if (langs.length == 3) {
					editorNumbers = [1, 2, 4];
				}
		}
		else
			if (callobj.number == 1) {
				if (langs.length == 2) {
					editorNumbers = [2];
				}
				else
					if (langs.length == 3) {
						editorNumbers = [2, 4];
					}
			}
			else
				if (callobj.number == 3) {
					editorNumbers = [4];
				}
		this.translationBuffer.setEditorNumbers(editorNumbers);

		var messageId = this.messageManager.addChangingModeMessage('Formatting data for translation.');
		new Ajax.Request('./php/ajax/document-translation/set-translation-properties.php', {
			method: 'post',
			parameters: formText,
			controller: controller,
			onSuccess: function(httpObj){
				try {
					var responseJSON = httpObj.responseText.evalJSON()
					var checker = new StatusProcessor(responseJSON);
					checker.error = function(){
						controller.messageManager.addErrorMessage('Access limit exceeded.', true);
						if (this.errorMessage != undefined) {
							alert(this.errorMessage);
						}
						else
							if (this.message != undefined) {
								alert(this.message);
							}
							else {
								alert("Playground Error");
							}
						return false;
					}
					if (!checker.check()) {
						return;
					}
					responseJSON = responseJSON.contents;
					var checker = new StatusProcessor(responseJSON);
					if (!checker.check()) {
						return;
					}

					controller._recursiveObservation(callobj.number, messageId, true);
				}
				catch (e) {
					if (e.message.strip() != "Badly formed JSON string: ''") { //* 通信中にF5 (Firefox)
						alert('Playground Server Error.');
						controller.messageManager.addErrorMessage('An error has occured.', true);
					}
					else {
						controller.messageManager.addErrorMessage('Connection was closed.', true);
					}
					controller.messageManager.removeMessage(messageId);
					document.fire("finish:translation");
				}
			},
			onFailure: function(httpObj){
				controller.messageManager.addErrorMessage('An error has occured.', true);
				controller.messageManager.removeMessage(messageId);
				document.fire("finish:translation");
				handleHTTPStatusCode(httpObj);
			},
			onComplete: function(){
			},
			onException: function(){
				document.fire("finish:translation");
			}
		});
	},

	//* _translateメソッドが呼ばれた後に呼ばれるメソッド
	//* 得られた文番号に対して即座に翻訳を発行する
	//* -1が帰ってきた場合は終了フラグを送る
	_recursiveObservation: function(sourceEditorNumber, messageId, isFirst){
		var controller = this;
		new Ajax.Request('./php/ajax/document-translation/original-document-observer.php', {
			method: 'post',
			controller: controller,
			onSuccess: function(httpObj){
				try {
					var responseJSON = httpObj.responseText.evalJSON()
					var checker = new StatusProcessor(responseJSON);
					if (!checker.check()) {
						return;
					}
					responseJSON = responseJSON.contents;

					if (responseJSON.number == -1) {
						//* 全ての文に関して検索が終了したとき
						//* 翻訳バッファに対して終了処理を依頼する
						controller.translationBuffer.setLastFlag();
						controller.translationBuffer.issueWithProcessesLimitCheck();
						controller.messageManager.removeMessage(messageId);
						if (isFirst) {
							document.fire('finish:translation');
						}
					}
					else
						if (responseJSON.number == -2) {
							//* 最大処理時間を超過して一旦処理が戻ってきたとき
							//* 続けてエディタの観察処理行う
							controller._recursiveObservation(sourceEditorNumber, messageId, isFirst);
						}
						else {
							//* 翻訳すべき文番号が得られたとき
							//* 翻訳の発行を翻訳バッファに依頼し，続けてエディタの観察処理行う
							var oRequest = {
								callobj: {
									sentenceNumber: responseJSON.number,
									editorNumber: sourceEditorNumber
								},
								sentence: responseJSON.sentence,
								isBackTranslationRequest: false
							}
							controller.translationBuffer.addRequest(oRequest);
							controller.translationBuffer.issueWithProcessesLimitCheck();
							controller._recursiveObservation(sourceEditorNumber, messageId, false);
						}
				}
				catch (e) {
					if (e.message.strip() != "Badly formed JSON string: ''") { //* 通信中にF5 (Firefox)
						alert('Playground Server Error.');
						controller.messageManager.addErrorMessage('An error has occured.', true);
					}
					else {
						controller.messageManager.addErrorMessage('Connection was closed.', true);
					}
					controller.messageManager.removeMessage(messageId);
					document.fire("finish:translation");
				}
			},
			onFailure: function(httpObj){
				controller.messageManager.addErrorMessage('An error has occured.', true);
				controller.messageManager.removeMessage(messageId);
				handleHTTPStatusCode(httpObj);
				document.fire("finish:translation");
			},
			onComplete: function(){
			}
		});
	},

	//* サーバから原文，翻訳文，折り返し翻訳文の情報を取得してUIに反映するメソッド（ファイルロード時に適用）
	refreshEditorContentsFromServer: function(){
		var controller = this;
		new Ajax.Request('./php/ajax/document-translation/refresh-contents-of-all-editors.php', {
			method: 'post',
			controller: controller,
			onSuccess: function(httpObj){
				try {
					var responseJSON = httpObj.responseText.evalJSON()
					var checker = new StatusProcessor(responseJSON);
					if (!checker.check()) {
						return;
					}

					var translations = responseJSON.contents.translations;
					[1, 2, 4].each(function(n){
						controller.setText(n, translations.shift());
					});

					var backTranslations = responseJSON.contents.backTranslations;
					var editorNumbers = [3, 5, 6];
					for (var i = 0; i < backTranslations.size(); i++) {
						for (var j = 0; j < backTranslations[i].size(); j++) {
							controller.backTranslationManager.setBackTranslation(editorNumbers[i], j+1, backTranslations[i][j]);
						}
					}

					controller.getEditors().each(function(e){
						e.refreshEditorContent();
					})
				}
				catch (e) {
					if (e.message.strip() != "Badly formed JSON string: ''") { //* 通信中にF5 (Firefox)
						alert('Playground Server Error.');
						controller.messageManager.addErrorMessage('An error has occured.', true);
					}
					else {
						controller.messageManager.addErrorMessage('Connection was closed.', true);
					}
				}
			},
			onFailure: function(httpObj){
				controller.messageManager.addErrorMessage('An error has occured.', true);
				handleHTTPStatusCode(httpObj);
			},
			onComplete: function(){
			}
		});
	}
};

//* 翻訳要求は次々と行われるが，翻訳要求の発行速度より翻訳にかかる時間の方が遅いため，要求を蓄える
//* 要求を同時に発行できる数を設定できる（デフォルトは1である）
//* 翻訳結果の出力に関しても管理・制御の役割を果たす
//* メッセージ表示欄に表示するメッセージを送る（表示の仕方はMessageManagerクラスに任せる）
TranslationBuffer.prototype = {
	controller: null, //* DocumentTranslationWorkspace
	leftEditor: null, //* Editor
	rightEditor: null, //* Editor
	messageManager: null, //* MessageManager
	backTranslationManager: null, //* BackTranslationManager

	editorNumbers: null, //* 翻訳先となるエディタ番号
	concurrentProcessesLimit: 1, //* 翻訳における最大の同時処理数．デフォルトは1
	queue: new Array(), //* 翻訳要求のキュー
	issuedNumbers: new Array(), //* 翻訳文の発行番号リスト
	issuingProcessesNumber: 0, //* 発行中の処理の数
	lastRequest: false, //* 最後の要求が発行されたかどうかのフラグ
	startTime: null, //* 翻訳の最初の開始時間を設定

	initialize: function(controller, messageManager, backTranslationManager){
		this.controller = controller;
		this.leftEditor = this.controller.leftEditor;
		this.rightEditor = this.controller.rightEditor;
		this.messageManager = messageManager;
		this.backTranslationManager = backTranslationManager;
	},

	//* 最大の同時処理数を変更
	//* limit{Integer} 新しい同時処理数
	setConcurrentProcessLimit: function(limit){
		this.concurrentProcessesLimit = limit;
	},

	//* 最後の翻訳要求が行われたことを示すフラグを設定する
	setLastFlag: function(){
		this.lastRequest = true;
	},

	//* 翻訳開始時間を記録する
	setNewTranslation: function(){
		this.startTime = new Date();
		this.issuedNumbers.clear();
	},

	//* 翻訳元・先となるエディタ番号の配列を設定する
	//* editorNumbers{Array<Integer>} 翻訳元・先となるエディタ番号の配列
	setEditorNumbers: function(editorNumbers){
		this.editorNumbers = editorNumbers;
	},

	//* バッファに翻訳要求を追加する(通常の翻訳要求を前の方に)
	//* oRequest{Object} 翻訳に必要な情報
	//* oRequest -> callobj{Object}, sentence{String}, isBackTranslationRequest{boolean}
	addRequest: function(oRequest){
		if (oRequest.isBackTranslationRequest) {
			this.queue.push(oRequest);
		}
		else {
			var isInserted = false;
			for (i = 0; i < this.queue.size(); i++) {
				if (this.queue[i].isBackTranslationRequest) {
					this.queue.splice(i, 0, oRequest);
					isInserted = true;
					break;
				}
			}
			if (!isInserted) {
				this.queue.push(oRequest);
			}
		}
	},

	//* 発行最大数を超過していないかを確認しながら翻訳要求を発行する
	//* 発行すべき要求が無いならば，何も呼ばずに終了
	//* 発行すべき要求が無く，かつ最後の要求のフラグが立っていたら，終了処理を行う
	issueWithProcessesLimitCheck: function(){
		if (this.issuingProcessesNumber < this.concurrentProcessesLimit && this.queue.size() > 0) {
			this.issuingProcessesNumber++;
			if (this.queue.first().isBackTranslationRequest) {
				this._issueBacktranslation();
			}
			else {
				this._issue();
			}
		}

		//* 通常翻訳要求がすべてなくなったら終了処理（折り返し翻訳は続ける）
		if (this.lastRequest && (this.queue.size() == 0 || this.queue.first().isBackTranslationRequest)
			&& this._areAllTranslationResponsesReturned()) {
			this.lastRequest = false;
			this._finalize();
		}
	},

	//* 発行した翻訳命令に対するレスポンスがすべて返ってきたか
	//* @return{boolean}
	_areAllTranslationResponsesReturned: function(){
		var isAllReturned = true;
		for(var i=0;i<this.issuedNumbers.length;i++){
			if(!this.issuedNumbers[i].returned){
				isAllReturned = false;
				break;
			}
		}
		return isAllReturned;
	},

	//* 翻訳中に割り込みが起こった場合の処理
	//* 発行待ちのリクエストキューを破棄し，現在の翻訳の終了まで待つ
	interrupt: function(){
		//* TODO
	},

	//* 翻訳文を取得してから表示用に処理するメソッド
	//* translations{Array<String>} 翻訳文の配列
	//* sentenceNumber{Integer} 文番号
	//* startEditorNumber{Integer} 翻訳開始エディタ番号(1|2|4)
	//* isFinale{boolean} _finalize処理において呼び出されたかどうか
	_setTranslation: function(translations, sentenceNumber, startEditorNumber, isFinale){
		if (startEditorNumber == 4) {
			return;
		}
		else {
			if (!isFinale) {
				//* 発行番号のオブジェクトリストの文番号が一致する要素が返ってきたフラグを立てる
				this.issuedNumbers.find(function(o){
					return o.sentenceNumber == sentenceNumber;
				}).returned = true;

				//* trueが先頭から連続しているかどうかを調べる
				//* 連続していないならば，結果の表示は行わずに終了
				var isContinuousTrueValues = true;
				for (var i = 0; i < this.issuedNumbers.size(); i++) {
					if (this.issuedNumbers[i].returned == true) {
						if (isContinuousTrueValues == false) {
							return; //* 終了
						}
					}
					else {
						isContinuousTrueValues = false;
					}
				}
			}

			//* 翻訳結果を蓄積しておく
			if (startEditorNumber == 2) {
				this.controller.setText(4, translations.shift());
			}
			else if (startEditorNumber == 1) {
				this.controller.setText(2, translations.shift());
				if (translations.size() != 0) {
					this.controller.setText(4, translations.shift());
				}
			}

			//* エディタの表示の更新
			this.leftEditor.refreshEditorContent();
			this.rightEditor.refreshEditorContent();
		}
	},

	//* 折り返し翻訳文を取得してから表示用に処理するメソッド
	//* backTranslations{Array<String>} 折り返し翻訳文の配列
	//* sentenceNumber{Integer} 文番号
	//* startEditorNumber{Integer} 翻訳開始エディタ番号
	_setBackTranslation: function(backTranslations, sentenceNumber, startEditorNumber){
		if(startEditorNumber == 1 || startEditorNumber == 2){
			this.backTranslationManager.setBackTranslation(3, sentenceNumber, backTranslations.shift());
			if(backTranslations.size() != 0){
				this.backTranslationManager.setBackTranslation(5, sentenceNumber, backTranslations.shift());
				this.backTranslationManager.setBackTranslation(6, sentenceNumber, backTranslations.shift());
			}
		} else if (startEditorNumber == 4){
			this.backTranslationManager.setBackTranslation(5, sentenceNumber, backTranslations.shift());
			this.backTranslationManager.setBackTranslation(6, sentenceNumber, backTranslations.shift());
		}
	},

	//* 先頭の要求を発行する
	//* 翻訳が終わった後新しい翻訳要求を発行
	_issue: function(){
		var queueItem = this.queue.shift();
		var callobj = queueItem.callobj;
		var formText = $H(callobj).toQueryString();

		//* 翻訳結果表示制御用のオブジェクトの挿入
		this.issuedNumbers.push({sentenceNumber: callobj.sentenceNumber, returned: false});

		var buffer = this;
		var translationMessageId = this.messageManager.addTranslationMessage(callobj.sentenceNumber, queueItem.sentence);
		new Ajax.Request('./php/ajax/document-translation/sentence-translator.php', {
			method: 'post',
			parameters: formText,
			buffer: buffer,
			onSuccess: function(httpObj){
				try {
					buffer.issuingProcessesNumber--;
					var responseJSON = httpObj.responseText.evalJSON()
					var checker = new StatusProcessor(responseJSON);
					if (!checker.check()) {
						return;
					}

					resultSentences = responseJSON.contents;
					buffer.messageManager.removeMessage(translationMessageId);
					var oRequest = {
						callobj: callobj,
						isBackTranslationRequest: true
					}
					buffer.addRequest(oRequest);
					buffer.issueWithProcessesLimitCheck();
					buffer._setTranslation(resultSentences, callobj.sentenceNumber, callobj.editorNumber+1);
				}
				catch (e) {
					if (e.message.strip() != "Badly formed JSON string: ''") { //* 通信中にF5 (Firefox)
						alert('Playground Server Error.');
						buffer.messageManager.addErrorMessage('An error has occured.',true);
					} else {
						buffer.messageManager.addErrorMessage('Connection was closed.',true);
					}
					document.fire("finish:translation");
				}
				//* ライセンス情報を表示
				buffer.messageManager.removeMessage(translationMessageId);
				serviceInformation.update(responseJSON.profile);
			},
			onFailure: function(httpObj){
				buffer.messageManager.addErrorMessage('An error has occured.',true);
				buffer.messageManager.removeMessage(translationMessageId);
				handleHTTPStatusCode(httpObj);
				document.fire("finish:translation");
			},
			onComplete: function(){
			}
		});
	},

	//* 折り返し翻訳を発行
	_issueBacktranslation: function(){
		var queueItem = this.queue.shift();
		var callobj = queueItem.callobj;
		var formText = $H(callobj).toQueryString();
		var buffer = this;
		new Ajax.Request('./php/ajax/document-translation/sentence-back-translator.php', {
			method: 'post',
			parameters: formText,
			buffer: buffer,
			onSuccess: function(httpObj){
				try {
					buffer.issuingProcessesNumber--;
					var responseJSON = httpObj.responseText.evalJSON()
					var checker = new StatusProcessor(responseJSON);
					if (!checker.check()) {
						return;
					}
					buffer.issueWithProcessesLimitCheck();
					backTranslations = responseJSON.contents;
					buffer._setBackTranslation(backTranslations, callobj.sentenceNumber + 1, callobj.editorNumber + 1);
					buffer.backTranslationManager.refreshBackTranslation();
				}
				catch (e) {
					if (e.message.strip() != "Badly formed JSON string: ''") { //* 通信中にF5 (Firefox)
						alert('Playground Server Error.');
						buffer.messageManager.addErrorMessage('An error has occured.', true);
					}
					else {
						buffer.messageManager.addErrorMessage('Connection was closed.', true);
					}
					document.fire('finish:translation');
				}
				//* ライセンス情報を表示
				serviceInformation.update(responseJSON.profile);
			},
			onFailure: function(httpObj){
				buffer.messageManager.addErrorMessage('An error has occured.');
				handleHTTPStatusCode(httpObj);
			},
			onComplete: function(){
			}
		});
	},

	//* 終了処理を行う
	//* 翻訳にかかった時間を表示するメッセージを投げる
	_finalize: function(){
		var startEditorNumber = this.editorNumbers.first();
		var targetEditorNumbers = this.editorNumbers;
		//* サーバ処理用にインデックスをひとつ減らしておく
		targetEditorNumbers = targetEditorNumbers.map(function(n){
			return n - 1;
		});
		var callobj = {};
		for (var i = 0; i < targetEditorNumbers.length; i++) {
			callobj['numbers[' + i + ']'] = targetEditorNumbers[i];
		}
		var formText = $H(callobj).toQueryString();
		var buffer = this;
		var messageId = this.messageManager.addChangingModeMessage('Now terminating translation processes.');
		new Ajax.Request('./php/ajax/document-translation/refresh-editors.php', {
			method: 'post',
			parameters: formText,
			buffer: buffer,
			onSuccess: function(httpObj){
				try {
					var responseJSON = httpObj.responseText.evalJSON()
					var checker = new StatusProcessor(responseJSON);
					if (!checker.check()) {
						return;
					}
					translations = responseJSON.contents;
					buffer.controller.setText(startEditorNumber, translations.shift()); //* 原文(spanタグ付)を設定
					buffer._setTranslation(translations, 0, startEditorNumber, true);

					//* 経過時間を出力
					var executionTime = (new Date() - buffer.startTime) / 1000;
					buffer.messageManager.addNormalNoticeMessage('Execution Time: ' + executionTime + ' sec.', true);
				}
				catch (e) {
					if (e.message.strip() != "Badly formed JSON string: ''") { //* 通信中にF5 (Firefox)
						alert('Playground Server Error.');
						buffer.messageManager.addErrorMessage('An error has occured.', true);
					}
					else {
						buffer.messageManager.addErrorMessage('Connection was closed.', true);
					}
				}
				//* ライセンス情報を表示
				serviceInformation.update(responseJSON.profile);
			},
			onFailure: function(httpObj){
				buffer.messageManager.addErrorMessage('An error has occured.', true);
				handleHTTPStatusCode(httpObj);
			},
			onComplete: function(){
				buffer.messageManager.removeMessage(messageId);
				document.fire("finish:translation");
			}
		});
	}
}

//* ユーザが操作するボタンによる挙動を管理するクラス
//* カスタムイベントを投げる
ButtonManager.prototype = {
	DISABLE_TRANSLATION_BUTTON_ID: 'disable-translation-button', //* 翻訳ボタンがクリックできない状態のボタンのID
	DISABLE_SAVE_LOG_BUTTON_ID: 'disable-save-log-button', //* Save Logボタンがクリックできない状態のボタンのID
	DISABLE_LOAD_LOG_BUTTON_ID: 'disable-load-log-button', //* Load Logボタンがクリックできない状態のボタンのID

	DISABLE_BUTTON_ON_CLICK_ACTION: "if(window.confirm('Translation process may be active now. Would you like to start another translation process?')){document.fire('finish:translation');}",

	translateButtonElement: null,
	saveLogButtonElement: null,
	loadLogButtonElement: null,
	editorExpansionButtonElement: null,
	editorReductionButtonElement: null,
	cancelTranslationButtonElement: null,

	initialize: function(properties){
		this.editorExpansionButtonElement = $(properties.editorsExpansionButtonId);
		this.editorReductionButtonElement = $(properties.editorsReductionButtonId);
		this.cancelTranslationButtonElement = $(properties.cancelTranslationButtonId);
		this.translateButtonElement = $(properties.translateButtonId);
		this.saveLogButtonElement = $(properties.saveLogButtonId);
		this.loadLogButtonElement = $(properties.loadLogButtonId);
		this._initEvent();
	},

	//* イベントを初期化する
	_initEvent: function(){
		var manager = this;
		this.translateButtonElement.observe('click', function(ev){
			manager._disableAjaxButtons();
			document.fire("start:translation");
		});
		this.editorExpansionButtonElement.observe('click', function(ev){
			document.fire("expand:editors");
		});
		this.editorReductionButtonElement.observe('click', function(ev){
			document.fire("reduce:editors");
		});
		document.observe('finish:translation',this._enableAjaxButtons.bindAsEventListener(this));
		document.observe('start:formatting',this._disableAjaxButtons.bindAsEventListener(this));
		document.observe('finish:formatting',this._enableAjaxButtons.bindAsEventListener(this));
	},

	//* 翻訳ボタン，セーブボタン，ロードボタンを無効化する
	_disableAjaxButtons: function(){
		this.translateButtonElement.hide();
		this.saveLogButtonElement.hide();
		this.loadLogButtonElement.hide();
		if (!$(this.DISABLE_TRANSLATION_BUTTON_ID)) {
			new Insertion.After(this.translateButtonElement, '<input type="button" id="' + this.DISABLE_TRANSLATION_BUTTON_ID + '" value="Translate" class="button-blue inactive" onclick="' + this.DISABLE_BUTTON_ON_CLICK_ACTION + '" />');
		}
		else {
			$(this.DISABLE_TRANSLATION_BUTTON_ID).show();
		}

		if (!$(this.DISABLE_SAVE_LOG_BUTTON_ID)) {
			new Insertion.After(this.saveLogButtonElement, '<input type="button" id="' + this.DISABLE_SAVE_LOG_BUTTON_ID + '" value="Save Log" class="button-green inactive" style="width:120px;" onclick="' + this.DISABLE_BUTTON_ON_CLICK_ACTION + '" />');
		}
		else {
			$(this.DISABLE_SAVE_LOG_BUTTON_ID).show();
		}

		if (!$(this.DISABLE_LOAD_LOG_BUTTON_ID)) {
			new Insertion.After(this.loadLogButtonElement, '<input type="button" id="' + this.DISABLE_LOAD_LOG_BUTTON_ID + '" value="Load Log" class="button-green inactive" style="width:120px;" onclick="' + this.DISABLE_BUTTON_ON_CLICK_ACTION + '" />');
		}
		else {
			$(this.DISABLE_LOAD_LOG_BUTTON_ID).show();
		}
	},

	//* 翻訳ボタン，セーブボタン，ロードボタンを有効化する
	_enableAjaxButtons: function(){
		if ($(this.DISABLE_TRANSLATION_BUTTON_ID)) {
			$(this.DISABLE_TRANSLATION_BUTTON_ID).hide();
		}
		if ($(this.DISABLE_SAVE_LOG_BUTTON_ID)) {
			$(this.DISABLE_SAVE_LOG_BUTTON_ID).hide();
		}
		if ($(this.DISABLE_LOAD_LOG_BUTTON_ID)) {
			$(this.DISABLE_LOAD_LOG_BUTTON_ID).hide();
		}
		this.translateButtonElement.show();
		this.saveLogButtonElement.show();
		this.loadLogButtonElement.show();
	}
}

//* ユーザに見えるメッセージを管理するクラス
//* 各クラスからメッセージを適当に投げる
//* それを制御して適切なメッセージを表示する
MessageManager.prototype = {
	ERROR_PRIORITY:			1,
	WARNING_PRIORITY: 		2,
	TRANSLATION_PRIORITY: 	3,
	CHANGING_MODE_PRIORITY: 4,
	NORMAL_NOTICE_PRIORITY: 5,

	AUTO_DELETE_TIME: 5,
	MAX_STRING_LENGTH: 80,

	TRANSLATING_IMAGE: 'img/document-translation/translation.gif',
	CONNECTING_IMAGE: 'img/anime/ajax-loader3.gif',

	controller: null, //* DocumentTranslationWorkspace
	queue: new Array(), //* メッセージのキュー．メッセージは優先度順に並んでいる．
	messagePane: null, //* メッセージを表示するDOM
	messageIdIndex: 0, //* 一意に出力するためのメッセージID
	sourceLanguage: null, //* 翻訳開始言語

	initialize: function(messagePaneId, controller){
		this.controller = controller;
		this.messagePane = $(messagePaneId);
	},

	//* 翻訳開始言語を設定する
	setSourceLanguage: function(){
		var activeTabIndex = this.controller.getActiveEditor().getActiveTabIndex();
		var languages = this.controller.getSelector().getLanguages();
		activeTabIndex -= 1;
		if (activeTabIndex == 3) {
			activeTabIndex = 2;
		}
		this.sourceLanguage = languages[activeTabIndex];
		this._show();
	},

	//* 翻訳中の文番号などを表示するメッセージ
	//* number{Integer} 文番号
	//* sentence{String} 原文
	//* @return{Integer} 生成された文のID
	addTranslationMessage: function(number, sentence){
		message = 'Translating the '+getOrdinalNumber(number+1)+' sentence'
			+' "<u>'+ sentence.truncate(20).escapeHTML()+ '</u>"';
		option = {sentenceNumber: number, sentence: sentence};
		return this.addMessage(message, this.TRANSLATION_PRIORITY, option);
	},

	//* 通常のシステムメッセージ
	//* message{String} メッセージ文
	//* autoDelete{boolean} trueの場合AUTO_DELETE_TIME秒後メッセージ自動削除を行う
	//* @return{Integer} 生成された文のID
	addNormalNoticeMessage: function(message, autoDelete){
		var id = this.addMessage(message, this.NORMAL_NOTICE_PRIORITY);
		if (autoDelete) {
			this._setAutoDelete(id);
		}
		return id;
	},

	//* システムエラー時のメッセージ等
	//* message{String} メッセージ文
	//* autoDelete{boolean} trueの場合AUTO_DELETE_TIME秒後メッセージ自動削除を行う
	//* @return{Integer} 生成された文のID
	addErrorMessage: function(message, autoDelete){
		var id = this.addMessage(message, this.ERROR_PRIORITY);
		if (autoDelete) {
			this._setAutoDelete(id);
		}
		return id;
	},

	//* 翻訳ボタンをクリックしたときに反映されるメッセージ等
	//* message{String} メッセージ文
	//* @return{Integer} 生成された文のID
	addChangingModeMessage: function(message){
		return this.addMessage(message, this.CHANGING_MODE_PRIORITY);
	},

	//* 警告時のメッセージ等
	//* message{String} メッセージ文
	//* @return{Integer} 生成された文のID
	addWarningMessage: function(message){
		return this.addMessage(message, this.WARNING_PRIORITY);
	},

	//* メッセージIDを返す
	//* message{String} 表示メッセージ
	//* priority{Integer} 優先度
	//* oOption{Object} メッセージへのオプション
	addMessage: function(message, priority, oOption){
		var id = this._generateId();
		var obj = {id:id, message:message, priority:priority, option: oOption};

		//* priority順にqueueにobjを挿入する
		var isInserted = false;
		for(i=0;i<this.queue.size();i++){
			if(this.queue[i].priority >= obj.priority){
				this.queue.splice(i,0,obj);
				isInserted = true;
				break;
			}
		}
		if (!isInserted) {
			this.queue.push(obj);
		}

		this._show();
		return id;
	},

	//* メッセージIDに該当するメッセージを削除する
	//* id{Integer} 削除する文のID
	removeMessage: function(id){
		var message = this.queue.find(function(o){
			return o.id == id;
		});
		this.queue = this.queue.without(message);
		this._show();
	},

	//* メッセージの自動消去設定を行うプライベートメソッド
	//* id{Integer} 自動削除する文のID
	//* time{Integer} 自動消去にかかる時間(秒)
	_setAutoDelete: function(id, time){
		if (!time) {
			time = this.AUTO_DELETE_TIME;
		}
		var messageManager = this;
		new PeriodicalExecuter(function(pe) {
			messageManager.removeMessage(id);
			pe.stop();
		}, time);
	},

	//* queueの情報からメッセージ情報を出力
	//* 表示の仕方はこの関数が調整する
	_show: function(){
		var first = this.queue.first();
		if (first && first.priority == this.ERROR_PRIORITY) {
			this._showMessage(first.message);
		}
		else if (first && first.priority == this.WARNING_PRIORITY) {
			this._showMessage(first.message);
		}
		else if (first && first.priority == this.TRANSLATION_PRIORITY) {
			translationMessageObjs = new Array(first);
			for (i = 1; i < this.queue.size(); i++) {
				if (this.queue[i].priority != this.TRANSLATION_PRIORITY) {
					break;
				}
				translationMessageObjs.push(this.queue[i]);
			}
			if (translationMessageObjs.size() == 1) {
				this._showMessage(first.message, this.TRANSLATING_IMAGE);
			}
			else {
				translationNumbers = translationMessageObjs.map(function(o){
					return o.option.sentenceNumber;
				}).sort();
				message = 'Translating ';
				while ((val = translationNumbers.shift()) != null) {
					if (translationNumbers.size() > 1) {
						message += getOrdinalNumber(val + 1) + ', ';
					}
					else
						if (translationNumbers.size() == 1) {
							message += getOrdinalNumber(val + 1) + ' and ';
						}
						else
							if (translationNumbers.size() == 0) {
								message += getOrdinalNumber(val + 1);
							}
				}
				message += ' sentences';
				this._showMessage(message, this.TRANSLATING_IMAGE);
			}
		}
		else if (first && first.priority == this.CHANGING_MODE_PRIORITY) {
			this._showMessage(first.message, this.CONNECTING_IMAGE);
		}
		else if (first && first.priority == this.NORMAL_NOTICE_PRIORITY) {
			this._showMessage(first.message);
		}
		else {
			message = '(Source Language: ' + Language.getNameByTag(this.sourceLanguage) + ') System messages are shown here.'
			this._showMessage(message);
		}
	},

	//* 実際のDOMにメッセージを記述するメソッド
	//* message{String} システムのメッセージ
	//* imageURL{String} 表示する画像のURL
	//* length{Integer} 文字列の最大文字数（デフォルトはMAX_STRING_LENGTH）
	_showMessage: function(message, imageURL, length){
		var text = '';
		if (imageURL) {
			text += '<img src="' + imageURL + '"> ';
		}
		if (!length) {
			length = this.MAX_STRING_LENGTH;
		}
		text += message.truncate(length);
		this.messagePane.innerHTML = text;
	},

	//* メッセージIDを生成する
	_generateId: function(){
		this.messageIdIndex++;
		return this.messageIdIndex;
	}
}

//* 折り返し翻訳文の情報の蓄積・表示の制御を行うクラス
BackTranslationManager.prototype = {
	MAKE_VISIBLE_MESSAGE: '<span onclick="document.fire(\'enable:backtranslation\')" class="like-link">make a back-translation visible</span>',
	MAKE_INVISIBLE_MESSAGE: '&nbsp;&nbsp;&nbsp;<span onclick="document.fire(\'disable:backtranslation\')" class="like-link">make a back-translation invisible</span>',
	NOW_TRANSLATING_MESSAGE: '<img src="img/document-translation/waiting.gif" height="16" width="16"> Now translating',
	UNSUPPORTED_BACK_TRANSLATION: '[[:unsupported]]',
	ERROR_BACK_TRANSLATION: '[[:error]]',

	controller: null, //* DocumentTranslationWorkspace
	leftEditor: null,
	rightEditor: null,

	backtranslationAreaElement: null,
	backtranslationHeadElement: null,
	backTranslationCache: new Array(), //* 折り返し翻訳文の情報を保持するための2次元配列
	defaultBackTranslationText: null,

	isEnabled: true, //* 折り返し翻訳表示モードがOnであるかどうか
	sentenceNumber: -1,

	initialize: function(controller, properties){
		this.controller = controller;
		this.backtranslationAreaElement = $(properties.backtranslationAreaId);
		this.backtranslationHeadElement = $(properties.backtranslationHeadId);
		this.defaultBackTranslationText = this.backtranslationAreaElement.innerHTML;
		this._initEvent();
	},

	//* BackTranslationManagerに関するイベントを初期化する
	_initEvent: function(){
		var disableFunc = function(){
			this.disable();
			this.setSentenceNumber(-1);
			this.removeHilite();
			this.showDefaultMessage();
		}
		var enableFunc = function(){
			this.enable();
			this.showDefaultMessage();
		}
		var startTranslationFunc = function(){
			this.setSentenceNumber(-1);
			this.removeHilite();
			this.showDefaultMessage();
		}
		document.observe('enable:backtranslation',enableFunc.bindAsEventListener(this));
		document.observe('disable:backtranslation',disableFunc.bindAsEventListener(this));
		document.observe('start:translation', startTranslationFunc.bindAsEventListener(this));
	},

	//* 初期のエディタの設定を行う
	setEditors: function(leftEditor, rightEditor){
		this.leftEditor = leftEditor;
		this.rightEditor = rightEditor;
	},

	//* 折り返し翻訳表示モードをOnにする
	enable: function(){
		this.isEnabled = true;
	},

	//* 折り返し翻訳表示モードをOffにする
	disable: function(){
		this.isEnabled = false;
	},

	//* エディタ上のハイライトを消去する
	removeHilite: function(){
		[1, 2, 4].each(function(n){
			var text = this.controller.getText(n);
			text = text.replace(/\s*class="highlight"\s*/i, ""); //* ある文にかかっているハイライトクラスを消去
			this.controller.setText(n, text);
		}, this);

		this._removeHiliteEditor(this.leftEditor.editor);
		this._removeHiliteEditor(this.rightEditor.editor);
	},

	//* カーソルが乗っている文のIDを得て，それに対応する文のハイライト(クラス設定)，及び折り返し翻訳文の表示を行う
	//* spanNodeId{String} ノードのID(sentence-<editor_number>-<sentence_number>), その上に乗ってない場合はnull
	notify: function(spanNodeId){
		if(spanNodeId == null) {
			this.removeHilite();
			this.showDefaultMessage();
			return;
		}
		if (!this.isEnabled) {
			return;
		}
		if (this.controller.isTranslating()) {
			return;
		}
		var sentenceNumber = spanNodeId.match(/\d+/g)[1];
		this.setSentenceNumber(sentenceNumber);

		//* エディタの中身の更新をする
		[1, 2, 4].each(function(n){
			var text = this.controller.getText(n);
			text = text.replace(/\s*class="highlight"\s*/i, ""); //* ある文にかかっているハイライトクラスを消去
			var regexp = new RegExp('<span\\s+[^<>]*id="sentence-\\d-'+sentenceNumber+'"[^<>]*>','i');
			text = text.replace(regexp, '<span id="sentence-'+n+'-'+sentenceNumber+'" class="highlight">');
			this.controller.setText(n, text);
		}, this);

		this._hiliteEditor(this.leftEditor.editor);
		this._hiliteEditor(this.rightEditor.editor);
		this._scrollToHilitedElements();
		this.refreshBackTranslation();
	},

	//* エディタのiframe内の要素を直接書き換えてハイライトさせる
	//* yEditor{YAHOO.widget.Editor}
	_hiliteEditor: function(yEditor){
		var spans = yEditor._getDoc().getElementsByTagName('span');
		for(var i=spans.length-1;i>=0;i--){ //* 余分なspanタグは
			if(!spans[i].id || spans[i].id.search(/sentence-\d+-\d+/i) == -1){
				spans.splice(i,1);
			}
		}
		var regexp = new RegExp('sentence-\\d-'+this.getSentenceNumber(),'i');
		for(var i = 0;i<spans.length;i++){
			if(spans[i].className && spans[i].className.indexOf('highlight') != -1) spans[i].className = '';
			if(spans[i].id && spans[i].id.match(regexp)) spans[i].className = 'highlight';
		}
	},

	//* エディタのiframe内の要素を直接書き換えてハイライトを削除する
	//* yEditor{YAHOO.widget.Editor}
	_removeHiliteEditor: function(yEditor){
		var spans = yEditor._getDoc().getElementsByTagName('span');
		for(var i=spans.length-1;i>=0;i--){ //* 余分なspanタグは
			if(!spans[i].id || spans[i].id.search(/sentence-\d+-\d+/i) == -1){
				spans.splice(i,1);
			}
		}
		for(var i = 0;i<spans.length;i++){
			if (spans[i].className && spans[i].className.indexOf('highlight') != -1) {
				spans[i].className = '';
			}
		}
	},

	//* デフォルトのメッセージを表示する
	showDefaultMessage: function(){
		if (this.isEnabled) {
			this.backtranslationAreaElement.innerHTML = this.defaultBackTranslationText;
		}
		else {
			this.backtranslationAreaElement.innerHTML = this.MAKE_VISIBLE_MESSAGE;
		}
	},

	//* 表示する折り返し翻訳の番号を設定する
	//* sentenceNumber{Integer} 文番号
	setSentenceNumber: function(sentenceNumber){
		this.sentenceNumber = sentenceNumber;
	},

	//* 表示する折り返し翻訳の番号を得る
	getSentenceNumber: function(){
		return this.sentenceNumber;
	},

	//* エディタ番号，文番号を指定して，折り返し翻訳文を設定する
	//* editorNumber{Integer} エディタ番号
	//* sentenceNumber{Integer} 文番号
	//* sentence{String} 折り返し翻訳文
	setBackTranslation: function(editorNumber, sentenceNumber, sentence){
		if(this.backTranslationCache.size() == 0){ //* キャッシュの初期化
			this.backTranslationCache.push({
				editorNumber: 3,
				contents: new Array()
			});
			this.backTranslationCache.push({
				editorNumber: 5,
				contents: new Array()
			});
			this.backTranslationCache.push({
				editorNumber: 6,
				contents: new Array()
			});
		}
		for(i=0;i<this.backTranslationCache.size();i++){
			if(this.backTranslationCache[i].editorNumber == editorNumber){
				var isSetSentence = false;
				for(j=0;j<this.backTranslationCache[i].contents.size();j++){
					if(this.backTranslationCache[i].contents[j].sentenceNumber == sentenceNumber){
						this.backTranslationCache[i].contents[j].sentence = sentence;
						isSetSentence = true;
						break;
					}
				}
				if (!isSetSentence) {
					this.backTranslationCache[i].contents.push({
						sentenceNumber: sentenceNumber,
						sentence: sentence
					})
				}
				break;
			}
		}
	},

	//* エディタ番号，文番号を指定して，折り返し翻訳文を取得する
	//* editorNumber{Integer} エディタ番号
	//* sentenceNumber{Integer} 文番号
	//* @return{String} 折り返し翻訳文
	getBackTranslation: function(editorNumber, sentenceNumber){
		if (this.backTranslationCache.size() == 0) {
			return null;
		}
		var oEditorContent = this.backTranslationCache.find(function(o){
			return o.editorNumber == editorNumber;
		});
		var oSentenceContent = oEditorContent.contents.find(function(o){
			return o.sentenceNumber == sentenceNumber;
		});
		if(oSentenceContent) return oSentenceContent.sentence;
		else return null;
	},

	//* エディタ番号，文番号を指定して，折り返し翻訳文を消去する(nullを代入する)
	//* editorNumber{Integer} エディタ番号
	//* sentenceNumber{Integer} 文番号
	deleteBackTranslation: function(editorNumber, sentenceNumber){
		this.setBackTranslation(editorNumber, sentenceNumber, null);
	},

	//* 折り返し翻訳の言語のパスを取得
	_backTranslationPath: function(){
		langs = this.controller.getSelector().getLanguages();
		var rightIndex = this.rightEditor.getActiveTabIndex();
		var leftIndex = this.leftEditor.getActiveTabIndex();
		if (rightIndex == 2 && leftIndex == 1) {
			return "(" + Language.getNameByTag(langs[1]) + " --> " + Language.getNameByTag(langs[0]) + ")";
		}
		else if (rightIndex == 4 && leftIndex == 1) {
			return "(" + Language.getNameByTag(langs[2]) + " --> " + Language.getNameByTag(langs[1]) + " --> " + Language.getNameByTag(langs[0]) + ")";
		}
		else if (rightIndex == 4 && leftIndex == 2) {
			return "(" + Language.getNameByTag(langs[2]) + " --> " + Language.getNameByTag(langs[1]) + ")";
		}
	},

	//* 折り返し翻訳の言語のパスを表示
	//* 言語の変更，エディタのタブクリックに従って反映
	setBacktranslationPath: function(){
		this.backtranslationHeadElement.innerHTML = this._backTranslationPath();
	},

	//* 折り返し翻訳を適切に表示する
	refreshBackTranslation: function(){
		if(this.isEnabled && this.getSentenceNumber() != -1 && !this.controller.isTranslating()){
			var rightIndex = this.rightEditor.getActiveTabIndex();
			var leftIndex = this.leftEditor.getActiveTabIndex();
			var backTranslationEditorNumber;
			if (rightIndex == 2 && leftIndex == 1) {
				backTranslationEditorNumber = 3;
			}
			else if (rightIndex == 4 && leftIndex == 1) {
				backTranslationEditorNumber = 6;
			}
			else if (rightIndex == 4 && leftIndex == 2) {
				backTranslationEditorNumber = 5;
			}
			var backTranslation = this.getBackTranslation(backTranslationEditorNumber, this.getSentenceNumber());
			if (backTranslation == this.UNSUPPORTED_BACK_TRANSLATION) {
				this.backtranslationAreaElement.dir = 'ltr';
				this.backtranslationAreaElement.innerHTML = 'Translation Path ' + this._backTranslationPath() + ' is not supported.';
			}
			else if (backTranslation == this.ERROR_BACK_TRANSLATION) {
				this.backtranslationAreaElement.dir = 'ltr';
				this.backtranslationAreaElement.innerHTML = 'Translation error has happened. (Please retry translation)';
			}
			else if (backTranslation != null) {
				var sourceLanguageTag = this.controller.getSelector().getLanguages()[leftIndex - 1];
				var direction = Language.getTextDirection(sourceLanguageTag);
				this.backtranslationAreaElement.dir = direction;
				if(direction == 'ltr'){
					this.backtranslationAreaElement.setStyle({
						textAlign: 'left'
					});
					this.backtranslationAreaElement.innerHTML = backTranslation + this.MAKE_INVISIBLE_MESSAGE;
				} else {
					this.backtranslationAreaElement.setStyle({
						textAlign: 'right'
					});
					this.backtranslationAreaElement.innerHTML = backTranslation + '<font dir="ltr">' + this.MAKE_INVISIBLE_MESSAGE + '</font>';
				}
			}
			else {
				this.backtranslationAreaElement.dir = 'ltr';
				this.backtranslationAreaElement.innerHTML = this.NOW_TRANSLATING_MESSAGE;
			}
		}
	},

	//* 折り返し翻訳表示モードの時に，適切にハイライトの位置に移動するメソッド
	_scrollToHilitedElements: function(){
		var editor = this.controller.getActiveEditor().totherEditor();
		var yEditor = editor.getYahooEditor();
		var spans = yEditor._getDoc().getElementsByTagName('span');
		for(var i = 0;i<spans.length;i++){
			if (spans[i].className && spans[i].className.indexOf('highlight') != -1) {
				hiliteElement = spans[i];
				break;
			}
		}
		if(!hiliteElement){
			return;
		}

		var element = null;
		if (editor.property == 'source') {
			element = $('original-textarea_container').select('div.yui-editor-editable-container')[0];
		}
		else {
			element = $('translation-textarea_container').select('div.yui-editor-editable-container')[0];
		}
		var scrollY = Element.cumulativeOffset(hiliteElement)[1] - Math.round(element.getHeight()*0.4);
		if (scrollY < 0) {
			scrollY = 0;
		}

		yEditor._getWindow().scrollTo(0,scrollY);
	}
}

Editor.prototype = {
	BUTTON_ID_TEMPLATE: new Template('#{editorId}-button-#{number}'),
	EDITOR_INDEX_ARRAY: [1, 2, 4],
	BIG_CHANGE_THRESHOLD: 150,

	id: null, //* String
	editor: null, //* YAHOO.widget.Editor
	tabNumber: null, //* integer
	messageManager: null, //* MessageManager
	property: null,
	textSelectMode: false,
	eventConstant: new Hash(),
	isInFormatting: false, //* フォーマット待ちかどうか
	pos: null, //* フォーカス時のスクロール位置を保持しておくメンバ
	controller: null,
	backTranslationManager: null, //* BackTranslationManager

	initialize: function(id, property, controller, messageManager, backTranslationManager){
		this.id = id;
		this.controller = controller;
		this.messageManager = messageManager;
		this.backTranslationManager = backTranslationManager;
		this.property = property;

		if (property == 'source') {
			disabledList = [false, false, true];
		}
		else if (property == 'target') {
			disabledList = [true, false, false];
		}

		this._initEditor(disabledList);
	},

	//* エディタの初期化
	_initEditor: function(disabledList){
		this.editor = new YAHOOEditorForDocumentTranslation(this.id);
		//* li, ol, ulやdt, dl, dd のようなリストや, table, tr, th, td, captionのようなテーブルは許可
		//* span, font はレンダリングのために許容
		var editorContainor = this;
		this.editor.on('toolbarLoaded', function(){
			var myEditor = editorContainor.editor;
			disabledList.each(function(el, i){
				if (i == 2) {
					index = 4;
				}
				else {
					index = i + 1;
				}

				var tabConfig = {
					type: 'button',
					label: '',
					value: 'showeditor' + index,
					disabled: el,
					id: editorContainor.BUTTON_ID_TEMPLATE.evaluate({
						editorId: editorContainor.id,
						number: index
					})
				};

				myEditor.toolbar.addButtonToGroup(tabConfig, 'textstyle');

				if (!el && editorContainor.tabNumber == null) {
					editorContainor.tabNumber = index;
				}
				myEditor.toolbar.on('showeditor' + index + 'Click', editorContainor._onTabClick.bindAsEventListener(editorContainor, index), myEditor, true);
			})
		});

		//* エディタ内の文要素にカーソルが乗ったときのアクションのハンドラ
		this.editor.on('afterNodeChange', function(){
			this._cursorOnSentenceAction();
		}, this, true);

		this.editor.on('afterNodeChange', function() {
			this._fixNodes();
	    }, this.editor, true);

		this.editor.on('afterRender',function(){
			var indexList = [1, 2, 4];
			for (i = 0; i < 3; i++) {
				if (indexList[i] == editorContainor.tabNumber) {
					editorContainor.getYahooButton(indexList[i]).addClass('playground-button-selected');
				}
				else {
					editorContainor.getYahooButton(indexList[i]).addClass('playground-button-unselected');
					editorContainor.getYahooButton(indexList[i]).addClass('playground-button-disabled');
				}
				editorContainor.getYahooButton(indexList[i]).on('contentReady', editorContainor._setInitialButtonView.bindAsEventListener(editorContainor, i));
				if (BrowserIdentifier.isIE7()) { //* for IE7 rendering
					editorContainor.getYahooButton(indexList[i]).setStyle('width', '10%');
				}
				else
					if (BrowserIdentifier.isIE6()) { //* for IE6 rendering
						editorContainor.getYahooButton(indexList[i]).setStyle('width', '65px');
						editorContainor.getYahooButton(indexList[i]).setStyle('textAlign', 'center');
					}
			}
			if (BrowserIdentifier.isIE()) {
				$(this.toolbar.get('element').id).select('.yui-toolbar-subcont')[0].setStyle({
					height: "21px"
				});
			}
			if (BrowserIdentifier.isFF3()) { //* Firefox3系のレンダリング
				$$(".yui-toolbar-container .yui-toolbar-group").each(function(e){
					e.setStyle({
						marginBottom: '-4px'
					});
				})
			}
			//* Firefox3, Safari, Chromeでボタン位置の変更
			if (BrowserIdentifier.isFF3() || BrowserIdentifier.isSafari() || BrowserIdentifier.isChrome()) {
				$$(".yui-toolbar-container .yui-toolbar-group").each(function(e){
					e.setStyle({
						marginLeft: '-30px'
					});
				})
			}

			editorContainor.changeLastTabIndex(2);
		});

		if (BrowserIdentifier.isFF()) { //* Firefox系で最初に削除ができない不具合のための修正
			this.editor.on('editorContentLoaded', function(){
				var evt = this._getDoc().createEvent("KeyboardEvent");
				evt.initKeyEvent("keypress", true, true, this._getWindow(), false, false, false, false, 0, 32);
				this._getDoc().body.dispatchEvent(evt);
				this.setEditorHTML('');
			});
		}

		this.editor.on('editorMouseUp', this._onSomeEditorAction.bindAsEventListener(this));
		this.editor.on('editorMouseUp', this._onShowContextmenu.bindAsEventListener(this));
		this.editor.on('editorKeyUp', this._onSomeEditorAction.bindAsEventListener(this));

		//* フォーカス時のスクロール位置調整のハンドラ定義
		var beforeFocus = function(){
			if (BrowserIdentifier.isIE()) {
				this.pos = this.editor._getDoc().body.scrollTop;
			}
			else {
				this.pos = this.editor._getWindow().scrollY;
			}
		}
		var afterFocus = function(){
			var callback = function(){
				this.editor._getWindow().scrollTo(0, this.pos);
				this.editor.removeListener('beforeExecCommand', this.eventConstant.get('before_focus_window_action' + this.property));
				this.editor.removeListener('afterFocusWindow', this.eventConstant.get('after_focus_window_action' + this.property));
			}
			this.__sleep(callback.bind(this));
		}
		this.eventConstant.set('before_focus_window_action'+this.property, beforeFocus.bindAsEventListener(this));
		this.eventConstant.set('after_focus_window_action'+this.property, afterFocus.bindAsEventListener(this));

		//* コピペ時の即座な整形への対応
		//* Ctrl + V
		this.editor.on('editorKeyDown', function(ev){
			if (ev.ev.ctrlKey && ev.ev.keyCode == '86') {
				this.eventConstant.set('ctrlv_action' + this.property, this._onPressCtrlV.bindAsEventListener(this, this.getEditorHTML()));
				this.editor.on('editorKeyUp', this.eventConstant.get('ctrlv_action' + this.property), this);
			}
		}, this, true);

		this.editor.render();
	},

	//* エディタの文要素のカーソルが移動したときの動作
	//* BackTranslationマネージャにカーソル上の文IDを得て送信する（無かったらnullを送信）
	//* IEとIE以外で処理が異なる
	_cursorOnSentenceAction: function(){
		if (BrowserIdentifier.isIE()) {
			//* IEの場合，選択開始位置（カーソルの位置）と，各span要素の開始位置を取得
			//* それらの値から，カーソルを含んでいるspanのIDを取得
			var selection = this.editor._getSelection();
			var doc = this.editor._getDoc();
			var textRange = selection.createRange();
			var spanNodeId = null;

			var spans = doc.getElementsByTagName('span');

			// eip kawauchi add start 20100118
			// IE版のハイライト表示
			if (spans.length != 0) {
				spanNodeId = textRange.parentElement().id;
				this.backTranslationManager.notify(spanNodeId);
				return;
			}
			// eip kawauchi add end 20100118

			for (var i = spans.length - 1; i >= 0; i--) {
				if (!spans[i].id || spans[i].id.search(/sentence-\d+-\d+/i) == -1) {
					spans.splice(i, 1);
				}
			}

			for (var i = 0; i < spans.length - 1; i++) {
				var nowSpanOffset = Element.cumulativeOffset(spans[i]);
				nowSpanOffset[1] -= doc.body.scrollTop;
				var nextSpanOffset = Element.cumulativeOffset(spans[i + 1]);
				nextSpanOffset[1] -= doc.body.scrollTop;
				if (nowSpanOffset[1] == textRange.offsetTop) {
					if (nowSpanOffset[0] <= textRange.offsetLeft) {
						if (nextSpanOffset[1] == textRange.offsetTop) {
							if (nextSpanOffset[0] > textRange.offsetLeft) {
								spanNodeId = spans[i].id;
								break;
							}
						}
						else { //* nextSpanOffset[1] > textRange.offsetTop
							spanNodeId = spans[i].id;
							break;
						}
					}
				}
				else { //* nowSpanOffset[1] < textRange.offsetTop
					if (nextSpanOffset[1] == textRange.offsetTop) {
						if (nextSpanOffset[0] > textRange.offsetLeft) {
							spanNodeId = spans[i].id;
							break;
						}
					}
					else
						if (nextSpanOffset[1] > textRange.offsetTop) {
							spanNodeId = spans[i].id;
							break;
						}
				}
			}
			if (spans.length > 0 && spanNodeId == null) {
				spanNodeId = spans[spans.length - 1].id;
			}
			this.backTranslationManager.notify(spanNodeId);
		}
		else {
			var selection = this.editor._getSelection();
			var node = this.editor._getSelection().anchorNode;
			var spanNodeId = null;
			while (node.nodeType == 3 || !node.tagName.match(/body/i)) {
				if (node.id && node.id.search(/sentence-\d+-\d+/i) != -1) {
					spanNodeId = node.id;
					break;
				}
				node = node.parentNode;
			}
			this.backTranslationManager.notify(spanNodeId);
		}
	},

	//* キーボードのペーストの操作から即座に整形を行うコード
	_onPressCtrlV: function(){
		this.editor.removeListener('editorKeyUp', this.eventConstant.get('ctrlv_action'+this.property));
		this.__fixEditorContent($A(arguments)[1]);
	},

	//* エディタ上の言語選択タブがクリックされた後のリスナ
	_onTabClick: function(ev){
		this.editor.on('beforeExecCommand', this.eventConstant.get('before_focus_window_action'+this.property), this);
		this.editor.on('afterFocusWindow', this.eventConstant.get('after_focus_window_action'+this.property), this);
		this.controller.setActiveEditor(this); //* アクティブなエディタをクリックしたタブのエディタに変える

		var clickedTabNumber = $A(arguments)[1];
		if (!this.isEnableLanguageTab(clickedTabNumber)) {
			return; //* もし，クリック不可能なタブをクリックした時，何も処理をせず終了
		}

		if(BrowserIdentifier.isFF()) {
			//* Firefoxではいったんhighlightクラスを切っておかないと不具合が起こる
			this.backTranslationManager.removeHilite();
		}
		this.controller.setText(this.tabNumber, this.getEditorHTML()); //* 現在のエディタの状態を保存

		if(clickedTabNumber != this.tabNumber){ //* タブクリック前に選択されていない言語タブを選択した時
			//* 3言語時でかつ中間言語を他方のエディタが表示しているとき，表示言語を変える
			if(this.totherEditor().getActiveTabIndex() == 2){
				//* 他方のエディタの状態を保存
				this.controller.setText(this.totherEditor().tabNumber, this.totherEditor().getEditorHTML());

				var tabIndex;
				if (this.property == 'source') {
					tabIndex = 4;
				}
				else
					if (this.property == 'target') {
						tabIndex = 1;
					}
				this.totherEditor().selectButton(tabIndex);
				this.totherEditor().refreshEditorContent();
			}

			this.selectButton(clickedTabNumber);
			this.backTranslationManager.refreshBackTranslation();
			this.backTranslationManager.setBacktranslationPath();
			this.setDirection();
		}

		this.messageManager.setSourceLanguage(); //* メッセージ管理者の翻訳元言語更新
		this.refreshEditorContent(); //* 表示を更新する
	},

	//* AlertBoxがすでに表示されているかどうかを調べる
	isShownAlertBox: function(){
		var div_panel = $("internal-panel-wait-"+this.property);
		if (!div_panel) {
			return false;
		}
		if (div_panel.getStyle("display") == 'none') {
			return false;
		}
		else {
			return true;
		}
	},

	//* 処理に時間がかかるときに"Just a minute"と表示する
	showAlertBox: function(html){
		var div_panel = $("internal-panel-wait-" + this.property);
		var div_panel_height = 20;
		if (!div_panel) {
			var div_panel = new Element('div', {
				'id': 'internal-panel-wait-' + this.property
			});
			new Insertion.Bottom($$('body')[0], div_panel);
			div_panel.setStyle({
				backgroundColor: "#A8FFF1",
				padding: "2px 5px",
				verticalAlign: 'middle',
				fontWeight: 'bold',
				height: div_panel_height + 'px'
			});
		}
		var element = null;
		if (this.property == 'source') {
			element = $('original-textarea_container').select('div.yui-editor-editable-container')[0];
		}
		else {
			element = $('translation-textarea_container').select('div.yui-editor-editable-container')[0];
		}

		var buttonPosition = element.cumulativeOffset();
		var buttonWidth = element.getWidth();
		var buttonHeight = element.getHeight();
		var editableAreaContainerWidth = null;
		var bodyPosition = $$('body')[0].cumulativeOffset();
		var x = buttonPosition[0] - bodyPosition[0];
		var y = buttonPosition[1] - bodyPosition[1] + buttonHeight - div_panel_height - 4;
		div_panel.setStyle({
			position: 'absolute',
			left: x + 'px',
			top: y + 'px',
			zIndex: 1,
			width: (buttonWidth - 27) + 'px'
		});
		if (html) {
			div_panel.innerHTML = html;
		}
		else {
			div_panel.innerHTML = '<img src="img/document-translation/waiting.gif" height="16" width="16"> Just a minute';
		}
		div_panel.show();
	},

	//* 処理が終了した時に"Just a minute"パネルを隠す
	hideAlertBox: function(){
		var div_panel = $("internal-panel-wait-"+this.property);
		if (div_panel) {
			div_panel.hide();
		}
	},

	//* サーバを介して少し時間をとる
	//* f: bindingした関数をコールバックとして実行
	__sleep: function(f){
		new Ajax.Request('./php/ajax/document-translation/sleep.php', {
			method: 'post',
			parameters: 'microseconds=10',
			onComplete: function(){
				if (f)
					f();
			}
		});
	},

	//* エディタに対して何らかのアクションを起こしたとき
	_onSomeEditorAction: function(ev){
		this.controller.setActiveEditor(this);
		this.messageManager.setSourceLanguage(); //* メッセージ管理者の翻訳元言語更新
	},

	//* 右クリックしてコンテキストメニューを表示させたときのアクション
	//* 右クリックかどうかはイベントをチェックして確認する必要がある(editorMouseUpからlistenしている)
	_onShowContextmenu: function(ev){
		//* Firefox以外は中クリック(左クリック以外)も検知してしまう可能性がある
		if ((!BrowserIdentifier.isFF() && !Event.isLeftClick(ev.ev)) || ev.ev.isRightClick()) {
			var tempHTML = this.getEditorHTML();
			var editorCont = this;
			var func = function(){
				this.someActionFlag = true;
			}
			this.eventConstant.set('some_action_on_window' + this.property, func.bindAsEventListener(this));
			document.body.observe('click', this.eventConstant.get('some_action_on_window' + this.property));
			this.someActionFlag = false;
			new PeriodicalExecuter(function(pe){
				if (tempHTML != editorCont.getEditorHTML() || editorCont.someActionFlag) {
					pe.stop();
					editorCont.someActionFlag = true;
					document.body.stopObserving('click', editorCont.eventConstant.get('some_action_on_window' + editorCont.property));
					if (tempHTML != editorCont.getEditorHTML()) {
						editorCont.__fixEditorContent(tempHTML); //* 念のため
					}
				}
			}, 0.5);
		}
	},

	//* Ctrl + V, 右クリックペーストによって得られた文書を整形する
	//* 変更が大きい場合はサーバを利用する
	__fixEditorContent: function(previousContent){
		if (this.controller.isTranslating() || this.controller.isFormatting()) {
			alert('Please wait until other action is over.');
			this.setEditorHTML(previousContent)
			return;
		}
		var isFormattedOnServer = false;
		if (Math.abs(this.getEditorHTML().length - previousContent.length) > this.BIG_CHANGE_THRESHOLD) {
			isFormattedOnServer = true;
			this.showAlertBox();
		}
		var callbackFunc = function(){
			this.setFormatting(true);
			this.editor._fixNodes();
			if (isFormattedOnServer) {
				var lang;
				if (this.getActiveTabIndex() == 4) {
					lang = this.controller.getSelector().getLanguages()[2];
				}
				else {
					lang = this.controller.getSelector().getLanguages()[this.getActiveTabIndex() - 1];
				}
				var callobj = {
					language: lang,
					html: this.getEditorHTML(),
					number: this.getActiveTabIndex() - 1
				};
				var formText = $H(callobj).toQueryString();
				var ed = this;
				if (!this.isShownAlertBox()) {
					this.showAlertBox();
				}
				new Ajax.Request('./php/ajax/document-translation/formatting-text.php', {
					method: 'post',
					parameters: formText,
					ed: ed,
					onSuccess: function(httpObj){
						try {
							var responseJSON = httpObj.responseText.evalJSON()
							var checker = new StatusProcessor(responseJSON);
							if (!checker.check()) {
								return;
							}
							ed.setEditorHTML(responseJSON.contents);
						}
						catch (e) {
							ed.controller.setSystemMessage('An error has occured.');
						}
					},
					onFailure: function(httpObj){
						ed.controller.setSystemMessage('An error has occured.');
						handleHTTPStatusCode(httpObj);
					},
					onComplete: function(){
						ed.hideAlertBox();
						ed.setFormatting(false);
					}
				});
			}
			else {
				this.setFormatting(false);
			}
		}
		this.__sleep(callbackFunc.bind(this));
	},

	//* 対の他方(tother)のエディタを得る
	totherEditor: function(){
		if (this.property == 'source') {
			return this.controller.getRightEditor();
		}
		else
			if (this.property == 'target') {
				return this.controller.getLeftEditor();
			}
		return null;
	},

	//* エディタエリアを引き伸ばす
	expand: function(){
		this.editor.set('height',(parseInt(this.editor.get('height'))+50)+'px',false);
	},

	//* エディタエリアを縮める
	reduce: function(){
		this.editor.set('height',(parseInt(this.editor.get('height'))-50)+'px',false);
	},

	//* アクティブなエディタとなったときのアクション
	activate: function(){
		var window = this.editor._getWindow().document.body.style.background = '#FCFAE1';
	},

	//* デアクティブなエディタとなったときのアクション
	deactivate: function(){
		var window = this.editor._getWindow().document.body.style.background = '#FFF';
	},

	//* タブ選択による状態変更，及びクラス指定による描画指定
	selectButton: function(nextIndex){
		var oPreviousSelectedButton = this.getYahooButton(this.tabNumber);
		if (oPreviousSelectedButton.hasClass('playground-button-selected')) {
			oPreviousSelectedButton.removeClass('playground-button-selected');
		}
		if (!oPreviousSelectedButton.hasClass('playground-button-unselected')) {
			oPreviousSelectedButton.addClass('playground-button-unselected');
		}

		var oNextSelectingButton = this.getYahooButton(nextIndex);
		if (oNextSelectingButton.hasClass('playground-button-unselected')) {
			oNextSelectingButton.removeClass('playground-button-unselected');
		}
		if (!oNextSelectingButton.hasClass('playground-button-selected')) {
			oNextSelectingButton.addClass('playground-button-selected');
		}

		this.tabNumber = nextIndex;
	},

	//* 最初のボタンのラベルをつける
	_setInitialButtonView: function(ev){
		var index = $A(arguments)[1];
		this.__waitDomReady(index);
	},

	//* 言語・翻訳機選択におけるIEの初期レンダリングの問題への対策
	__waitDomReady: function(index){
		var selector = this.controller.getSelector();
		if (!selector.domReady) {
			var editor = this;
			new Ajax.Request('./php/ajax/document-translation/sleep.php', {
				method: 'post',
				parameters: 'microseconds=100000',
				editor: editor,
				onComplete: function(){
					editor.__waitDomReady(index);
				}
			});
		}
		else {
			this.setButtonLabel(this.EDITOR_INDEX_ARRAY[index], Language.getNameByTag(selector.languageMenuElements[index].value));
			this.setDirection();
			if (index == 2) {
				this.hideTab(this.EDITOR_INDEX_ARRAY[index]);
			}
			//* 折り返し翻訳の言語パスの初期表示
			this.backTranslationManager.setBacktranslationPath();
			this.messageManager.setSourceLanguage();
		}
	},

	refreshEditorContent: function(){
		this.setEditorHTML(this.controller.getText(this.tabNumber));
	},

	//* ボタンのラベル変更
	setButtonLabel: function(index, label){
		var oButton = this.getYahooButton(index);
		oButton.set('label', label, false);
	},

	//* アクティブなエディタの言語に合わせて文字の流れる方向を調整する
	setDirection: function(){
		var index = this.getActiveTabIndex()-1;
		if(index == 3){
			index = 2;
		}
		var languageTag = this.controller.getSelector().getLanguages()[index];
		var direction = Language.getTextDirection(languageTag);
		this.editor._getDoc().body.dir = direction;
	},

	//* ボタンを有効化する
	enableButton: function(index){
		this.getYahooButton(index).removeClass('playground-button-disabled');
	},

	//* ボタンを無効化する
	disableButton: function(index){
		this.getYahooButton(index).addClass('playground-button-disabled');
	},

	//* 最高尾の言語選択ボタンのindexを指定する(ボタンのデザイン調整)
	changeLastTabIndex: function(index){
		for(var i=0;i<this.EDITOR_INDEX_ARRAY.size();i++){
			this.getYahooButton(this.EDITOR_INDEX_ARRAY[i]).removeClass('playground-button-last');
		}
		this.getYahooButton(index).addClass('playground-button-last');
	},

	//* エディタ上の言語選択タブが選択可能なタブか
	isEnableLanguageTab: function(index){
		return !this.getYahooButton(index).hasClass('playground-button-disabled');
	},

	//* 文書整形モードを設定する
	setFormatting: function(isFormatting){
		this.isInFormatting = isFormatting;
		if (this.isFormatting()) {
			document.fire('start:formatting');
		}
		else {
			document.fire('finish:formatting');
		}
	},

	//* 文書整形しようと待っている状態か
	isFormatting: function(){
		return this.isInFormatting;
	},

	//* エディタの高さを変更する。
	//* @param {Number} height
	changeHeight: function(height){
		this.editor.set('height',height+'px',true);
	},

	//* エディタの中身のHTMLを得る。
	getEditorHTML: function(){
		return this.editor.getEditorHTML();
	},

	//* エディタの中身のHTMLを変更する。
	//* @param {String} contentHTML
	setEditorHTML: function(contentHTML){
		if(BrowserIdentifier.isIE6() || BrowserIdentifier.isIE8()){ //* IE6でのエラーに対応
			this.editor._getDoc().body.innerHTML = contentHTML;
		} else {
			this.editor.setEditorHTML(contentHTML);
		}
	},

	//* エディタの中身のテキストを得る。
	getEditorText: function(){
		var stripHTML = /<\S[^><]*>/g;
		this.editor.saveHTML();
		return this.editor.get('textarea').value.replace(stripHTML, '');
	},

	//* 表示しているエディタの番号を得る
	//* 原文:1, 翻訳文(2言語)または中間言語(3言語): 2, 翻訳文(3言語): 4
	getActiveTabIndex: function(){
		return this.tabNumber;
	},

	//* return YAHOO.widget.Editor
	getYahooEditor: function(){
		return this.editor;
	},

	//* return YAHOO.widget.Toolbar
	getYahooToolbar: function(){
		return this.getYahooEditor().toolbar;
	},

	//* return YAHOO.widget.Button
	getYahooButton: function(index){
		return this.getYahooToolbar().getButtonById(this.BUTTON_ID_TEMPLATE.evaluate({
			editorId: this.id,
			number: index
		}))
	},

	//* return Element
	getYahooButtonElement: function(index){
		return $(this.BUTTON_ID_TEMPLATE.evaluate({
			editorId: this.id,
			number: index
		}));
	},

	showTab: function(index){
		this.getYahooButtonElement(index).show();
	},

	hideTab: function(index){
		this.getYahooButtonElement(index).hide();
	}
};

//* ドキュメント翻訳用にYAHOO.widget.Editorを継承してカスタマイズする
YAHOOEditorForDocumentTranslation = function(el) {
	var myConfig = {
		animate: false,
		dompath: false,
		toolbar: {
			buttons: [{
				group: 'textstyle',
				buttons: [{
					type: 'button',
					label: ' ',
					value: 'selectsentence',
					disabled: false
				}]
			}]
		},
		css: "html {height: 95%;}" +
		"body {height: 100%;padding: 7px; background-color: #fff; font:13px/1.22 arial,helvetica,clean,sans-serif;*font-size:small;*font:x-small;}" +
		"a {color: blue;text-decoration: underline;cursor: pointer;}" +
		".warning-localfile {border-bottom: 1px dashed red !important;}" +
		".yui-busy {cursor: wait !important;}" +
		"img.selected { border: 2px dotted #808080;}" +
		"img {cursor: pointer !important;border: none;}" +
		".highlight {background-color:#FFFF30;}" //* ハイライトに関するクラスの追加
	};
    YAHOOEditorForDocumentTranslation.superclass.constructor.call(this, el, myConfig);
};
YAHOO.extend(YAHOOEditorForDocumentTranslation, YAHOO.widget.Editor, {
	//* エディタ内で使用不可とするHTMLタグを設定（オーバーライド）
    invalidHTML: {
		html: true,
		body: true,
		link: true,
		script: true,
		style: true,
		title: true,
		p: true,
		div: true,
		center: true,
		pre: true,
		blockquote: true,
		i: true,
		b: true,
		tt: true,
		u: true,
		sup: true,
		strong: true,
		em: true,
		code: true,
		a: true,
		img: true,
		map: true,
		area: true,
		form: true,
		input: true,
		button: true,
		select: true,
		option: true,
		textarea: true
	},

	//* _fixNodesメソッドに用いる情報を設定
	convertToBr: ['div'], //* 改行を1つはさむ
	convertToBr2: ['p'], //* 改行を2つはさむ
	directlyDelete: ['script','style','title','html','link'],
	tbodyTags: ['tbody','tr','td','th'],

	//* YAHOOエディタのレンダリング修正メソッドをオーバーライド
	//* 現在のメソッドのバグを修正
	_fixNodes: function(){
		try {
			var doc = this._getDoc();
			var els = [];

			if (doc.body.getElementsByTagName('table').length > 0) {
				var html = doc.body.innerHTML;
				html = html.replace(/<\/?table.*?>/ig, '');
				html = html.replace(/<\/?tr.*?>/ig, '');
				html = html.replace(/<\/?tbody.*?>/ig, '');
				html = html.replace(/<\/?thead.*?>/ig, '');
				html = html.replace(/<t(d|h)(>|\s.*?>)/ig, '');
				html = html.replace(/<\/t(d|h)>/ig, '<br />');
				doc.body.innerHTML = html;
			}

			for (var v in this.invalidHTML) {
				if (YAHOO.lang.hasOwnProperty(this.invalidHTML, v)) {
					var tags = doc.body.getElementsByTagName(v);
					if (tags.length) {
						for (var i = 0; i < tags.length; i++) {
							if (BrowserIdentifier.isIE()) { //* IEのときは深く見る
								var descendant = true;
								for (var j = 0; j < els.length; j++) {
									if (Element.descendantOf(els[j][0], tags[i])) {
										descendant = false;
										break;
									}
									else
										if (Element.descendantOf(tags[i], els[j][0])) {
											els.splice(j, 1);
											j--;
										}
								}
								if (descendant) {
									els.push([tags[i], v]);
								}
							}
							else {
								els.push([tags[i], v]);
							}
						}
					}
				}
			}

			for (var h = 0; h < els.length; h++) {
				try {
					if (this.directlyDelete.indexOf(els[h][1]) == -1 && !els[h][0].innerHTML.match(/^\s*$/)) {
						if (this.convertToBr.indexOf(els[h][1]) != -1) {
							Element.replace(els[h][0], '<br>' + els[h][0].innerHTML);
						}
						else
							if (this.convertToBr2.indexOf(els[h][1]) != -1) {
								Element.replace(els[h][0], '<br>' + els[h][0].innerHTML + '<br>');
							}
							else {
								Element.replace(els[h][0], els[h][0].innerHTML);
							}
					}
					else {
						Element.remove(els[h][0]);
					}
				}
				catch (e) { //* IEではここでキャッチされる．この部分をどう扱うかを考える必要がある　（おそらくinnerHTMLの扱い）
				}
			}
			if (els.length > 0) {
				this._fixNodes();
			}
			else {
				var html = doc.body.innerHTML;
				if (html.match(/<!--.*-->/)) {
					html = html.replace(/<!--.*-->/ig, '');
					doc.body.innerHTML = html;
				}
			}
		}
		catch (e) {
		}
	},

	//* ボタンなどをクリックするとウィンドウにフォーカスをする
	//* ウィンドウにフォーカスする瞬間にウィンドウが先頭まで戻る場合があるため，それを修正したい
	//* その時のbeforeとafterのアクションを発行(fireEvent)する
	//* コードの中身は元のコードと同じである．
	_focusWindow: function(){
		this.fireEvent('beforeFocusWindow', {
			type: 'beforeFocusWindow',
			target: this
		});
		YAHOOEditorForDocumentTranslation.superclass._focusWindow.call(this);
		this.fireEvent('afterFocusWindow', {
			type: 'afterFocusWindow',
			target: this
		});
	}
})

