/*
//
// JSActions script
// capture.atom.js
//
// Original auther:  KUMAGAI Kentaro <ku@ido.nu>
//
// Released under the Creative Commons Attribution-ShareAlike licence.
// http://creativecommons.org/licenses/by-sa/3.0/
//
*/

var SCRIPT_VERSION = "0.0.2b";
var DEBUG_TRACE = 0;

function trace() {

	if ( (typeof DEBUG_TRACE != 'undefined') && ! DEBUG_TRACE )
		return;

	var fn;
	if ( typeof Firebug == 'undefined' ) {
		fn = console;
	} else if ( Firebug && Firebug.Console && Firebug.Console.log  ) {
		fn = Firebug.Console;
	}

	if ( fn ) {
		if ( arguments.length == 1 ) {
			fn.log(arguments[0]);
		} else {
			var args = [];
			for (var i = 0; i < arguments.length; i++) {
				args.push(arguments[i]);
			}
			fn.log(args);
		}
	}
}

String.prototype.qq = function ( ) {
	return '"' + this.toString() + '"';
}

Function.prototype.bind = function(object) {
  var __method = this;
    return function() {
	    __method.apply(object, arguments);
	}
}

var capture_phase = true;

var ImageSaver = {
	VERSION: SCRIPT_VERSION,
	from: null,
	to: null,
	rect: null,
	onkeypress: null,
	onmousedown: null,
	onmousemove: null,
	status: null,
	style: null,
	dim: {
			x: {prop: 'width', origin: 'left'},
			y: {prop: 'height', origin: 'top'}
	},

	update_rect: function (e) {
		var to = {x: e.clientX, y: e.clientY};

		var x, y;
		for ( var n in this.dim ) {
			var v = this.dim[n];
			var t;
			if ( ( t = to[n] - this.from[n] ) < 0 ) {
				this.rect.style[v.origin] = (this.from[n] + t) + 'px';
			}
			this.rect.style[v.prop] = Math.abs(t) + 'px';
		}
			
		this.rect.style.height = (to.y - this.from.y ) + 'px';
	},

	cancel_capture: function (e) {
		this.stop_event_listening();

		this.show_text('');
		this.close_text_delayed('Canceled', 3 * 1000);
	},

	stop_event_listening: function () {
		document.removeEventListener( 'click', this.onclick, capture_phase );
		document.removeEventListener( 'keypress', this.onkeypress, capture_phase );
		document.removeEventListener( 'mousedown', this.onmousedown, capture_phase );
		if ( this.onmousemove ) { 
			document.removeEventListener( 'mousemove', this.onmousemove, capture_phase );
		}
		this.style.parentNode.removeChild( this.style );

		if ( this.rect ) {
			this.rect.parentNode.removeChild(this.rect);
		}
	},

	select_rect: function (e) {
		if ( this.from == null ) {
			this.from = {x: e.clientX, y: e.clientY};

			this.rect = document.createElement('div');
			this.rect.style.outlineWidth = '2px';
			this.rect.style.outlineStyle = 'dotted';
			this.rect.style.outlineColor = '#0063DC';
			this.rect.style.backgroundColor = '#0063DC';
			this.rect.style.opacity = '0.5';
			this.rect.style.position = 'fixed';
			this.rect.style.width = '0px';
			this.rect.style.height = '0px';
			this.rect.style.zIndex = 2100000000;
			this.rect.style.top = this.from.y + 'px',
			this.rect.style.left = this.from.x + 'px',
			document.body.appendChild(this.rect);

			document.addEventListener( 'mousemove', this.onmousemove, capture_phase );
		} else if ( this.to == null ) {
			this.to = {x: e.clientX, y: e.clientY};

			var rect = {};
			["top", "left", "width", "height"].forEach( function(n) {
				rect[n] = parseInt( this.rect.style[n] );
			}.bind(this) );

			this.stop_event_listening();
			this.copy( rect );
		}
		return false;
	},
	onclick: function (e) {
		e.preventDefault();
		return false;
	},

	key_pressed: function(e) {
		if ( e.keyCode == e.DOM_VK_ESCAPE ) {
			this.cancel_capture();
			return false;
		}
		return true;
	},

	start_select: function () {
		this.onkeypress = this.key_pressed.bind(this);
		this.onmousemove = this.update_rect.bind(this);
		this.onmousedown = this.select_rect.bind(this);
		document.addEventListener( 'click', this.onclick, capture_phase );
		document.addEventListener( 'keypress', this.onkeypress, capture_phase );
		document.addEventListener( 'mousedown', this.onmousedown, capture_phase );

		var s = document.createElement('style');
		s.innerHTML = '* {cursor: crosshair !important;}';
		document.body.appendChild(s);
		this.style = s;
	},

	show_text: function (text) {
		var d = document.createElement('div');
		d.innerHTML = text;
		d.style.position = 'fixed';
		d.style.top = '2px';
		d.style.right = '2px';
		d.style.color = 'white';
		d.style.backgroundColor = '#E72D89';
		d.style.padding = '5px';
		d.style.zIndex = 2100000000;

		document.body.appendChild(d);
		this.status = d;
	},
	close_text_delayed: function (html, duration) {
		duration = duration || 20 * 1000;

		this.status.innerHTML = html;
		this.status.style.backgroundColor = '#0063DC';
		window.setTimeout( function () {
			this.status.parentNode.removeChild( this.status );
		}.bind(this), duration );
	},

	copy: function ( rect ) {
		var canvas = document.createElement('canvas');
		canvas.setAttribute("id", "svcanvas");
		canvas.style.display = "inline";
		canvas.width = rect.width;
		canvas.height = rect.height;

		var x = window.content.document.width;
		var y = window.content.document.height;

		var statusbar = ( top.document.getElementById('status-bar') );
		var body = ( document.getElementsByTagName('body') );

		var p = body[0];
		p.appendChild(canvas);

		var ctx = canvas.getContext("2d");
		ctx.clearRect(0, 0, rect.width, rect.height);
		//ctx.clearRect(0, 0, x, y);
		//trace( rect.width + " * " + rect.height);
		ctx.save();
		ctx.scale(1.0, 1.0);

		var x1 = rect.left + window.pageXOffset;
		var x2 = rect.left + rect.width + window.pageXOffset;
		var y1 = rect.top + window.pageYOffset;
		var y2 = rect.top + rect.height + window.pageYOffset;

		ctx.drawWindow(window.content, x1, y1, x2, y2, "rgb(255,255,255)");
		ctx.restore();

		try {
			var url = canvas.toDataURL("image/png");
		} catch(ex) {
			return alert("This feature requires Firefox 2.0.\n" + ex);
		}
		const IO_SERVICE = Components.classes['@mozilla.org/network/io-service;1']
				.getService(Components.interfaces.nsIIOService);
		url = IO_SERVICE.newURI(url, null, null);

		var channel = IO_SERVICE.newChannelFromURI( url );
		var input = channel.open();
		var bstream = Components.classes["@mozilla.org/binaryinputstream;1"]
			.createInstance(Components.interfaces.nsIBinaryInputStream);
		bstream.setInputStream(input);
		
		var base64_image = '';
		var n;
		while ( n = bstream.available() ) {
			b = bstream.readBytes(n);
			base64_image += SBME.toBase64( b );
			break;
		}

		var atom = SBME.newAtomAPI( this.conf );

		this.show_text( 'Saving...' );


		var uc = Components.classes["@mozilla.org/intl/scriptableunicodeconverter"]
						.createInstance(Components.interfaces.nsIScriptableUnicodeConverter);
		uc.charset = 'UTF-8';
		var title = uc.ConvertFromUnicode(window.content.document.title);

		atom.create(
			{
				title: {
					TEXT: title
				},
				content: {
					mode: "base64",
					type: "image/png",
					TEXT: base64_image
				}
			}, function (xml, channel) {
				var a = arguments;

				var atomns = new Namespace('http://purl.org/atom/ns#');
				channel.QueryInterface(Components.interfaces.nsIHttpChannel);
				
				// FIXME: errors not handled correctly.
				var html;
				if ( channel.requestSucceeded ) {
					var href = xml..atomns::link.(@rel == "alternate").@href;
					html = '<a href="' + href + '" style="color: white;">' + channel.responseStatusText + '</a>';
				} else {
					html = channel.responseStatusText;
				}

				this.close_text_delayed(html);
			}.bind(this)
		);

		canvas.style.display = "none";
		canvas.width = 1;
		canvas.height = 1;
	}
};


