// ==UserScript==
// @name NicoSkip
// @namespace  mtamaki.com
// @include  http://www.nicovideo.jp/watch/*
// ==/UserScript==

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

function dom_get(id) {
	return document.getElementById(id)
}

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

function array_each(obj, func) {
	if('number' == typeof obj)
		for(var index = 0; index < obj; ++index) { if(func(index)) return }
	else if(undefined != obj.length)
		for(var index = 0; index < obj.length; ++index) { if(func(obj[index], index)) return }
	else
		for(var name in obj) { if(func(obj[name], name)) return }
}

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

function array_each_result(array, init, func) {
	array_each(array, function(value, key) { result = func(init, value, key); init = result[0]; if(1 < result.length) return result[1]; return false; })
	return init
}

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

function array_reduce(array, init, func) {
	return array_each_result(array, init, function(result, value, key) { return [func(result,value, key)]; })
}

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

function array_map(array, func) {
	return array_reduce(array, [], function(results, value, key) { results.push(func(value, key)); return results })
}

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

function array_filter(array, func) {
	return array_reduce(array, [], function(results, value, key) { var result = func(value, key); if(result) results.push(result); return results })
}

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

function dict_create(array) {
	return array_reduce(array, {}, function(results, value) { results[value[0]] = value[1]; return results })
}

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

function string_parse_query(queryies_str) {
	return array_reduce(queryies_str.split('&'), {}, 
		function(results, query, index) {
			var key_value = query.split('=')
			results[decodeURIComponent(key_value[0])] = decodeURIComponent(key_value[1])
			return results
		}
	)
}

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

function http_get(url, options, after_func) {
	if(!after_func) {
		after_func = options
		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['url'] = options['url'] || url
	options['onload'] = 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'] = options['method'] || 'POST'
		options['headers']['Content-Type'] = options['headers']['Content-Type'] || 'application/x-www-form-urlencoded'
	} else
		options['method'] = options['method'] || 'GET'
	GM_xmlhttpRequest(options)
}

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

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

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

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 dom_insert_before (target, element) {
	target.parentNode.insertBefore(element, target)
}

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

function cookie_get(key, default_value) {
	var matched = document.cookie.match(new RegExp("(^| )" + encodeURIComponent(key) + "=([^;]*)"))
	if(matched)
		return decodeURIComponent(matched[2])
	return default_value || ""
}

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

function cookie_set(key, value, expires) {
	document.cookie = encodeURIComponent(key || location.href) + "=" + encodeURIComponent( value ) + "; expires=" + (new Date( new Date().getTime() + (60 * 60 * 24 * 1000 * (expires || 30)))).toGMTString()
}

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

function form_get(element) {
	if( "SELECT" == element.tagName ) {
		if( -1 == element.selectedIndex )
			return ""
		return element.options[element.selectedIndex].text
	}
	if( "checkbox" == element.type )
		return element.checked
	return element.value
}

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

function form_set(element, value) {
	if(!element)
		return;
	if("SELECT" == element.tagName) {
		for( var index = 0; index < element.options.length; ++index )
		{
			if( value == element.options[index].text )
			{
				element.selectedIndex = index
				break;
			}
		}
	}
	else if("checkbox" == element.type){
		if( "true" == value )
			element.checked = value
		else
			element.removeAttribute( "checked" )
	}
	else
		element.value = value;
}

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

function cookie_bind(element, default_value, key) {
	key = key || location.href + '#' + element.id
	form_set(element, cookie_get(key, default_value))
	element.addEventListener('change',
		function(event) {
			cookie_set(key, form_get(element))
		},
		false
	)
}

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

function wait(is_ready, ready_func, wait_time) {
	if( is_ready() )
		ready_func()
	else
		setTimeout(function(){ wait(is_ready, ready_func, wait_time) }, wait_time || 100)
}

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

function set_is_contain( set, value ) {
	return set.set.hasOwnProperty((typeof value) + "_" + value);
}

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

function set_insert( set, value ) {
	if( !set.set )
		set.set = {}
	var value_name = (typeof value) + "_" + value;
	if( !set.set.hasOwnProperty( value_name ) ) {
		set.set[value_name] = true;
		set.push(value)
	}
	return set
}

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

function set_create(array) {
	return array_reduce(array, [], function(results, value){ return set_insert(results, value) })
}

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

function style_add(selector, property) {
	//ref: http://bmky.net/diary/log/1342.html
	GM_addStyle( selector + "{" + property + "}" );
}

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

