module('apps.Dropbox').requires().toRun(function() {

Object.subclass('DropboxAPI', 'initializing', {
	serviceURLs: {
		requestToken:		"http://api.dropbox.com/0/oauth/request_token",
		userAuthorization:	"http://api.dropbox.com/0/oauth/authorize",
		accessToken:		"http://api.dropbox.com/0/oauth/access_token",
		directTokenRequest:	"http://api.dropbox.com/0/token",
		accountInfo:		"http://api.dropbox.com/0/account/info",
		accountCreation:	"http://api.dropbox.com/0/account",
		metadataAccess:		"http://api.dropbox.com/0/metadata/dropbox/",
		fileAccess:			"http://api-content.dropbox.com/0/files/dropbox/",
	},

	signatureMethod: "HMAC-SHA1",

	initialize: function(key, secret) {
		this.serverSigning = (arguments.length == 0);
		this.consumerKey = key;
		this.consumerSecret = secret;
	},
}, 'helper' , {
	createMessage: function(action, method, addParams, actionExt) {
		var msg = {
			action: this.serviceURLs[action] + (actionExt ? actionExt : ''),
			method: method,
			parameters: []
		};

		msg.parameters.push(['oauth_consumer_key', this.consumerKey]);
		msg.parameters.push(['oauth_signature_method', this.signatureMethod]);
		msg.parameters.push(['oauth_signature', null]);
 
		addParams = addParams || {};
		for (var p in addParams) {
			if (p && p.substr(-7) != "_secret")
				msg.parameters.push([p, addParams[p]]);
		}
 
		return msg;
	},

	signMessage: function(msg, oAuthInfo) {
		if (this.serverSigning)
			return this.signMessageOnServer(msg, oAuthInfo);

		var accessor = {
			consumerSecret: this.consumerSecret,
			tokenSecret: (oAuthInfo && oAuthInfo['oauth_token_secret'] ? oAuthInfo['oauth_token_secret'] : '')
		};

		OAuth.setTimestampAndNonce(msg);
		OAuth.SignatureMethod.sign(msg, accessor);

		return msg;
	},
	signMessageOnServer: function(msg, oAuthInfo) {
		var data;
		if (oAuthInfo)
			data = [msg, oAuthInfo];
		else
			data = msg;

		var content = JSON.serialize(data);

		var res = new WebResource(new URL('http://www.lively-kernel.org/nodejs/OAuthServer/signDropboxMsg'));
		res.post(content);
		var response = res.content;

		msg = JSON.unserialize(response);
		return msg;
	},


	getTokenObject: function(tokenString) {
		if (!tokenString)
			throw Error('Error getting token object: ');
		var tokenObj = {};
		tokenString.split('&').collect(function (ea) {
			return ea.split('=');
		}).each(function(ea) { 
			this[ea[0]] = ea[1];
		}, tokenObj);

		this.tokenObject = tokenObj;
		return tokenObj;
	},
}, 'api calls', {
	requestToken: function() {
		var msg = this.signMessage(this.createMessage('requestToken', 'POST'));

		// var header = OAuth.getAuthorizationHeader(msg.action, msg.parameters);

		// should be header information instead of query!
		var params = msg.parameters.collect(function (ea) { return ea[0] + '=' + ea[1]; }).join('&');
		var content = new WebResource(msg.action + '?' + params).get().content;

		return this.getTokenObject(content);
	},

	authorizationURL: function(tokenObj) {
		tokenObj = tokenObj || this.tokenObject;
		return this.serviceURLs['userAuthorization'] + '?oauth_token=' + tokenObj['oauth_token'];
	},

	accessToken: function(tokenObj) {
		tokenObj = tokenObj || this.tokenObject;
		var msg = this.signMessage(this.createMessage('accessToken', 'GET', tokenObj), tokenObj);

		// var header = OAuth.getAuthorizationHeader(msg.action, msg.parameters);

		// should be header information instead of query!
		var params = msg.parameters.collect(function (ea) { return ea[0] + '=' + ea[1]; }).join('&');
		var content = new WebResource(msg.action + '?' + params).get().content;

		return this.getTokenObject(content);
	},

	directTokenRequest: function(email, passwd) {
		var cred = { email: email, password: passwd };
		var msg = this.signMessage(this.createMessage('directTokenRequest', 'GET', cred));

		// var header = OAuth.getAuthorizationHeader(msg.action, msg.parameters);

		// should be header information instead of query!
		var params = msg.parameters.collect(function (ea) { return ea[0] + '=' + ea[1]; }).join('&');
		var content = new WebResource(msg.action + '?' + params).get().content;

		content = JSON.parse(content);
		this.tokenObject = { 'oauth_token': content.token, 'oauth_token_secret': content.secret };

		return this.tokenObject;
	},

	getFile: function(filename, tokenObj) {
		tokenObj = tokenObj || this.tokenObject;
		var msg = this.signMessage(this.createMessage('fileAccess', 'GET', tokenObj, filename), tokenObj);

		// var header = OAuth.getAuthorizationHeader(msg.action, msg.parameters);

		// should be header information instead of query!
		var params = msg.parameters.collect(function (ea) { return ea[0] + '=' + ea[1]; }).join('&');
		var content = new WebResource(msg.action + '?' + params).get().content;

		return content;
	},

	storeFile: function(filename, content, tokenObj) {
		tokenObj = tokenObj || this.tokenObject;

		var slash = filename.endsWith('/') ? filename.lastIndexOf('/', filename.length - 2) : filename.lastIndexOf('/');
		var path = filename.substring(0, slash + 1);
		var file = filename.substring(slash + 1);

		// don't destroy original token by enhancing
		tokenObj = Object.clone(tokenObj);
		tokenObj['file'] = file;
		var msg = this.signMessage(this.createMessage('fileAccess', 'POST', tokenObj, path), tokenObj);

		// var header = OAuth.getAuthorizationHeader(msg.action, msg.parameters);

		// should be header information instead of query!
		var params = msg.parameters.collect(function (ea) { return ea[0] + '=' + ea[1]; }).join('&');
		var result = new WebResource(msg.action + '?' + params)
			.setRequestHeaders({'Content-Type': 'multipart/form-data; boundary=LIVELY123KERNEL'})
			.post('--LIVELY123KERNEL\nContent-Disposition: form-data; name="file"; filename="' + file + '"\nContent-Type: application/xhtml+xml\n\n' + content + '\n--LIVELY123KERNEL--\n');

		return result.content;
	},

	storeWorld: function(filename, tokenObj) {
		var url = new URL('http://dl.dropbox.com/u/user_id/' + filename);

		// copied from saveWorld() :-(
		var start = new Date().getTime();
		var world = WorldMorph.current();
		var db = this;
		var serializeTime;
		var onFinished = function() {
			var time = new Date().getTime() - start;
			world.setStatusMessage("world saved to Dropbox in " + time + "ms \n(" + serializeTime + "ms serialization)", Color.green, 3)
		}
		var statusMessage = world.setStatusMessage("serializing....");
		(function() {
			var oldHand = this.firstHand();
			var oldKeyboardFocus = oldHand.keyboardFocus;
			this.removeHand(oldHand);
			var doc;
			// var world = this;
			try {
				doc = Exporter.shrinkWrapMorph(this.world());
			} catch(e) {
				this.setStatusMessage("Save failed due to:\n" + e, Color.red, 10, function() {
					world.showErrorDialog(e)
				})
			} finally {
				this.addHand(oldHand);
				console.log("setting back keyboard focus to" + oldKeyboardFocus)
				if (oldKeyboardFocus)
					oldKeyboardFocus.requestKeyboardFocus(oldHand);
			}
			new DocLinkConverter(URL.codeBase, url).convert(doc);			
			statusMessage.remove();
			serializeTime = new Date().getTime() - start;
			(function() {
				db.storeFile(filename, Exporter.stringify(doc), tokenObj);
				onFinished();
			}).bind(this).delay(0);
		}).bind(world).delay(0);
	},

	getUserInfo: function(tokenObj) {
		tokenObj = tokenObj || this.tokenObject;
		var msg = this.signMessage(this.createMessage('accountInfo', 'GET', tokenObj), tokenObj);

		// var header = OAuth.getAuthorizationHeader(msg.action, msg.parameters);

		// should be header information instead of query!
		var params = msg.parameters.collect(function (ea) { return ea[0] + '=' + ea[1]; }).join('&');
		var content = new WebResource(msg.action + '?' + params).get().content;

		return JSON.parse(content);
	},
});
Object.subclass('DropboxLogin',
'default category', {
	// new DropboxLogin().establishConnection()

	initialize: function(db) {
		this.db = db || new DropboxAPI();
	},

	establishConnection: function() {
		if (this.db.tokenObject) {
			alertOK('Dropbox connection already established.');
			lively.bindings.signal(this.db, 'tokenObject');
			return;
		}

		connect(this, 'usernameThere', this, 'askPassword');
		connect(this, 'passwordThere', this, 'requestToken');
		this.askUsername();
	},

	askUsername: function() {
		if (localStorage.dropBoxUser) {
			lively.bindings.signal(this, 'usernameThere');
		} else {
			var self = this;
			WorldMorph.current().prompt("DropBox username:", function(name) {
				localStorage.dropBoxUser = name; // remember it
				lively.bindings.signal(self, 'usernameThere');
			});
		}
	},

	askPassword: function() {
		if (localStorage.dropBoxPassword) {
			lively.bindings.signal(this, "passwordThere");
		} else {
			var self = this;
			WorldMorph.current().prompt("DropBox password:", function(password) {
				// how can we ensure that this object does not get serialized?
				localStorage.dropBoxPassword = password; // remember it
				lively.bindings.signal(self, "passwordThere");
			});
		}
	},

	requestToken: function() {
		alertOK('request DropBox token ' + localStorage.dropBoxUser)
		this.db.directTokenRequest(localStorage.dropBoxUser, localStorage.dropBoxPassword)
	},

});

}) // end of module