/*
 *
 * suggest-common-ver5.js
 * オブジェクトのインクリメンタルサーチ ライブラリ
 *
 */

(function($) {

	// SuggestObject格納用変数
	var _suggest_objects = {};
	var _suggest_api_url = undefined;
	var _suggest_api_key = undefined;

	//プラグイン定義
	// api URLのデフォルト設定
	$.suggest_api = function(url, key) {
		_suggest_api_url = url;
		_suggest_api_key = key;
		return(this);
	};
	// サジェストオブジェクト生成
	$.fn.suggest = function(options){

		if (!options) {
			// if (!this.is(':focus')) { this.focus(); };
			return _suggest_objects[this.selector];
		}

		//引数を設定する
		var defaults = {
			text_selector   : this.selector,
			api_url         : _suggest_api_url,
			api_key         : _suggest_api_key
		};
		var setting = $.extend(defaults, options);

		_suggest_objects[this.selector] = new SuggestObject(setting);

		//メソッドチェーン対応(thisを返す)
		return(this);
	};
	
	// windowオブジェクトに対するイベントの設定
	$.window_event = function(event_name, func) {
		$(window).on(event_name, func);
		return(this);
	};
	
	// サジェストのオブジェクトをすべて取得
	$.get_suggest_objects = function(){
		return _suggest_objects;
	};
})(jQuery);

var SuggestCommonObserver = {

	// SuggestObjectを格納する配列
	suggest_objects: [],

	// インクリメンタルサーチAPIのURL
	api_url : false,

	// 初期化 オプションを配列で渡す
	init: function(objects){
		for (var i = 0; i < objects.length; i++) {
			this.push(objects[i]);
		};
		return this;
	},

	api: function(url) {
		this.api_url = url;
		return this;
	},
	// 追加
	push: function(options) {

		var suggest = new SuggestObject(options);
		if (this.api_url) {
			suggest.api_url = this.api_url;
		}

		$(suggest.text_selector).suggest = suggest;

		this.suggest_objects.push();
		return this;
	}
};


var SuggestObject = function(options) {

	for( key in options ) {
		this[key] = options[key];
	}

	this._init();
};

var _suggestHideULTimerId = null;

