// ==UserScript==
// @name           dA stupid
// @namespace      http://maat.fivebyfive.be/
// @include        http://*.deviantart.com/*
// ==/UserScript==

/*
	JSON from json.org/json2.js.
*/

if(!this.JSON){JSON=function(){function f(n){return n<10?'0'+n:n;}
Date.prototype.toJSON=function(key){return this.getUTCFullYear()+'-'+
f(this.getUTCMonth()+1)+'-'+
f(this.getUTCDate())+'T'+
f(this.getUTCHours())+':'+
f(this.getUTCMinutes())+':'+
f(this.getUTCSeconds())+'Z';};String.prototype.toJSON=Number.prototype.toJSON=Boolean.prototype.toJSON=function(key){return this.valueOf();};var cx=/[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,escapeable=/[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,gap,indent,meta={'\b':'\\b','\t':'\\t','\n':'\\n','\f':'\\f','\r':'\\r','"':'\\"','\\':'\\\\'},rep;function quote(string){escapeable.lastIndex=0;return escapeable.test(string)?'"'+string.replace(escapeable,function(a){var c=meta[a];if(typeof c==='string'){return c;}
return'\\u'+('0000'+
(+(a.charCodeAt(0))).toString(16)).slice(-4);})+'"':'"'+string+'"';}
function str(key,holder){var i,k,v,length,mind=gap,partial,value=holder[key];if(value&&typeof value==='object'&&typeof value.toJSON==='function'){value=value.toJSON(key);}
if(typeof rep==='function'){value=rep.call(holder,key,value);}
switch(typeof value){case'string':return quote(value);case'number':return isFinite(value)?String(value):'null';case'boolean':case'null':return String(value);case'object':if(!value){return'null';}
gap+=indent;partial=[];if(typeof value.length==='number'&&!(value.propertyIsEnumerable('length'))){length=value.length;for(i=0;i<length;i+=1){partial[i]=str(i,value)||'null';}
v=partial.length===0?'[]':gap?'[\n'+gap+
partial.join(',\n'+gap)+'\n'+
mind+']':'['+partial.join(',')+']';gap=mind;return v;}
if(rep&&typeof rep==='object'){length=rep.length;for(i=0;i<length;i+=1){k=rep[i];if(typeof k==='string'){v=str(k,value);if(v){partial.push(quote(k)+(gap?': ':':')+v);}}}}else{for(k in value){if(Object.hasOwnProperty.call(value,k)){v=str(k,value);if(v){partial.push(quote(k)+(gap?': ':':')+v);}}}}
v=partial.length===0?'{}':gap?'{\n'+gap+partial.join(',\n'+gap)+'\n'+
mind+'}':'{'+partial.join(',')+'}';gap=mind;return v;}}
return{stringify:function(value,replacer,space){var i;gap='';indent='';if(typeof space==='number'){for(i=0;i<space;i+=1){indent+=' ';}}else if(typeof space==='string'){indent=space;}
rep=replacer;if(replacer&&typeof replacer!=='function'&&(typeof replacer!=='object'||typeof replacer.length!=='number')){throw new Error('JSON.stringify');}
return str('',{'':value});},parse:function(text,reviver){var j;function walk(holder,key){var k,v,value=holder[key];if(value&&typeof value==='object'){for(k in value){if(Object.hasOwnProperty.call(value,k)){v=walk(value,k);if(v!==undefined){value[k]=v;}else{delete value[k];}}}}
return reviver.call(holder,key,value);}
cx.lastIndex=0;if(cx.test(text)){text=text.replace(cx,function(a){return'\\u'+('0000'+
(+(a.charCodeAt(0))).toString(16)).slice(-4);});}
if(/^[\],:{}\s]*$/.test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,'@').replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,']').replace(/(?:^|:|,)(?:\s*\[)+/g,''))){j=eval('('+text+')');return typeof reviver==='function'?walk({'':j},''):j;}
throw new SyntaxError('JSON.parse');}};}();}


