/*global gapi, google */
define([
	"underscore",
	"jquery",
	"constants",
	"core",
	"utils",
	"storage",
	"logger",
	"settings",
	"eventMgr",
	"classes/AsyncTask"
], function(_, $, constants, core, utils, storage, logger, settings, eventMgr, AsyncTask) {

	var connected = false;
	var authorizationMgrMap = {};

	function AuthorizationMgr(accountId) {
		var permissionList = {
			profile: true
		};
		var refreshFlag = true;
		_.each((storage[accountId + '.permissions'] || '').split(';'), function(permission) {
			permission && (permissionList[permission] = true);
		});
		this.setRefreshFlag = function() {
			refreshFlag = true;
		};
		this.isAuthorized = function(permission) {
			return refreshFlag === false && _.has(permissionList, permission);
		};
		this.add = function(permission) {
			permissionList[permission] = true;
			storage[accountId + '.permissions'] = _.keys(permissionList).join(';');
			refreshFlag = false;
		};
		this.getListWithNew = function(permission) {
			var result = _.keys(permissionList);
			if(!_.has(permissionList, permission)) {
				result.push(permission);
			}
			return result;
		};
		var userId = storage[accountId + '.userId'];
		this.setUserId = function(value) {
			userId = value;
			storage[accountId + '.userId'] = userId;
		};
		this.getUserId = function() {
			return userId;
		};
	}

	var googleHelper = {};

	// Listen to offline status changes
	var isOffline = false;
	eventMgr.addListener("onOfflineChanged", function(isOfflineParam) {
		isOffline = isOfflineParam;
	});

	// Try to connect by downloading client.js
	function connect(task) {
		task.onRun(function() {
			if(isOffline === true) {
				connected = false;
				return task.error(new Error("Operation not available in offline mode.|stopPublish"));
			}
			if(connected === true) {
				return task.chain();
			}
			window.delayedFunction = function() {
				gapi.load("client", function() {
					gapi.client.load('drive', 'v2', function() {
						connected = true;
						task.chain();
					});
				});
			};
			$.ajax({
				url: "https://apis.google.com/js/api.js?onload=runDelayedFunction",
				dataType: "script",
				timeout: constants.AJAX_TIMEOUT
			}).fail(function(jqXHR) {
				var error = {
					code: jqXHR.status,
					message: jqXHR.statusText
				};
				handleError(error, task);
			});
		});
	}

	// Try to authenticate with Oauth
	var scopeMap = {
		profile: [
			'https://www.googleapis.com/auth/userinfo.profile'
		],
		gdrive: [
			'https://www.googleapis.com/auth/drive.install',
			settings.gdriveFullAccess === true ? 'https://www.googleapis.com/auth/drive' : 'https://www.googleapis.com/auth/drive.file'
		],
		blogger: [
			'https://www.googleapis.com/auth/blogger'
		],
		picasa: [
			'https://www.googleapis.com/auth/photos'
		]
	};

	function authenticate(task, permission, accountId) {
		var authorizationMgr = authorizationMgrMap[accountId];
		if(!authorizationMgr) {
			authorizationMgr = new AuthorizationMgr(accountId);
			authorizationMgrMap[accountId] = authorizationMgr;
		}
		task.onRun(function() {
			var currentToken = gapi.auth.getToken();
			var newToken;

			function getTokenInfo() {
				$.ajax({
					url: 'https://www.googleapis.com/oauth2/v1/tokeninfo',
					data: {
						access_token: newToken.access_token
					},
					timeout: constants.AJAX_TIMEOUT,
					type: "GET"
				}).done(function(data) {
					if(authorizationMgr.getUserId() && authorizationMgr.getUserId() != data.user_id) {
						// Wrong user id, try again
						startAuthenticate();
					}
					else {
						authorizationMgr.setUserId(data.user_id);
						authorizationMgr.add(permission);
						authorizationMgr.token = newToken;
						task.chain();
					}
				}).fail(function(jqXHR) {
					var error = {
						code: jqXHR.status,
						message: jqXHR.statusText
					};
					handleError(error, task);
				});
			}

			var authuser = 0;
			var immediate;

			function localAuthenticate() {
				if(authuser > 5) {
					return task.error(new Error('Unable to authenticate user ' + authorizationMgr.getUserId() + ', please sign in with Google.'));
				}
				if(immediate === false) {
					task.timeout = constants.ASYNC_TASK_LONG_TIMEOUT;
				}
				var scopeList = _.chain(scopeMap).pick(authorizationMgr.getListWithNew(permission)).flatten().value();
				gapi.auth.authorize({
					client_id: constants.GOOGLE_CLIENT_ID,
					scope: scopeList,
					immediate: immediate,
					authuser: immediate === false ? '' : authuser
				}, function(authResult) {
					newToken = gapi.auth.getToken();
					gapi.auth.setToken(currentToken);
					if(!authResult || authResult.error) {
						if(connected === true && immediate === true) {
							// If immediate did not work
							if(authuser < 5 && authorizationMgr.getUserId()) {
								// Try immediate with next authuser
								authuser++;
							}
							else {
								// retry without immediate flag
								immediate = false;
							}
							task.chain(oauthRedirect);
						}
						else {
							// Error
							task.error(new Error("Access to Google account is not authorized."));
						}
					}
					else {
						// Success but we need to check the user id
						immediate === true && authuser++;
						task.chain(getTokenInfo);
					}
				});
			}

			function oauthRedirect() {
				if(immediate === true) {
					return task.chain(localAuthenticate);
				}
				utils.redirectConfirm('You are being redirected to <strong>Google</strong> authorization page.', function() {
					task.chain(localAuthenticate);
				}, function() {
					task.error(new Error('Operation canceled.'));
				});
			}

			function startAuthenticate() {
				immediate = true;
				if(authorizationMgr.token && authorizationMgr.isAuthorized(permission)) {
					return task.chain();
				}
				if(!authorizationMgr.getUserId()) {
					immediate = false;
				}
				task.chain(oauthRedirect);
			}

			startAuthenticate();
		});
	}

	googleHelper.refreshGdriveToken = function(accountId) {
		var task = new AsyncTask();
		connect(task);
		var authorizationMgr = authorizationMgrMap[accountId];
		authorizationMgr && authorizationMgr.setRefreshFlag();
		authenticate(task, 'gdrive', accountId);
		task.enqueue();
	};

	function runWithToken(accountId, functionToRun) {
		var currentToken = gapi.auth.getToken();
		var authorizationMgr = authorizationMgrMap[accountId];
		gapi.auth.setToken(authorizationMgr.token);
		functionToRun();
		gapi.auth.setToken(currentToken);
	}

	googleHelper.upload = function(fileId, parentId, title, content, contentType, etag, accountId, callback) {
		var result;
		var task = new AsyncTask();
		connect(task);
		authenticate(task, 'gdrive', accountId);
		task.onRun(function() {
			var boundary = '-------314159265358979323846';
			var delimiter = "\r\n--" + boundary + "\r\n";
			var close_delim = "\r\n--" + boundary + "--";
			contentType = contentType || settings.markdownMimeType;
			var metadata = {
				title: title,
				mimeType: contentType
			};
			if(parentId) {
				// Specify the directory
				metadata.parents = [
					{
						kind: 'drive#fileLink',
						id: parentId
					}
				];
			}
			var path = '/upload/drive/v2/files';
			var method = 'POST';
			if(fileId) {
				// If it's an update
				path += "/" + fileId;
				method = 'PUT';
			}
			var headers = {
				'Content-Type': 'multipart/mixed; boundary="' + boundary + '"'
			};

			var base64Data = utils.encodeBase64(content);
			var multipartRequestBody = [
				delimiter,
				'Content-Type: application/json\r\n\r\n',
				JSON.stringify(metadata),
				delimiter,
				'Content-Type: ',
				contentType,
				'\r\n',
				'Content-Transfer-Encoding: base64\r\n',
				'\r\n',
				base64Data,
				close_delim
			].join("");

			runWithToken(accountId, function() {
				var request = gapi.client.request({
					'path': path,
					'method': method,
					'params': {
						'uploadType': 'multipart'
					},
					'headers': headers,
					'body': multipartRequestBody
				});
				request.execute(function(response) {
					if(response && response.id) {
						// Upload success
						result = response;
						result.content = content;
						return task.chain();
					}
					var error = response.error;
					// Handle error
					if(error !== undefined && fileId !== undefined) {
						if(error.code === 404) {
							error = 'File ID "' + fileId + '" not found on Google Drive.|removePublish';
						}
						else if(error.code === 412) {
							// We may have missed a file update
							storage.removeItem(accountId + ".gdrive.lastChangeId");
							error = 'Conflict on file ID "' + fileId + '". Please restart the synchronization.';
						}
					}
					handleError(error, task);
				});
			});
		});
		task.onSuccess(function() {
			callback(undefined, result);
		});
		task.onError(function(error) {
			callback(error);
		});
		task.enqueue();
	};

	googleHelper.rename = function(fileId, title, accountId, callback) {
		var result;
		var task = new AsyncTask();
		connect(task);
		authenticate(task, 'gdrive', accountId);
		task.onRun(function() {
			var body = {'title': title};
			runWithToken(accountId, function() {
				var request = gapi.client.drive.files.patch({
					'fileId': fileId,
					'resource': body
				});
				request.execute(function(response) {
					if(response && response.id) {
						// Rename success
						result = response;
						return task.chain();
					}
					var error = response.error;
					// Handle error
					if(error !== undefined && fileId !== undefined) {
						if(error.code === 404) {
							error = 'File ID "' + fileId + '" not found on Google Drive.|removePublish';
						}
					}
					handleError(error, task);
				});
			});
		});
		task.onSuccess(function() {
			callback(undefined, result);
		});
		task.onError(function(error) {
			callback(error);
		});
		task.enqueue();
	};

	googleHelper.checkChanges = function(lastChangeId, accountId, callback) {
		var changes = [];
		var newChangeId = lastChangeId || 0;
		var task = new AsyncTask();
		connect(task);
		authenticate(task, 'gdrive', accountId);
		task.onRun(function() {
			var nextPageToken;

			function retrievePageOfChanges() {
				runWithToken(accountId, function() {
					var request;
					if(nextPageToken === undefined) {
						request = gapi.client.drive.changes.list({
							'startChangeId': newChangeId + 1
						});
					}
					else {
						request = gapi.client.drive.changes.list({
							'pageToken': nextPageToken
						});
					}

					request.execute(function(response) {
						if(!response || !response.largestChangeId) {
							// Handle error
							return handleError(response.error, task);
						}
						// Retrieve success
						newChangeId = response.largestChangeId;
						nextPageToken = response.nextPageToken;
						if(response.items !== undefined) {
							changes = changes.concat(response.items);
						}
						if(nextPageToken !== undefined) {
							task.chain(retrievePageOfChanges);
						}
						else {
							task.chain();
						}
					});
				});
			}

			task.chain(retrievePageOfChanges);
		});
		task.onSuccess(function() {
			callback(undefined, changes, newChangeId);
		});
		task.onError(function(error) {
			callback(error);
		});
		task.enqueue();
	};

	googleHelper.downloadMetadata = function(ids, accountId, callback, skipAuth) {
		var result = [];
		var task = new AsyncTask();
		connect(task);
		if(!skipAuth) {
			authenticate(task, 'gdrive', accountId);
		}
		task.onRun(function() {
			function recursiveDownloadMetadata() {
				if(ids.length === 0) {
					return task.chain();
				}
				var id = ids[0];
				var headers = {};
				var authorizationMgr = authorizationMgrMap[accountId];
				if(authorizationMgr && authorizationMgr.token) {
					headers.Authorization = "Bearer " + authorizationMgr.token.access_token;
				}
				$.ajax({
					url: "https://www.googleapis.com/drive/v2/files/" + id,
					headers: headers,
					data: {
						key: constants.GOOGLE_API_KEY
					},
					dataType: "json",
					timeout: constants.AJAX_TIMEOUT
				}).done(function(data) {
					result.push(data);
					ids.shift();
					task.chain(recursiveDownloadMetadata);
				}).fail(function(jqXHR) {
					var error = {
						code: jqXHR.status,
						message: jqXHR.statusText
					};
					// Handle error
					if(error.code === 404) {
						error = 'File ID "' + id + '" not found on Google Drive.';
					}
					handleError(error, task);
				});
			}

			task.chain(recursiveDownloadMetadata);
		});
		task.onSuccess(function() {
			callback(undefined, result);
		});
		task.onError(function(error) {
			callback(error);
		});
		task.enqueue();
	};

	googleHelper.downloadContent = function(objects, accountId, callback, skipAuth) {
		var result = [];
		var task = new AsyncTask();
		// Add some time for user to choose his files
		task.timeout = constants.ASYNC_TASK_LONG_TIMEOUT;
		connect(task);
		if(!skipAuth) {
			authenticate(task, 'gdrive', accountId);
		}
		task.onRun(function() {
			function recursiveDownloadContent() {
				if(objects.length === 0) {
					return task.chain();
				}
				var object = objects[0];
				result.push(object);
				var file;
				// object may be a file
				if(object.kind == "drive#file") {
					file = object;
				}
				// object may be a change
				else if(object.kind == "drive#change") {
					file = object.file;
				}
				if(!file) {
					objects.shift();
					return task.chain(recursiveDownloadContent);
				}
				var url = file.downloadUrl;
				// if file is a real time document
				if(file.mimeType.indexOf("application/vnd.google-apps.drive-sdk") === 0) {
					file.isRealtime = true;
					url = 'https://www.googleapis.com/drive/v2/files/' + file.id + '/realtime';
				}
				var headers = {};
				var authorizationMgr = authorizationMgrMap[accountId];
				if(authorizationMgr && authorizationMgr.token) {
					headers.Authorization = "Bearer " + authorizationMgr.token.access_token;
				}
				$.ajax({
					url: url,
					headers: headers,
					data: {
						key: constants.GOOGLE_API_KEY
					},
					dataType: file.isRealtime ? 'json' : 'text',
					timeout: constants.AJAX_TIMEOUT
				}).done(function(data) {
					file.content = file.isRealtime ? data.data.value.content.value : data;
					objects.shift();
					task.chain(recursiveDownloadContent);
				}).fail(function(jqXHR) {
					var error = {
						code: jqXHR.status,
						message: jqXHR.statusText
					};
					// Handle error
					handleError(error, task);
				});
			}

			task.chain(recursiveDownloadContent);
		});
		task.onSuccess(function() {
			callback(undefined, result);
		});
		task.onError(function(error) {
			callback(error);
		});
		task.enqueue();
	};

	googleHelper.uploadImg = function(name, content, albumId, callback) {
		var accountId = 'google.picasa0';
		var result;
		var task = new AsyncTask();
		connect(task);
		authenticate(task, 'picasa', accountId);
		task.onRun(function() {
			var headers = {
				"Slug": name
			};
			if(name.match(/.jpe?g$/i)) {
				headers["Content-Type"] = "image/jpeg";
			}
			else if(name.match(/.png$/i)) {
				headers["Content-Type"] = "image/png";
			}
			else if(name.match(/.gif$/i)) {
				headers["Content-Type"] = "image/gif";
			}
			var authorizationMgr = authorizationMgrMap[accountId];
			if(authorizationMgr && authorizationMgr.token) {
				headers.Authorization = "Bearer " + authorizationMgr.token.access_token;
			}

			$.ajax({
				url: constants.PICASA_IMPORT_IMG_URL + '?' + $.param({
					albumId: albumId
				}),
				headers: headers,
				data: content,
				processData: false,
				dataType: "xml",
				timeout: constants.AJAX_TIMEOUT,
				type: "POST"
			}).done(function(data) {
				result = data;
				task.chain();
			}).fail(function(jqXHR) {
				var error = {
					code: jqXHR.status,
					message: jqXHR.statusText
				};
				if(error.code == 200) {
					error.message = jqXHR.responseText;
				}
				handleError(error, task);
			});
		});
		task.onSuccess(function() {
			callback(undefined, result);
		});
		task.onError(function(error) {
			callback(error);
		});
		task.enqueue();
	};

	function handleError(error, task) {
		var errorMsg;
		if(error) {
			logger.error(error);
			// Try to analyze the error
			if(typeof error === "string") {
				errorMsg = error;
			}
			else {
				errorMsg = "Google error (" + error.code + ": " + error.message + ").";
				if(error.code >= 500 && error.code < 600) {
					// Retry as described in Google's best practices
					return task.retry(new Error(errorMsg));
				}
				else if(error.code === 401 || error.code === 403 || error.code == "token_refresh_required") {
					_.each(authorizationMgrMap, function(authorizationMgr) {
						authorizationMgr.setRefreshFlag();
					});
					errorMsg = "Access to Google account is not authorized.";
					return task.retry(new Error(errorMsg), 1);
				}
				else if(error.code === 0 || error.code === -1) {
					connected = false;
					_.each(authorizationMgrMap, function(authorizationMgr) {
						authorizationMgr.setRefreshFlag();
					});
					core.setOffline();
					errorMsg = "|stopPublish";
				}
			}
		}
		task.error(new Error(errorMsg));
	}

	var pickerLoaded = false;

	function loadPicker(task) {
		task.onRun(function() {
			if(pickerLoaded === true) {
				return task.chain();
			}
			$.ajax({
				url: "//www.google.com/jsapi",
				data: {
					key: constants.GOOGLE_API_KEY
				},
				dataType: "script",
				timeout: constants.AJAX_TIMEOUT
			}).done(function() {
				google.load('picker', '1', {
					callback: function() {
						task.chain();
					}
				});
				pickerLoaded = true;
			}).fail(function(jqXHR) {
				var error = {
					code: jqXHR.status,
					message: jqXHR.statusText
				};
				handleError(error, task);
			});
		});
	}

	googleHelper.picker = function(callback, pickerType, accountId) {
		var docs = [];
		var picker;

		function hidePicker() {
			if(picker !== undefined) {
				picker.setVisible(false);
				$(".modal-backdrop, .picker").remove();
			}
		}

		var task = new AsyncTask();
		// Add some time for user to choose his files
		task.timeout = constants.ASYNC_TASK_LONG_TIMEOUT;
		connect(task);
		if(pickerType == 'doc' || pickerType == 'folder') {
			authenticate(task, 'gdrive', accountId);
		}
		else {
			accountId = 'google.picasa0';
			authenticate(task, 'picasa', accountId);
		}
		loadPicker(task);
		task.onRun(function() {
			var authorizationMgr = authorizationMgrMap[accountId];
			var pickerBuilder = new google.picker.PickerBuilder();
			pickerBuilder.setAppId(constants.GOOGLE_DRIVE_APP_ID);
			var view;
			if(pickerType == 'doc') {
				view = new google.picker.DocsView(google.picker.ViewId.DOCS);
				view.setParent('root');
				view.setIncludeFolders(true);
				view.setMimeTypes([
					"text/x-markdown",
					"text/plain",
					"application/octet-stream",
					"application/vnd.google-apps.drive-sdk." + constants.GOOGLE_DRIVE_APP_ID
				].join(","));
				pickerBuilder.enableFeature(google.picker.Feature.NAV_HIDDEN);
				pickerBuilder.enableFeature(google.picker.Feature.MULTISELECT_ENABLED);
				pickerBuilder.addView(view);
				authorizationMgr && authorizationMgr.token && pickerBuilder.setOAuthToken(authorizationMgr.token.access_token);
			}
			else if(pickerType == 'folder') {
				view = new google.picker.DocsView(google.picker.ViewId.FOLDERS);
				view.setParent('root');
				view.setIncludeFolders(true);
				view.setSelectFolderEnabled(true);
				view.setMimeTypes('application/vnd.google-apps.folder');
				pickerBuilder.enableFeature(google.picker.Feature.NAV_HIDDEN);
				pickerBuilder.addView(view);
				authorizationMgr && authorizationMgr.token && pickerBuilder.setOAuthToken(authorizationMgr.token.access_token);
			}
			else if(pickerType == 'img') {
				view = new google.picker.PhotosView();
				view.setType('flat');
				pickerBuilder.addView(view);
				view = new google.picker.PhotosView();
				view.setType('ofuser');
				pickerBuilder.addView(view);
				pickerBuilder.addView(google.picker.ViewId.PHOTO_UPLOAD);
				authorizationMgr && authorizationMgr.token && pickerBuilder.setOAuthToken(authorizationMgr.token.access_token);
			}
			pickerBuilder.setCallback(function(data) {
				if(data.action == google.picker.Action.PICKED || data.action == google.picker.Action.CANCEL) {
					if(data.action == google.picker.Action.PICKED) {
						docs = data.docs;
					}
					hidePicker();
					task.chain();
				}
			});
			picker = pickerBuilder.build();
			$(utils.createBackdrop()).on('click.backdrop', function() {
				hidePicker();
				task.chain();
			});
			picker.setVisible(true);
		});
		task.onSuccess(function() {
			callback(undefined, docs);
		});
		task.onError(function(error) {
			hidePicker();
			callback(error);
		});
		task.enqueue();
	};

	googleHelper.uploadBlogger = function(blogUrl, blogId, postId, labelList, isDraft, publishDate, title, content, callback) {
		var accountId = 'google.blogger0';
		var task = new AsyncTask();
		connect(task);
		authenticate(task, 'blogger', accountId);
		task.onRun(function() {
			var headers = {};
			var authorizationMgr = authorizationMgrMap[accountId];
			if(authorizationMgr && authorizationMgr.token) {
				headers.Authorization = "Bearer " + authorizationMgr.token.access_token;
			}
			function uploadPost() {
				var url = "https://www.googleapis.com/blogger/v3/blogs/" + blogId + "/posts/";
				var data = {
					kind: "blogger#post",
					blog: {
						id: blogId
					},
					labels: labelList,
					title: title,
					content: content
				};
				if(publishDate) {
					data.published = publishDate.toISOString();
				}
				var type = "POST";
				// If it's an update
				if(postId !== undefined) {
					url += postId;
					data.id = postId;
					type = "PUT";
				}
				$.ajax({
					url: url,
					data: JSON.stringify(data),
					headers: headers,
					type: type,
					contentType: "application/json",
					dataType: "json",
					timeout: constants.AJAX_TIMEOUT
				}).done(function(post) {
					postId = post.id;
					task.chain(publish);
				}).fail(function(jqXHR) {
					var error = {
						code: jqXHR.status,
						message: jqXHR.statusText
					};
					// Handle error
					if(error.code === 404 && postId !== undefined) {
						error = 'Post ' + postId + ' not found on Blogger.|removePublish';
					}
					handleError(error, task);
				});
			}

			function publish() {
				var url = "https://www.googleapis.com/blogger/v3/blogs/" + blogId + "/posts/" + postId;
				if(isDraft) {
					url += "/revert";
				}
				else {
					url += "/publish";
					if(publishDate) {
						url += '?publishDate=' + publishDate.toISOString();
					}
				}
				$.ajax({
					url: url,
					headers: headers,
					type: 'POST',
					dataType: "json",
					timeout: constants.AJAX_TIMEOUT
				}).done(function() {
					task.chain();
				}).fail(function(jqXHR) {
					var error = {
						code: jqXHR.status,
						message: jqXHR.statusText
					};
					// Handle error
					if(error.code === 404) {
						error = 'Post ' + postId + ' not found on Blogger.|removePublish';
					}
					handleError(error, task);
				});
			}

			function getBlogId() {
				if(blogId !== undefined) {
					task.chain(uploadPost);
					return;
				}
				$.ajax({
					url: "https://www.googleapis.com/blogger/v3/blogs/byurl",
					data: {
						url: blogUrl
					},
					headers: headers,
					dataType: "json",
					timeout: constants.AJAX_TIMEOUT
				}).done(function(blog) {
					blogId = blog.id;
					task.chain(uploadPost);
				}).fail(function(jqXHR) {
					var error = {
						code: jqXHR.status,
						message: jqXHR.statusText
					};
					// Handle error
					if(error.code === 404) {
						error = 'Blog "' + blogUrl + '" not found on Blogger.|removePublish';
					}
					handleError(error, task);
				});
			}

			task.chain(getBlogId);
		});
		task.onSuccess(function() {
			callback(undefined, blogId, postId);
		});
		task.onError(function(error) {
			callback(error);
		});
		task.enqueue();
	};

	googleHelper.uploadBloggerPage = function(blogUrl, blogId, pageId, isDraft, publishDate, title, content, callback) {
		var accountId = 'google.blogger0';
		var task = new AsyncTask();
		connect(task);
		authenticate(task, 'blogger', accountId);
		task.onRun(function() {
			var headers = {};
			var authorizationMgr = authorizationMgrMap[accountId];
			if(authorizationMgr && authorizationMgr.token) {
				headers.Authorization = "Bearer " + authorizationMgr.token.access_token;
			}
			function uploadPage() {
				var url = "https://www.googleapis.com/blogger/v3/blogs/" + blogId + "/pages/";
				var data = {
					kind: "blogger#page",
					blog: {
						id: blogId
					},
					title: title,
					content: content
				};
				var type = "POST";
				// If it's an update
				if(pageId !== undefined) {
					url += pageId;
					data.id = pageId;
					type = "PUT";
				}
				$.ajax({
					url: url,
					data: JSON.stringify(data),
					headers: headers,
					type: type,
					contentType: "application/json",
					dataType: "json",
					timeout: constants.AJAX_TIMEOUT
				}).done(function(page) {
					pageId = page.id;
					task.chain();
				}).fail(function(jqXHR) {
					var error = {
						code: jqXHR.status,
						message: jqXHR.statusText
					};
					// Handle error
					if(error.code === 404 && pageId !== undefined) {
						error = 'Page ' + pageId + ' not found on Blogger.|removePublish';
					}
					handleError(error, task);
				});
			}

			function getBlogId() {
				if(blogId !== undefined) {
					task.chain(uploadPage);
					return;
				}
				$.ajax({
					url: "https://www.googleapis.com/blogger/v3/blogs/byurl",
					data: {
						url: blogUrl
					},
					headers: headers,
					dataType: "json",
					timeout: constants.AJAX_TIMEOUT
				}).done(function(blog) {
					blogId = blog.id;
					task.chain(uploadPage);
				}).fail(function(jqXHR) {
					var error = {
						code: jqXHR.status,
						message: jqXHR.statusText
					};
					// Handle error
					if(error.code === 404) {
						error = 'Blog "' + blogUrl + '" not found on Blogger.|removePublish';
					}
					handleError(error, task);
				});
			}

			task.chain(getBlogId);
		});
		task.onSuccess(function() {
			callback(undefined, blogId, pageId);
		});
		task.onError(function(error) {
			callback(error);
		});
		task.enqueue();
	};

	// Use by Google's client.js
	window.delayedFunction = undefined;
	window.runDelayedFunction = function() {
		if(window.delayedFunction !== undefined) {
			window.delayedFunction();
		}
	};

	return googleHelper;
});