new function () {
	var file = Components.classes["@mozilla.org/file/local;1"]
					.createInstance(Components.interfaces.nsILocalFile);

	var script_loader = Components.classes["@mozilla.org/moz/jssubscript-loader;1"]
									 .createInstance(Components.interfaces.mozIJSSubScriptLoader);

	const IO_SERVICE = Components.classes['@mozilla.org/network/io-service;1']
					.getService(Components.interfaces.nsIIOService);
	
	var folder = JsActions.scriptFolderPath || JsActions.getScriptFolder();
	file.initWithPath( folder  );
	file.append( 'capture.atom.conf' );
	var entryURI = IO_SERVICE.newFileURI(file).spec;

	try {
		var conf = script_loader.loadSubScript( entryURI, window );
		ImageSaver.conf = conf;
		ImageSaver.start_select();
	} catch (e) {
		trace(e);
	}								     

};



/**
 * (c) amachang
 * licence http://twitter.com/amachang/statuses/199049982
 * script available on http://usrb.in/svn/sbme/trunk/sbme@usrb.in/chrome/content/util.js
 * slightly modified by ku.
 **/ 
var SBME = {
	merge: function(dst) {
		for (var n in dst) this[n] = dst[n];
	},
	debug: function(text) {
		// TODO: DELETEME !! 超危険！！後で消す！
		window.content.wrappedJSObject.SBME = SBME;
	},
	getService: function(url, interface) {
		return Components.classes[url].getService(Components.interfaces[interface]);
	},
	createInstance: function(url, interface) {
		return Components.classes[url].createInstance(Components.interfaces[interface]);
	},
	QI: function(instance, interface) {
		return instance.QueryInterface(Components.interfaces[interface]);
	},
	newURI: function(uri) {
		const IO_SERVICE = Components.classes['@mozilla.org/network/io-service;1']
				.getService(Components.interfaces.nsIIOService);
		return IO_SERVICE.newURI(uri, null, null);
	},
	newChannel: function(uri) {
		const IO_SERVICE = Components.classes['@mozilla.org/network/io-service;1']
				.getService(Components.interfaces.nsIIOService);
		return IO_SERVICE.newChannelFromURI(SBME.newURI(uri, null, null));
	},
	setMethod: function(channel, method) {
		SBME.QI(channel, 'nsIHttpChannel').requestMethod = method.toUpperCase();
	},
	setPostData: function(channel, data) {
		var stream = SBME.createInstance('@mozilla.org/io/string-input-stream;1', 'nsIStringInputStream');
		stream.setData(data, data.length);
		SBME.QI(channel, 'nsIUploadChannel').setUploadStream(stream, 'application/x-www-form-urlencoded', -1);
		SBME.setMethod(channel, 'POST');
	},
	setHeaderData: function(channel, headers) {
		channel = SBME.QI(channel, 'nsIHttpChannel');
		for (var n in headers) {
			if (headers[n].constructor.name == 'Array') {
				headers[n].forEach(function(header) {
					channel.setRequestHeader(n, header, false);
				});
			}
			else {
				channel.setRequestHeader(n, headers[n], false);
			}
		}
	},
	setCookieData: function(channel, cookies) {
		SBME.setHeaderData(channel, { Cookie: cookies });
	},
	setReferrerData: function(channel, uri) {
		SBME.QI(channel, 'nsIHttpChannel').referrer = SBME.newURI(uri, null, null);
	},
	toBase64: function(data) {
		if (typeof data == 'string') return btoa(data);
		const toBase64Table = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
		const base64Pad = '=';

		var result = '';
		var length = data.length;

		for (var i = 0; i < (length - 2); i += 3) {
			result += toBase64Table[data[i] >> 2];
			result += toBase64Table[((data[i] & 0x03) << 4) + (data[i+1] >> 4)];
			result += toBase64Table[((data[i+1] & 0x0f) << 2) + (data[i+2] >> 6)];
			result += toBase64Table[data[i+2] & 0x3f];
		}

		if (length%3) {
			i = length - (length%3);
			result += toBase64Table[data[i] >> 2];
			if ((length%3) == 2) {
				result += toBase64Table[((data[i] & 0x03) << 4) + (data[i+1] >> 4)];
				result += toBase64Table[(data[i+1] & 0x0f) << 2];
				result += base64Pad;
			}
			else {
				result += toBase64Table[(data[i] & 0x03) << 4];
				result += base64Pad + base64Pad;
			}
		}
		return result;
	},
	setWsseData: function(channel, username, password) {
		var created = SBME.iso8601Date(new Date());
		var nonce = (Math.random() + "").substr(2, 32);
		var data = nonce + created + (password || '');

		var converter = SBME.createInstance(
			'@mozilla.org/intl/scriptableunicodeconverter', 'nsIScriptableUnicodeConverter');
		converter.charset = 'UTF-8';
		data = converter.convertToByteArray(data, {});
		var ch = SBME.createInstance('@mozilla.org/security/hash;1', 'nsICryptoHash');
		ch.init(ch.SHA1);
		ch.update(data, data.length);
		var data = ch.finish(false);

		var passwordDigest = SBME.toBase64(data);
		nonce = SBME.toBase64(nonce);

		var wsse = 'UsernameToken Username="' + username + '", PasswordDigest="' + passwordDigest + '", Created="' + created + '", Nonce="' + nonce + '"';

		SBME.setHeaderData(channel, { 'Authorization': 'WSSE profile="UsernameToken"', 'X-WSSE': wsse });
	},
	httpRequest: function(opts) {
		var uri          = opts.uri;
		var post         = opts.post;
		var cookies      = opts.cookies;
		var refer        = opts.refer;
		var callback     = opts.callback;
		var wsseUsername = opts.wsseUsername;
		var wssePassword = opts.wssePassword;
		var headers      = opts.headers;
		var method       = opts.method;
		var channel      = SBME.newChannel(uri);

		if (refer)        SBME.setReferrerData(channel, refer  );
		if (post)         SBME.setPostData    (channel, post   );
		if (cookies)      SBME.setCookieData  (channel, cookies);
		if (wsseUsername) SBME.setWsseData    (channel, wsseUsername, wssePassword);
		if (method)       SBME.setMethod      (channel, method );
		if (headers)      SBME.setHeaderData  (channel, headers);

		channel.asyncOpen({
			onStartRequest: function(request, context) {
				this.data = "";
			},
			onDataAvailable: function(request, context, stream, sourceOffset, length){
				var sStream = SBME.createInstance('@mozilla.org/scriptableinputstream;1', 'nsIScriptableInputStream');
				sStream.init(stream);
				this.data += sStream.read(length);
			},
			onStopRequest : function (request, context, status) {
				callback(this.data, request);
			}
		}, null);
	},
	newXmlRpc: function(uri, methodName) {

		function parseNumber(arg) {
			if (parseInt(arg) == arg) return <i4>{arg}</i4>;
			else                      return <double>{arg}</double>;
		}

		function parseBoolean(arg) {
			return <boolean>{arg ? 1 : 0}</boolean>;
		}

		function parseString(arg) {
			return <string>{arg}</string>;
		}

		function parseDate(arg) {
			return <dateTime.iso8601>{SBME.iso8601Date(arg)}</dateTime.iso8601>;
		}

		function parseArray(arg) {
			var array = <array />;
			arg.forEach(function(e) {
				array.appendChild(parse(e));
			});
			return array;
		}

		function parseObject(arg) {
			var struct = <struct />;
			for (var n in arg) {
				var member = <member />
				member.appendChild(<name>{n}</name>);
				member.appendChild(parse(arg[n]));
				struct.appendChild(member);
			}
			return struct;
		}

		function parse(arg) {

			var param;
			switch (typeof arg) {

				case 'number':  param = parseNumber(arg);  break;
				case 'boolean': param = parseBoolean(arg); break;
				case 'string':  param = parseString(arg);  break;

				default:
					switch(arg.constructor.name) {
						case 'Number':  param = parseNumber(arg);  break;
						case 'Boolean': param = parseBoolean(arg); break;
						case 'String':  param = parseString(arg);  break;
						case 'Date':    param = parseDate(arg);    break;
						case 'Array':   param = parseArray(arg);   break;
						default:        param = parseObject(arg);  break;
					}
					break;
			}

			var result = <value />;
			result.appendChild(param);
			return result;

		}

		function unparse(value) {
			var value = value.children()[0];
			switch(value.localName()) {
				case 'i4': case 'int': case 'double':
					value = +(value.text().toString());
					break;
				case 'string':
					value = value.text().toString();
					break;
				case 'dateTime.iso8601':
					value.text().toString().match(/^(\d{4})-?(\d{2})-?(\d{2})T(\d{2}):?(\d{2}):?(\d{2})/);
					new Date(Date.UTC(RegExp.$1, RegExp.$2-1, RegExp.$3, RegExp.$4, RegExp.$5, RegExp.$6));
					break;
				case 'array':
					var a = [];
					for (var n in value.value) {
						a.push(unparse(value.value));
					}
					value = a;
					break;
				case 'struct':
					var o = {};
					for (var n in value..member) {
						o[value..member[n].name.text().toString()] = unparse(value..member[n].value);
					}
					value = o;
					break;
				default:
					value = undefined;
					break;
			}
			return value;
		}

		return function(/* ..., opts */) {

			var methodXML = <methodCall>
					<methodName>{methodName}</methodName>
					<params></params>
				</methodCall>;

			var opts = Array.pop(arguments);

			switch(typeof opts) {
				case 'function':
					opts = { callback: opts };
					break;
				case 'string':
				case 'number':
				case 'boolean':
					Array.push(arguments, opts);
					opts = {};
					break;
			}

			Array.forEach(arguments, function(arg) {
				var param = <param />;
				param.appendChild(parse(arg));
				methodXML.params.appendChild(param);
			});

			opts.uri = uri;
			opts.post = methodXML.toString();
			opts.headers = {
				'Content-Type': 'text/xml'
			};
			if(opts.callback) {
				var callback = opts.callback;
				opts.callback = function(data, req) {
					if(data.match(/^<\?xml/)) {
						data = data.replace(/<\?xml.*?\?>/, '');
					}
					var data = new XML(data);
					var params = data.params.param;
					var args = [];

					for (var n in params) {
						args.push(unparse(params[n].value));
					}
					callback.apply(null, args);
				}
			}

			SBME.httpRequest(opts);
		};
	},
	newAtomAPI: function(opts) {
		var username         = opts.username;
		var password         = opts.password;
		var postUri          = opts.postUri;
		var editUri          = opts.editUri;
		var editFromEntryUri = opts.editFromEntryUri
		if (typeof editUri == 'function') {
			var editUriFactory = opts.editUri
		}
		else {
			var editUriFactory = function(id) { return editUri + id; }
		}
		if (typeof editFromEntryUri == 'function') {
			var editFromEntryUriFactory = opts.editFromEntryUri
		}
		else {
			var editFromEntryUriFactory = function(uri) { return editFromEntryUri + uri; }
		}

		return {
			_createEntryData: function(content) {
				var text = null;
				var tags = [ '<entry xmlns="http://purl.org/atom/ns#" >' ];
				for ( var tagname in content ) {
					var pairs = [];
					var attributes = content[tagname];
					var contentText = null;
					for ( var attr in attributes ) {
						if ( attr == 'TEXT' ) {
							contentText = attributes[attr];
						} else {
							pairs.push( attr + '=' + attributes[attr].qq() );
						}
					}
					pairs.unshift(tagname);
					tags.push( '<' + pairs.join(" ") + '>' + contentText +
								'</' + tagname + '>' );
				}

				tags.push('</entry>');
				var ret = tags.join("\n");
				return ret;
			},
			create: function(content, callback) {
				var uri = postUri;
				this._internal(uri, 'POST', this._createEntryData(content), callback);
			},
			read: function(id, callback) {
				var uri = editUriFactory(id);
				this._internal(uri, 'GET', undefined, callback);
			},
			update: function(id, opts, callback) {
				var uri = editUriFactory(id);
				this._internal(uri, 'PUT', this._createEntryData(opts), callback);
			},
			delete: function(id, callback) {
				var uri = editUriFactory(id);
				this._internal(uri, 'DELETE', undefined, callback);
			},
			readFromEntry: function(uri, callback) {
				var uri = editFromEntryUriFactory(uri);
				this._internal(uri, 'GET', undefined, callback);
			},
			_internal: function(uri, method, post, callback) {
				SBME.httpRequest({
					uri: uri,
					post: post,
					method: method,
					wsseUsername: username,
					wssePassword: password,
					callback: function(data, req) {
						if(data.match(/^<\?xml/)) {
							data = data.replace(/<\?xml.*?\?>/, '');
						}

						try {
							data = new XML(data);
						}
						catch(e) {
							data = '';
						}

						callback(data, req);
					}
				});
			}
		};

	},
	iso8601Date: function(date) {
		var datetime = date.getUTCFullYear();
		var month = String(date.getUTCMonth() + 1);
		datetime += (month.length == 1 ?  '0' + month : month);
		var day = date.getUTCDate();
		datetime += (day < 10 ? '0' + day : day);
		datetime += 'T';
		var hour = date.getUTCHours();
		datetime += (hour < 10 ? '0' + hour : hour) + ':';
		var minutes = date.getUTCMinutes();
		datetime += (minutes < 10 ? '0' + minutes : minutes) + ':';
		var seconds = date.getUTCSeconds();
		datetime += (seconds < 10 ? '0' + seconds : seconds);
		return datetime;
	}
};