SuggestObject.prototype = {

	// インスタンス変数

	// インクリメンタルサーチAPIのURL
	api_url : '//mob-gw.ekitan.com/inc/v2',

	// インクリメンタルサーチAPIのメソッド
	api_func : 'n_station',
	// {
	// 	TIMETABLE_STATION : "t_station",
	// 	TIMETABLE_BUS : "t_bus",
	// 	TIMETABLE_BUS_COMPANY : "t_bus_company",
	// 	TIMETABLE_ALL : "t_all",
	// 	NORIKAE_STATION : "n_station",
	// 	NORIKAE_BUS : "n_bus",
	// 	NORIKAE_BUS_COMPANY : "n_bus_company",
	// 	NORIKAE_ALL : "n_all",
	// },

	text_selector:		null,
	ul_selector:		null,
	objectList:		null,
	candidateList:	[],
	displayMax:		-1,   // 候補の表示件数（-1はAPIの出力件数に任せる）
	interval:		200,  // 入力のチェック間隔
	searchInterval:	200,  // 入力後に検索に行く遅延時間
	timerId:		null, // 入力確認タイマー
	searchTimerId:  null, // 入力後に検索に行く遅延用タイマー
	hideUlTimerId:  null,
	jqxhr:          null,
	
	company:        '',

	// 駅名候補表示時のヘッダーHTML
	listHeader: false,

	// マイデータ連携用
	isShowDefaultList: false, // マイデータ表示中フラグ

	isDeleteClick: false,	// 削除がクリックされたか

	preText:		null,

	keyCode: {
		TAB:	9,
		RETURN:	13,
		ESC:	27,
		UP:		38,
		DOWN:	40
	},

	selectStyleClass: "active",
	completedSytleClass: "completed",

	isDecision: false,

	// --------------------------------------------------------------------------------------------
	// オーバーライド推奨イベント
	// 確定イベントを実装
	complete: function(data) {},

	// 確定状態から未確定状態へ移行した時のイベント
	nocomplete: function() {},

	// 検索前に実行されるイベント
	beforeSearchFunc: function() {},

	// 検索後に実行されるイベント
	afterSearchFunc: function() {},

	// オブジェクト生成時にすでに確定状態かを設定する（FireFoxのリロード対応や戻るボタン対応）
	defaultComplete: function() {
		return false;
	},

	// マイデータ表示用のイベント
	showMyData: function(suggest, isShowDefaultList) {
	},

	hideMyData: function(suggest) {
	},

	myDataKeyEvent: function(e, suggest, visible) {

	},

	// 確定状態でEnterキーが押された時のイベント（フォームのsubmitを実行したい時などにどうぞ）
	formAction :function() {},

	// ulを表示するエレメントの位置を調整（位置調整不要な場合は戻り値に false を指定）
	// 位置指定する場合は、top と left を持つオブジェクトを返すようにする。
	position: function() {
		var $input = $(this.text_selector);
		var pos = $input.position();
		pos.top += $input.outerHeight();
		return pos;
	},

	// --------------------------------------------------------------------------------------------
	showDefaultList: function() {
		var $input = $(this.text_selector);
		$input.focus();
		this.hideCandidate();
		if (this.isShowDefaultList) {
			this.hideMyData(this);
		} else {
			// 一瞬フォーカスが外れるため、表示を遅延させる
			setTimeout(this._call(this.showMyData, this, true), this.interval);
		}
	},

	_init: function() {
		// フォーカス、キーイベント設定
		$(this.text_selector)
			.on('focus',this._callArgs(this._focus))
			.on('blur',this._callArgs(this._blur))
			.on('keydown keyup keypress', this._callArgs(this._keyEvent));

		// オブジェクト生成時にすでに確定状態かを設定する（FireFoxのリロード対応や戻るボタン対応）
		if (this.defaultComplete()) {
			this._decision(true);
		}
	},

	_focus: function() {
		// 邪魔になるのでautocompleteをoff
		$(this.text_selector).attr("autocomplete", "off");
		this.preText = this.getInputValue();

		// 候補クリックイベント設定
		$(this.ul_selector).off('click').on('click', 'li', this._callArgs(this._clickEvent));

//		this._checkStart();
		// 前のフォーカスが外れる処理の後に実行されるようにする
		this.timerId = setTimeout(this._call(this._checkStart), this.interval*2);
	},

	_blur: function() {

		// フォーカスが外れてから入力確認１回分は実行し続ける
		setTimeout(this._call(this._checkStop), this.interval);

		// history.backでのキャッシュが効かなくなるのでautocompleteをon
		$(this.text_selector).attr("autocomplete", "on");
	},

	_checkStart: function() {
		var inputText = this.getInputValue();

		// フォーカスされたら入力をチェック
		if ( this.preText != inputText ) {
			// 確定状態を解除
			this._decision(false);
			// 検索開始タイマーのクリア
			this._clearSeachTimer();
			// input textが変更されていたら検索実行
			this.searchTimerId = setTimeout(this._call(this.searchAndDisplay,inputText), this.searchInterval);
			this.preText = inputText;
		}

		this._clearTimer();
		this.timerId = setTimeout(this._call(this._checkStart), this.interval);
	},

	_checkStop: function() {
		if ($(this.text_selector).is(':focus')) {
			// フォーカスがまだ当たっていたらチェックを継続させる
			return;
		};
		if($('.ek-mydata-ul').is(':focus')) {
			$(this.text_selector).focus();
			return;
		}
		if(this.isDeleteClick) {
			this.isDeleteClick = false;
			return;
		}

		// フォーカスが外れたら入力チェック終了
		setTimeout(this._call(this._clearTimer), 200);
		// this._clearTimer();
		// this._clearSeachTimer();
		// 候補リストと閉じる前に確定していなければ確定させる
		setTimeout(this._call(this.selectListTop), 200);
		// 候補閉じる
		// setTimeout(this._call(this.hideCandidate), 200);
		this.hideCandidate();
		this.hideMyData(this);
	},

	selectListTop: function() {
		// 候補リストと閉じる前に確定していなければ確定させる
		if (!this.isDecision && this.isListActive() ) {
			var li_active = $(this.ul_selector).find('li.active');
			if (li_active.length) {
				var data = $.parseJSON(li_active.attr('data-ek-suggest-json'));
				data.selectedType = 'blur';
				this._selectedItem(data, true);
			}
		}
	},

	isListActive: function() {
		$ul = $(this.ul_selector);
		if ($ul.is(':visible') && $ul.attr('data-ek-suggest-selector') == this.c) {
			return true;
		}
		return false;
	},

	_clearTimer: function() {
		// 入力監視タイマーのクリア
		if (this.timerId){
			clearTimeout(this.timerId);
			this.timerId = null;
		}
	},
	_clearSeachTimer: function() {
		// 通信のキャンセル
		if (this.jqxhr) {
			this.jqxhr.abort();
			this.jqxhr = null;
		}
		// 検索開始タイマーのクリア
		if (this.searchTimerId) {
			clearTimeout(this.searchTimerId);
			this.searchTimerId = null;
		}
	},
	_clearHideUlTimer: function() {
		// 検索開始タイマーのクリア
		if (_suggestHideULTimerId) {
			clearTimeout(_suggestHideULTimerId);
			_suggestHideULTimerId = null;
		}
	},

	searchAndDisplay: function(inputText) {
		if (inputText == '') {
			this.hideCandidate();
			this.candidateList = [];
			return;
		}
		this.beforeSearchFunc();
		// 候補取得
		this._search(inputText);
	},

	_search: function(inputText) {
		var self = this;
		
		self.hideMyData(self);

		var data = {
			q: inputText,
			c: self.company
		};

		if (this.jqxhr) {this.jqxhr.abort();}; // 前回の通信をキャンセル

		var self = this;
		this.jqxhr = $.ajax({
			dataType: 'json',
			// url: url
			url:      this.api_url + '/' + this.api_func,
			data: data
		}).done(function(data){
			var resultData = data[0].result;
			if (!$(self.text_selector).is(':focus') && resultData.length > 0) {
				// すでにフォーカスが外れていて候補が帰ってきたら確定
				self.hideCandidate();
				resultData[0].selectedType = 'tab';
				self._selectedItem(resultData[0], true);
			} else {
				// フォーカス中なら候補リストを表示
				self.candidateList = resultData;
				self.showCandidate(resultData, self.listHeader);
			}
		});
	},

	// not_active_first リストの一番上を選択させたくない場合 true
	showCandidate: function(data, head, foot, not_active_first, isShowDefaultList) {
		// console.log(data);

		this.isShowDefaultList = isShowDefaultList ? isShowDefaultList : false;

		if (data.length == 0) {
			this.hideCandidate();
			return;
		}

		this._clearHideUlTimer();

		var $ul = $(this.ul_selector);

		$ul.removeAttr('data-ek-suggest-selector').attr('data-ek-suggest-selector',this.text_selector);

		// ulを表示するエレメントの位置を調整
		// var $input = $(this.text_selector);
		// var $ul = $(this.ul_selector);
		// var pos = $input.position();
		// $ul.css({'left': pos.left, 'top': pos.top + $input.outerHeight()});
		if (this.position || !this.position()) {
			$(this.text_selector).after($ul); // ul要素の位置をテキストボックスの直後に移動
			var pos = this.position();
			$ul.css({'left': pos.left, 'top': pos.top});
		};

		// header div 候補のヘッダー
		var header = '';
		if (head) {
			header = $('<div/>').append(head);
		};

		var candidate = $('<ul/>');
		for (var i = 0; i < data.length && (this.displayMax < 0 || i < this.displayMax); i++) {
			var item = data[i];
			var json = JSON.stringify(item);
			$a_tag = $('<a href="#"></a>').html(item.name);
			candidate.append( $('<li/>').attr('data-ek-suggest-json',json).append($a_tag) );
		};

		$ul.html('').append(header).append(candidate);

		if (!not_active_first) {
			$ul.find('li:first-child').addClass('active');
		}

		$ul.show();

		this.afterSearchFunc();

	},

	hideCandidate: function() {
		$(this.ul_selector).html('').hide();

		this._clearHideUlTimer();
		_suggestHideULTimerId = setTimeout(this._call(function(){
			$(this.ul_selector).html('').hide().removeAttr('data-ek-suggest-selector');
			this.isShowDefaultList = false;
		}), this.interval);
	},

	_clickEvent: function(e) {
		// console.log("click");
		e.preventDefault();
		// click element
		$li = $(e.currentTarget);
		var data = $.parseJSON($li.attr('data-ek-suggest-json'));

		data.selectedType = "click";

		this._selectedItem(data, true);
	},

	_selectedItem: function(data, overwrite) {
		// console.log(data);

		// code または name がないものは無視
		if (!data.code || !data.name) { return; };

		if(overwrite) {
			this._decision(true);
			this.preText = data.name;
			data.comp = '1';
			$(this.text_selector).val(this.preText).addClass(this.completedSytleClass);
		} else {
			data.comp = '';
		}
		this.hideCandidate();

		this._clearSeachTimer();

		this.complete(data);

		// 候補を選択したときに、入力欄が隠れていれば表示させる
		if($(window).scrollTop() > $(this.text_selector).offset().top) {
			$(window).scrollTop($(this.text_selector).offset().top)
		}
	},

	// keyup時のenter判定用、全角確定時のenterはfalseになる
	_enter: false,

	_keyEvent: function(e) {
		// e.preventDefault();
		// console.log(e.type + " "+ e.keyCode);
		// console.log(e);

		if (e.type == 'keydown' && e.keyCode == this.keyCode.RETURN) {
			this._enter = true;
		};

		var $ul = $(this.ul_selector);
		var $input = $(this.text_selector);

		if (e.type == 'keydown' && e.keyCode == this.keyCode.ESC) {
			if (this.candidateList.length == 0) {
				this.hideCandidate();
				this.hideMyData(this);
			} else if ($ul.is(':visible')) {
				this.hideCandidate();
				this.hideMyData(this);
			} else {
				if ($input.val() == '') {
//					this.showMydata(this);
					setTimeout(this._call(this.showMyData, this, true), this.interval);
				} else {
					this.showCandidate(this.candidateList, this.listHeader);
				}
			}
			return;
		}

		var ul_visible = true;
		if ($ul.is(':visible')) {
			// 候補リスト表示中のキーイベント
			if (e.type == 'keydown' && e.keyCode == this.keyCode.DOWN) {
				e.preventDefault();
				var li_active = $ul.find('li.active');
				if (li_active.length) {
					$(li_active).removeClass('active');
					$(li_active).next().addClass('active');
				} else {
					$ul.find('li:first-child').addClass('active');
				}

			} else if (e.type == 'keydown' && e.keyCode == this.keyCode.UP) {
				e.preventDefault();
				var li_active = $ul.find('li.active');
				if (li_active.length) {
					$(li_active).removeClass('active');
					$(li_active).prev().addClass('active');
				} else {
					$ul.find('li:last-child').addClass('active');
				}
			} else if (e.type == 'keydown' && e.keyCode == this.keyCode.TAB) {
				var li_active = $ul.find('li.active');
				if (li_active.length) {
					var data = $.parseJSON(li_active.attr('data-ek-suggest-json'));
					li_active.removeClass('active');
					data.selectedType = 'tab';
					this._selectedItem(data, true);
				}
			} else if (e.type == 'keyup' && this._enter && e.keyCode == this.keyCode.RETURN) {
				var li_active = $ul.find('li.active');
				if (li_active.length) {
					var data = $.parseJSON(li_active.attr('data-ek-suggest-json'));
					li_active.removeClass('active');
					data.selectedType = 'enter';
					this._selectedItem(data, true);
				}
			}
		} else {
			// 候補リスト非表示中のキーイベント
			if (e.type == 'keyup' && e.keyCode == this.keyCode.RETURN && this.isDecision && this._enter) {
				// this._blur();
				$input.blur();
				this.formAction();
				ul_visible = false;
			} else if (e.type == 'keydown' && (e.keyCode == this.keyCode.DOWN) || e.keyCode == this.keyCode.UP) {
				e.preventDefault();
				// this.preText = '';
				if ($input.val() != '') {
					this.showCandidate(this.candidateList, this.listHeader);
				}
			}
		}

		this.myDataKeyEvent(e, this, ul_visible);

		if (e.type == 'keyup') {
			this._enter = false;
		};
	},

	// 文字列が入力されていて未確定なら検索して第一候補で確定する
	selectedSearchFirst: function() {

		var $input = $(this.text_selector);

		if ($input.val() == '' || $input.hasClass(this.completedSytleClass)) {
			// 確定済みもしくは入力がからなら終了
			return;
		}

		var data = {
			q: $input.val(),
			c: self.company
		};

		if (this.jqxhr) {this.jqxhr.abort();}; // 前回の通信をキャンセル

		var res = null;
		this.jqxhr = $.ajax({
			dataType: 'json',
			url:      this.api_url + '/' + this.api_func,
			data: data,
			async: false // 非同期にしない
		}).done(function(data){
			console.log(data);
			if (data && data[0].result && data[0].result.length > 0) {
				// 候補内に入力値と同名の候補が存在した場合はそれを確定する
			    var api_list = Array.prototype.slice.call(data[0].result);
				api_list.forEach(function(element) {
					if(element.name == $input.val()) {
						res = element;
					}
				});
				// 候補内に入力値と同名の候補が存在しない場合は、リストの先頭で確定とする
				if(res == null) {
					res = data[0].result[0];
				}
			};
		});
		if (res) {
			this._selectedItem(res, false);
		};
	},

	_decision: function(val) {
		if (val) {
			this.isDecision = true;
			$(this.text_selector).addClass(this.completedSytleClass);
		} else if(this.isDecision) {
			this.isDecision = false;
			$(this.text_selector).removeClass(this.completedSytleClass);
			this.nocomplete();
		}
		// console.log(this.isDecision);
	},

	// ユーティリティ
	getInputValue: function() {
		return this._escapeString($(this.text_selector).val());
	},
	// コールバック処理でthisを設定元にする(_callの引数に渡せる)
	_call: function(func) {
		var self = this;
		var args = Array.prototype.slice.call(arguments, 1);
		return function(){func.apply(self,args);};
	},
	// コールバック処理でthisを設定元にする(_callの引数に渡せないがコールバック時に渡る引数を受け取れる)
	_callArgs: function(func) {
		var self = this;
		return function(){return func.apply(self,arguments);};
	},

	log: function(msg){
		if (this.logElement) {
			this.logElement.innerHTML = msg +"<br>";
		}
	},
	_escapeString: function(str) {
		return str.replace(/\&/g, '&amp;')
			.replace( /</g, '&lt;')
			.replace(/>/g, '&gt;')
			.replace(/\"/g, '&quot;')
			.replace(/\'/g, '&#39;');
	},
	_getEventElement: function(event) {
		return event.target || event.srcElement;
	},
	_sleep: function (waitMsec) {
		var startMsec = new Date();
		while (new Date() - startMsec < waitMsec);
	},
};

