// ==UserScript==
// @name		   GmailLabelSetter
// @namespace	   mtamaki.com
// @description    GmailLabelSetter
// @include 	   http://mail.google.com/mail/*
// @include 	   https://mail.google.com/mail/*
// ==/UserScript==

(function(){
////////////////////////////////////////////////////////////////////////////////

//objの要素を一つづつ順に取り出してfuncに処理させる。funcが真を返すと処理を中断する。
function array_each( obj, func ){
	//数値：その数だけループ。引数はインデックス
	if( "number" == typeof obj ) { for( var index = 0; index < obj; ++index ) { if( func( index ) ) return } }
	//文字列：文字ごとにループ
	else if( "string" == typeof obj ) { for( var index = 0; index < obj.length; ++index ) { if( func( obj.substr( index, 1 ) ) ) return } }
	//e4x：プロパティの値ごとにループ
	else if( "xml" == typeof obj ) { for each( var element in obj ) { if( func( element ) ) return } }
	else if( obj ) {
		//配列：要素ごとにループ
		if( null != obj.length ) { for( var index = 0; index < obj.length; ++index ) { if( func( obj[index] ) ) return } }
		//オブジェクト：プロパティの名前ごとにループ
		else { for( var name in obj ) { if( func( name ) ) return } }
	}
}

////////////////////////////////////////

//配列を順に見ながら、値を返していく。コールバック関数では[戻り値本体, 列挙を中断するならtrue]を返す。
function array_each_result( array, init, func ) {
	array_each(array, function( item ){ result = func( init, item ); init = result[0]; if( 1 < result.length ) return result[1]; return false; } )
	return init
}

////////////////////////////////////////

//funcが最初にtrueを返した要素を返す。見つからなかった場合はnull。
//findした結果を加工したいときは、コールバックの中ではなく、この関数の戻り値に対して処理する。
function array_find( array, func ) {
	return array_each_result( array, null, function( result, item ){ if( func(item) ) return [item, true]; return [null] } )
}

////////////////////////////////////////

function dom_insert_before ( target, element ) {
	target.parentNode.insertBefore( element, target )
}

////////////////////////////////////////

function dom_create( name, attrs, children, events ) {
	//ref $N in fastlookupalc.user.js
	attrs = attrs || {}; events = events || {}; children = children || []
	var result = document.createElement( name );
	for( var attr in attrs )
		result.setAttribute( attr, attrs[attr] )
	for( var index = 0; index < children.length; ++index ) {
		if( "string" == typeof children[index] )
			result.appendChild( document.createTextNode( children[index] ) )
		else
			result.appendChild( children[index] )
	}
	for( var event in events )
		result.addEventListener( event, events[event], false )
	return result
}

////////////////////////////////////////

function event_observe( target_element, event_name, observer_func, capture_or_bubbling ) {
	target_element.addEventListener( event_name, observer_func, capture_or_bubbling || false );
}

////////////////////////////////////////

function http_get( url, after_func, options ) {
	var count = 0;
	if( !http_get.is_not_log ) {
		if( !http_get.count )
			http_get.count = 0
		count = http_get.count++
		GM_log( count + ": " + url )
	}
	options = options || {}
	options["url"] = url
	options["onload"] = function(details) {
		if( !http_get.is_not_log )
			GM_log( count + ": " + url + " " + details.status )
		after_func( details.responseText, details )
	}
	options["headers"] = options["headers"] || {}
	if( options["data"] ) {
		options["method"] = "POST"
			options["headers"]["Content-Type"] = "application/x-www-form-urlencoded"
	}
	else
		options["method"] = "GET"
	GM_xmlhttpRequest(options)
}

////////////////////////////////////////

function gmail_ready( callback ) {
	event_observe(window, 'load',
		function() {
			if (unsafeWindow.gmonkey)
				unsafeWindow.gmonkey.load('1.0', callback);
		}
	)
}

////////////////////////////////////////

function gmail_get_form_data( key ) {
	var match = array_find( document.body.childNodes, function( node ){ return ("FORM"==node.tagName) } ).getAttribute( "action" ).match(new RegExp( key + "=([^&]+)" ))
	if( match )
		return match[1]
	return null
}

////////////////////////////////////////

function gmail_get_ik( callback ) {
	if( !gmail_get_ik.ik ) {
		http_get( location.protocol + "//" + location.host + "/mail/", function(result){ gmail_get_ik.ik = result.match( /ID_KEY:"([^"]+)"/)[1]; callback( gmail_get_ik.ik )  } )
	}
	else
		callback( gmail_get_ik.ik )
}

////////////////////////////////////////

//現在表示されているメールスレッドの先頭のメールのIDを取得する
function gmail_get_current_mail_thread_id() {
	//基本的にURLの末尾がメールスレッドの先頭のメールID
	var th = window.parent.location.href.split("/")
	th = th[th.length-1]
	//たまにURLがルートのままのときがある。
	if( !th.match(/[0-9a-z]+/i) ) {
		if( confirm( "can't get mail thread id. reload?" ) )
			window.parent.location.href = location.protocol + "//mail.google.com"
	}
	return th
}

////////////////////////////////////////

function gmail_check_thread_id( gmail ) {
	//ビューが切り替わったのにURLが変わってないときには正しくスレッドIDが取れないのでリロードを促す。
	var state = function(){}
	state = function() {
		//まず、最初のURLとビュータイプを保存。
		var view = gmail.getActiveViewType()
		var url = window.parent.location.href
		state = function(){
			//次に、ビュータイプが変わった時にURLが同一ならおかしい。
			if( view != gmail.getActiveViewType() && url == window.parent.location.href ) {
				if( confirm( "can't get thread id. reload?" ) )
					window.parent.location.href = location.protocol + "//mail.google.com"
			}
			else
				//変わってたらチェック終了
				state = function(){}
		}
	}
	gmail.registerViewChangeCallback(
		function () {
			state()
		}
	);
}
////////////////////////////////////////

function dom_insert_after( target, element ) {
	if( !target.nextSibling )
		target.parentNode.appendChild( element )
	else
		target.parentNode.insertBefore( element, target.nextSibling )
}

////////////////////////////////////////

function cookie_get( key )
{
	var obj = document.cookie.split("; ")
	for( var index = 0; index < obj.length; ++index )
	{
		var key_value = obj[index].split( "=" )
		if( key == key_value[0] )
			return decodeURIComponent( key_value[1] )
	}
	return null
}

////////////////////////////////////////

function gmail_get_at(){
	return cookie_get("GMAIL_AT")
}

////////////////////////////////////////

function gmail_title_get( gmail ){
	//はじめにビューの要素を得る
	var element = gmail.getActiveViewElement()
	//子が複数あるdivの最初のdivまでたどる。これがメールの上のナビゲーションバー
	while( 1 == element.childNodes.length )
		element = element.childNodes[0]
	element = element.childNodes[0]
	//その次の弟がメール本文のTABLE
	element = element.nextSibling
	//TABLEの子のうち一番初め出てくるH1がタイトル領域
	element = element.getElementsByTagName("H1")[0]
	//タイトル領域の最初の子SPANがタイトル
	return element.childNodes[0]
}

////////////////////////////////////////

function gmail_set_label( target_th, label_name ) {
	var url = location.protocol + "//" + location.host + "/mail/?ui=2&ik=" + gmail_get_ik() + "&at=" + gmail_get_at() + "&view=up&act=ac_" + label_name + "&rt=j"
	var search = gmail_get_form_data( "search" )
	var value = gmail_get_form_data( search )
	if( value )
		url += "&" + search + "=" + value
	url += "&search=" + search;
	http_get( url,
		function( result ) {},
		{ "data" : "t=" + target_th + "&"}
	)
}

////////////////////////////////////////////////////////////////////////////////

//ラベルをロード
//registerViewChangeCallbackのコールバックで呼ぶとGM_getValueがエラーになるのであらかじめ読んでおく
var labels = GM_getValue( "preset_labels" )
if( labels )
	labels = labels.split(",")
else
	labels = []

//gmailが初期化されたときにアタッチ
gmail_ready(
	function( gmail ) {
		gmail_check_thread_id( gmail )
		//ビューが変更されたときにアタッチ
		gmail.registerViewChangeCallback(
			function () {
				//メール表示中なら処理する
				if( "cv" == gmail.getActiveViewType() ) {
					//ラベルのリンクをメールタイトルの横に追加する

					//リンクを作成する( リンクのテキスト, リンクがクリックされたときに起動する関数 )
					function create_element( name ){
						return dom_create("span", {"style":"margin-right:1ex;color:gray;font-size:small;font-weight:normal;"}, [name])
					}

					function create_link( name, on_click ){
						var element = create_element( name )
						element.setAttribute( "style", element.getAttribute( "style" ) + "cursor:pointer;" )
						event_observe(element, "click", on_click )
						return element
					}


					//追加先であるメールのタイトルの要素を得る
					var title_element = gmail_title_get( gmail )

					//ラベル追加用リンク要素を作成
					var add_element = create_link( "+",
						function(){
							var label = prompt("please enter preset label name.")
							if( label ){
								labels.push(label)
								GM_setValue( "preset_labels", labels.join(",") )
								insert_label_link( label )
							}
						}
					)

					//LabelSetter用Divの作成
					var div_title = create_element("LabelSetter:")
					var label_setter_div = dom_create("div", {}, [div_title, add_element])

					//ラベルのリンクを追加するユーティリティ( ラベルの名前 )
					function insert_label_link( name ){
						label_setter_div.appendChild(
							create_link( "[" + name + "]",
								function(){
									//ラベル付けコマンド用urlを作成
									gmail_set_label( gmail_get_current_mail_thread_id(), name )
								}
							)
						)
					}

					//ラベルを追加するリンクを挿入
					//登録されているラベルのリンクを全て追加
					array_each( labels,
						function( label ){
							insert_label_link( label )
						}
					)

					//LabelSetterのDivを挿入
					dom_insert_before( title_element.parentNode, label_setter_div )
				}
			}
		);
	}
)

////////////////////////////////////////////////////////////////////////////////
})()