window.addEventListener('load',
	function() {
		var id = 'nicoskip'
		if(dom_get(id))
			return
		var div = dom_create('div', {'id':id})
		dom_insert_before(dom_get('WATCHFOOTER'), div)

		var flvplayer = null
		wait(
			function(){ return (flvplayer = unsafeWindow.document.getElementById('flvplayer')) },
			function(){
				var queries = {}
				wait(
					function(){
						queries = string_parse_query(flvplayer.GetVariable('o'))
						return queries['ms']
					},
					function(){
						http_get(queries['ms'], {
								method: 'POST',
								data: '<thread res_from="-1000" version="20061206" thread="' + queries['thread_id'] + '" />',
								headers: {'Content-Type': 'text/xml'}
							},
							function(result) {
								var responseXML = (new DOMParser).parseFromString(result, 'application/xml');
								var comments = responseXML.getElementsByTagName('chat');
								var clip = dom_create('input', {'type': 'text', 'style': 'width:' + (77-5) + 'px; display: block; float: left; margin-left: 16px;', 'id': 'nicoskip_clip'})
								div.appendChild(clip)
								cookie_bind(clip, "1/2", "nicoskip_clip")
								var clip_div = null
								function on_clip_change(){
									if(clip_div)
										clip_div.style.display = 'none'
									
									var new_clip_div = dom_create('div')
									clip_div = new_clip_div
									div.appendChild(new_clip_div)
									scrollTo(new_clip_div.style.clientLeft, new_clip_div.style.clientTop)
									
									//
									var length = null
									wait(
										function(){ return (length = parseInt(flvplayer.GetVariable('ContentLength'))) },
										function(){
											var clip_length = clip.value
											var matched = clip.value.match(/([0-9]+)\/([0-9]+)/)
											if(matched)
												clip_length = length * (parseFloat(matched[1]) / parseFloat(matched[2]))
											
											var clip_max_span = clip_length / 10
											if( clip_max_span < 20 )
												clip_max_span = 20

											var clip_span = length
											var span_count = 1
											while( clip_max_span < clip_span )
												clip_span = length / (++span_count)

											var DIVISION = span_count
											var counts = array_map(span_count, function(){ return 0; })
											var vlmsec = length * 100
											for(var i = 0, len = comments.length; i < len; i++) {
												var pmsec = comments[i].getAttribute('vpos');
												if(vlmsec > pmsec) {
													counts[~~(pmsec / vlmsec * DIVISION)]++;
												} else if(vlmsec == pmsec){
													counts[DIVISION-1]++;
												}
											}
											var count_span = array_filter(counts, function(value, index){ return [value, index] } )
											count_span.sort(function(r, l){ return r[0] < l[0] })
											count_span.length = parseInt(clip_length / clip_span)
											var play_spans = set_create(array_map(count_span, function(value, index){ return value[1]}))
											
											var span_width = (165-2) / span_count
											style_add('#nicoskip span', 'height: 1.3em; overflow-x:hidden;display: block; float: left; padding: 0px; margin: 0px; width: ' + span_width + 'px')
											style_add('#nicoskip span.play', 'background-color: skyblue; color: skyblue')
											style_add('#nicoskip span.noplay', 'background-color: gray; color: gray')
											array_each(span_count,
												function(index){
													var span = dom_create('span', {'class': set_is_contain(play_spans, index) ? 'play' : 'noplay'}, ['　　'])
													clip_div.appendChild(span)
												}
											)
											var id = 'nicovideoskip_is_enable'
											var enable_check = dom_create('input', {'type': 'checkbox', 'id': id})
											clip_div.appendChild(enable_check)
											cookie_bind(enable_check, true, id)
											function on_enable_check(){
												//var color = enable_check.checked ? 'gray' : 'skyblue'
												//style_add('#nicoskip span.noplay', 'background-color: ' + color + '; color: ' + color)
											}
											on_enable_check()
											enable_check.addEventListener('change', on_enable_check, false)

											
											var watch_interval = (length / span_count / 10) * 1000
											if(watch_interval < 1)
												watch_interval = 1
											var pre_index = 0
											var max_diff = watch_interval*2/1000
											var pre_time = -max_diff / 8
											var is_skip_suppress = false
											var interval_id = setInterval(
												function(){
													if(!enable_check.checked)
														return
													if('none' == new_clip_div.style.display)
														return
													
													var moved_time = flvplayer.GetVariable('moved_time')
													var percent_video_moving = moved_time / length;
													var current_index = parseInt(percent_video_moving * span_count)

													if( pre_index != current_index ) {
														var index = current_index
														while(!set_is_contain(play_spans, index) && index < span_count - 1)
															++index
														if(index != current_index) {
															var jump_time = index * clip_span
															//console.log(jump_time, length)
															if( moved_time < length - clip_span){
																if(!is_skip_suppress){
																	flvplayer.SetVariable("player.playheadTime", jump_time - 5)
																	is_skip_suppress = true
																	setTimeout(function(){is_skip_suppress = false}, 1000*10)
																}
															}
															pre_time = jump_time
														}
														pre_index = index
													}
												},
												watch_interval
											)
										}
									)
								}
								clip.addEventListener('change',on_clip_change, false)
								dom_insert_after(div, dom_create('div',{},['　']))
								on_clip_change()
							}
						)
					}
				)
			}
		)
	},
	false
)

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