/*
	dAstupid 0.7, CARP 0.3, and mkElement 0.1
	Copyright (C) 2008 Kalle Raisanen.

	This program is free software: you can redistribute it and/or modify
	it under the terms of the GNU Affero General Public License as published by
	the Free Software Foundation, either version 3 of the License, or
	(at your option) any later version.

	This program 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 Affero General Public License for more details.

	You should have received a copy of the GNU Affero General Public License
	along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

function mkElement(node) {
	if(typeof node == 'string')
		return document.createTextNode(node);
	else if(typeof node != 'object' || typeof node.tagname == 'undefined')
		return null;

	var el = document.createElement(node.tagname);

	if(typeof node.attributes != 'undefined') {
		for(var k in node.attributes) {
			el[k] = node.attributes[k];
		}
	}

	if(typeof node.setAttributes != 'undefined') {
		for(var k in node.setAttributes)
			el.setAttribute(k, node.setAttributes[k]);
	}

	if(typeof node.eventListeners != 'undefined') {
		for(var i = 0; i < node.eventListeners.length; i++) {
			el.addEventListener(
				node.eventListeners[i].type,
				node.eventListeners[i].listener,
				(typeof node.eventListeners[i].useCapture != 'undefined')
					? node.eventListeners[i].useCapture
					: false
			);
		}
	}

	if(typeof node.children != 'undefined') {
		for(var i = 0; i < node.children.length; i++)
			el.appendChild(mkElement(node.children[i]));
	}

	return el;
}

var CARP = {
	_carped: {}
	,prefixes: {all: '', carp: 'Warning', croak: 'Error' }
	
	,_carp: function(msg) {
		if(typeof CARP._carped[msg] == 'undefined' || msg.match(/You have already voted/)) {
			CARP._carped[msg] = true;
			alert(msg);
		}
	}

	,carp:  function(msg) {
		CARP._carp(CARP.prefixes.all + CARP.prefixes.carp +  ': ' + msg);
	}
	,croak: function(msg) {
		CARP._carp(CARP.prefixes.all + CARP.prefixes.croak + ': ' + msg);
	}
};

Date.prototype.getTimeString = function()
{
	var ds = {
		h: (this.getHours()   > 9 ? '' : '0') + this.getHours(),
		m: (this.getMinutes() > 9 ? '' : '0') + this.getMinutes(),
		s: (this.getSeconds() > 9 ? '' : '0') + this.getSeconds()
	}

	return ds.h + ':' + ds.m + ':' + ds.s;
}

var dAstupid = {
	VERSION:         '0.7.8'
	,backendVersion: '0.0.0'
	,CHUNK_SIZE:     1024 // push classify requests in 1kB chunks (in theory: it'll
	                      // actually work out to about 2kB with JSON overhead, greedy counting, etc.
	                      // And large comments are not split).
	,WARNINGS:       true
	,STRIPSIGS:      false
	,STATUS:         {ERROR: 'Error', WARNING: 'Warning', SUCCESS: 'Success' }
	,DEBUG:          false

	,comments: []
	,dA:       null
	,added_styles: []


	,_D: function(msg) {
		if(this.DEBUG && typeof console != 'undefined' && typeof console.log != 'undefined') {
			console.log(
				'<dAstupid [%s]> %s(%d): %s',
					new Date().getTimeString(),
					this._D.caller.name ? this._D.caller.name : 'anonymous-function',
					this._D.caller.length,
					msg
			);
		}
	}

	,_init: function dAstupit_init() {
		this.backendVersion = GM_getValue('das-backendVersion', this.backendVersion);

		if(unsafeWindow.deviantART && unsafeWindow.deviantART.deviant) {
			this.dA = unsafeWindow.deviantART.deviant;
			this.dA.senior =  (this.dA.loggedIn && (!this.dA.symbol.match(/[=*!~]/) ));
		} else {
			this.dA = { username: '_', senior: false };
		}
		this._D('dAstupid ' + this.VERSION + ' (backend ' + this.backendVersion + ')'
		       +' initialising for user ' + this.dA.symbol + this.dA.username + ' (' + (this.dA.senior ? 'senior' : 'user') + ')');

		var dAdiv = document.getElementById('deviantART-v6');

		if(dAdiv) {
			this._D('Found #deviantART-v6. Adding styles...');

			GM_addStyle(
				'div.thought       { padding-bottom: '+  (dAstupid.dA.senior ? '60' : '35') + 'px; } ' +
				(dAstupid.dA.senior ? 'div.thought:hover .das-container {height: 35px; } div.thought:hover .das-form { display: block; }' : '') +

				'.das-container    {  color: #000; text-align: right; position: absolute; bottom: 0; ' +
				                   '  right: 0; padding: 5px; min-width: 200px; height: 16px; }'+

				'.das-form          { font-size: 10px; height: 20px; color: #000; display: none; } ' +
				'.das-button        { font-size: inherit; padding: 1px 2px; -moz-border-radius: 8px; border: 0px outset #92A399; cursor: pointer; }  ' +
				'.das-button-stupid { background-color: #A39299 } ' +
				'.das-button-smart  { background-color: #92A399 } ' +
				'.das-info          { height: 14px; color: #fff; bottom: 5px; right: 5px; position: absolute; line-height: 16px; } ' +
				'.das-info strong   { font-size: 16px; } '
			);

			this._D('Looking for comments...');

			var commentrange = document.evaluate("//div[@class='thought block']", dAdiv, null, XPathResult.ANY_TYPE, null);

			if(commentrange) {
				var current = commentrange.iterateNext();

				while(current) {
					this.comments.push(current.id);
					current = commentrange.iterateNext();
				}

				if(this.comments.length) {
					this._D('Found ' + this.comments.length + ' comments.');

					this.add_buttons();
					this.classify_all();
				} else {
					this._D("Didn't find any comments. Quitting.");
				}
			}
		}
	}

	,_prepdata: function(data) {
		return escape(data);
	}

	,_post_json: function dAstupid_post_json(url, data, callback, alwayscall) {
		var json = this._prepdata(JSON.stringify(data));

		this._D('Sending request (' + json.length + ' bytes) to <' + url + '>');

		GM_xmlhttpRequest({
			method: 'POST',
			url:    url,
			data:   'json=' + json,
			headers: {
				'User-agent': 'Mozilla/4.0 (compatible) Greasemonkey',
				'Content-type': 'application/x-www-form-urlencoded'
			},
			onerror: function(responseDetails) {
				CARP.croak("dAstupid's backend is temporarily unavailable.");
			},
			onload: function dAstupid_post_json_onload(responseDetails) {
				var ret;
				try {
					ret = JSON.parse(responseDetails.responseText);
				} catch(e) {
					unsafeWindow._das_error = {
						error:    e,
						jsonret:  ret,
						response: responseDetails
					};
					ret = false;
				}
				unsafeWindow._das_res = responseDetails;
				dAstupid._D('Received response from <' + responseDetails.finalUrl + '>, status: ' + (ret ? ret.status : 'no status'));

				if(ret == false) {
					CARP.croak("dAstupid's backend is temporarily unavailable.");
				} else if(ret.status == dAstupid.STATUS.ERROR) {
					CARP.croak(ret.msg);
				} else if(ret.status == dAstupid.STATUS.WARNING) {
					if(dAstupid.WARNINGS)
						CARP.carp(ret.msg);
				} else if(ret.status == dAstupid.STATUS.SUCCESS) {
					if(ret.backendVersion != dAstupid.backendVersion) {
						dAstupid.backendVersion = ret.backendVersion;
						GM_setValue('das-backendVersion', ret.backendVersion);

						CARP.prefixes.all = 'dAstupid ' + dAstupid.VERSION + ' [backend ' + dAstupid.backendVersion + "]\n\n";
					}
					dAstupid._D('Calling ' + callback.name + '()');
					callback(ret);
				} else {
					CARP.croak(
						"I'm not sure, but I think the universe might've imploded. "+
					   'Please check your local emergency broadcast system for more information.'
					);
				}

				if(typeof alwayscall != 'undefined') {
					dAstupid._D('Calling ' + alwayscall.name + '()');
					alwayscall(ret);
				}
			}
		});
	}


	,add_buttons: function dAstupid_addbuttons() {
		var comments = this.comments;

		this._D('Adding dAstupid controls...');

		for(var ind = 0; ind < comments.length; ind++) {
			var cont  = document.getElementById(comments[ind]);

			var div   = mkElement({
				tagname: 'div',
				attributes: {
					id:        'container-' + comments[ind],
					className: 'das-container'
				}
			});

			if(cont) {
				cont.appendChild(div);

				if(this.dA.senior && this.dA.username != this.get_author(comments[ind])) {
					div.appendChild(
						mkElement({
							tagname: 'form',
							attributes: {
								id:        'frm-' + comments[ind],
								className: 'das-form',
								action:    '',
								method:    'get'
							},
							children: [
								'Vote: ',
								{
									tagname:       'input',
									attributes:    { className: 'das-button das-button-stupid', value: 'Stupid', type: 'button'},
									setAttributes: {'target': String(comments[ind]) },
									eventListeners: [
										{ type: 'click', listener: function(evt) { dAstupid.train(evt.target, true); } }
									]
								},
								' ',
								{
									tagname:       'input',
									attributes:    { className: 'das-button das-button-smart', value: 'Smart', type: 'button'},
									setAttributes: {'target': String(comments[ind]) },
									eventListeners: [
										{ type: 'click', listener: function(evt) { dAstupid.train(evt.target, false); } }
									]
								}
							]
						}) // mkElement
					); // cont.appendChild
				}

				div.appendChild(mkElement({
					tagname: 'div',
					attributes: {id: 'info-' + comments[ind], innerHTML: 'Getting comment score...', className: 'das-info' }
				}));
			} // if
		} // for
	}


	,classify: function dAstupid_classify(data) {
		var jdata = { version: this.VERSION, posts: data};

		this._D('Classifying ' + data.length + ' posts.');

		this._post_json(
			'http://dastupid.fivebyfive.be/classify.php',
			jdata,
			function dAstupid_classify__classify_apply(ret) {
				var res = ret.res;

				for(var i = 0; i < res.length; i++)
					dAstupid.classify_apply(res[i].id, res[i].ratio);
			}
		);
	}

	,classify_all: function dAstupid_classify_all() {
		var comments = this.comments;
		var pdata    = [];
		var limit    = this.CHUNK_SIZE;
		var len      = 2;

		for(var i = 0; i < comments.length; i++) {
			var txt   = this.get_text(comments[i]);
			var cont  = document.getElementById(comments[i]);

			len += (txt.length + comments[i].length); // Yes, I know this is inexact.
	
			pdata.push([comments[i], txt]);

			if(len > limit) {
				this._D('Sending '+ len +'bytes of data to dAstupid.classify');
				this.classify(pdata);
				pdata = [];
				len = 2;
			}

		}
		if(pdata.length) {
			this._D('Sending ' + len + ' bytes of data to dAstupid.classify');
			this.classify(pdata);
		}
	}

	,classify_apply: function dAstupid_classify_apply(id, ratio) {
		this._D('Applying classification to ' + id + ' (ratio ' + ratio + ')');

		var container = document.getElementById('info-' + id);

		if(container) {
			if (typeof this.added_styles[cl] == 'undefined') {
				var c = Math.round(0xA0 * ratio);
				var cl = 'das-' + c;
				var bl = 0x20 + (0x50 - Math.abs(c - 0x50));

				GM_addStyle('.' + cl + ' { color: rgb(' + [c,(0xA0 - c),bl].join(', ') +') !important; }' );
				this.added_styles[cl] = true;
			}
			container.className.replace(/\s*das-\d+/, '');
			container.className += ' ' + cl;

			document.getElementById('info-' + id).innerHTML = 'This comment is <strong>' + Math.round(ratio * 100) + '%</strong> stupid.';
		}
	}


	,disable_buttons: function(container, disabled) {
		var btns = container.getElementsByTagName('input');
	
		for(var i = 0; i < btns.length; i++) {
			if(btns[i].className.match(/das-button/))
				btns[i].disabled = disabled;
		}
	}


	,_authors:  {}
	,_comments: {}
	,_links:    {}

	,get_author: function dAstupid_get_author(par) {
		if(typeof this._authors[par] != 'undefined') {
			this._D('Got author, ' + this._authors[par] + ', from ' + par + ' [cached]');

			return this._authors[par]
		} else if(document.getElementById(par)) {
			var cspans = document.getElementById(par).getElementsByTagName('span');
			var author;

			for(var i = 0; i < cspans.length; i++) {
				if(cspans[i].className.match(/author/)) {
					this._authors[par] = author = cspans[i].innerHTML.replace(/<.*?>|\W/g, '');

					this._D('Got author, ' + author + ', from ' + par);

					return author;
				}
			}
		}
		return '';
	}

	,get_link: function dAstupid_get_link(par) {
		if(typeof this._links[par] != 'undefined') {
			this._D('Got url, ' + this._links[par] + ', from ' + par + ' [cached]');
			return this._links[par];
		} else if(document.getElementById(par)) {
			var cspans = document.getElementById(par).getElementsByTagName('span');
			var link;

			for(var i = 0; i < cspans.length; i++) {
				if(cspans[i].className.match(/time/)) {
					link = this._links[par] = cspans[i].getElementsByTagName('a')[0].href.replace(/^http:\/\/comments\.deviantart\.com\//, '');

					this._D('Got link, ' + link + ', from ' + par);

					return link;
				}
			}
		}
		return '';
	}

	,get_text: function dAstupid_get_text(par) {
		if(typeof this._comments[par] != 'undefined') {
			this._D('Got ' + this._comments[par].length + ' bytes of text from ' + par + ' [cached]');

			return this._comments[par]
		} else if(document.getElementById(par)) {
			var txt   = dAstupid.get_author(par) + ' ';
			var cdivs = document.getElementById(par).getElementsByTagName('div');

			for(var i = 0; i < cdivs.length; i++) {
				if(cdivs[i].className.match(/body/)) {
					txt += (dAstupid.STRIPSIGS ? String(cdivs[i].innerHTML).split(/<br>--<br>/)[0] : cdivs[i].innerHTML);
					break;
				}
			}
			txt = txt.replace(/%/, ' ');
			txt = txt.replace(/[^\x20-\x7E]/g, ''); // Remove non-printable chars

			this._comments[par] = txt;

			this._D('Got ' + txt.length + ' bytes of text from ' + par);

			return txt;
		}

		return '';
	}


	,train: function dAstupid_train(btn, stupid) {
		var id   = btn.getAttribute('target');
		var data = dAstupid.get_text(id);

		this._train(id, data, stupid);
	}

	,_train: function dAstupid__train(id, data, stupid) {
		var jdata = {
			version: this.VERSION,
			id:      id,
			data:    data,
			stupid:  stupid,
			link:    this.get_link(id)
		};

		this._D('Sending train request: { id:' + id + ', class:' + (stupid ? 'stupid' : 'smart') + '}');

		this.disable_buttons(document.getElementById(id), true);

		this._post_json(
			'http://dastupid.fivebyfive.be/train.php',
			jdata,
			function dAstupid__train__classify_apply(ret) {
				dAstupid.classify_apply(ret.id, ret.ratio);
			},
			function dAstupid__train__disable_buttons(ret) {
				dAstupid.disable_buttons(document.getElementById(ret.id), false);
			}
		);
	}
};


dAstupid._init();

