/**
 * Copyright (c) 2006-2017, JGraph Ltd
 * Copyright (c) 2006-2017, Gaudenz Alder
 */
DriveFile = function(ui, data, desc)
{
	DrawioFile.call(this, ui, data);
	
	this.desc = desc;
};

//Extends mxEventSource
mxUtils.extend(DriveFile, DrawioFile);

/**
 * Delay for last save in ms.
 */
DriveFile.prototype.saveDelay = 0;

/**
 * Delay for last save in ms.
 */
DriveFile.prototype.allChangesSavedKey = 'allChangesSavedInDrive';

/**
 * Specifies if notify events should be ignored.
 */
DriveFile.prototype.getSize = function()
{
	return this.desc.fileSize;
};

/**
 * Returns true if copy, export and print are not allowed for this file.
 */
DriveFile.prototype.isRestricted = function()
{
	return DrawioFile.RESTRICT_EXPORT || (this.desc.userPermission != null && this.desc.labels != null &&
		this.desc.userPermission.role == 'reader' && this.desc.labels.restricted);
};

/**
 * Adds the listener for automatically saving the diagram for local changes.
 */
DriveFile.prototype.isConflict = function(err)
{
	return err != null && err.error != null && err.error.code == 412;
};

/**
 * Returns the current etag.
 */
DriveFile.prototype.getCurrentUser = function()
{
	return (this.ui.drive != null) ? this.ui.drive.user : null;
};

/**
 * Translates this point by the given vector.
 * 
 * @param {number} dx X-coordinate of the translation.
 * @param {number} dy Y-coordinate of the translation.
 */
DriveFile.prototype.getMode = function()
{
	return App.MODE_GOOGLE;
};

/**
 * Returns true if copy, export and print are not allowed for this file.
 */
DriveFile.prototype.getFileUrl = function()
{
	return 'https://drive.google.com/open?authuser=0&id=' + this.getId();
};

/**
 * Returns true if copy, export and print are not allowed for this file.
 */
DriveFile.prototype.getFolderUrl = function()
{
	if (this.desc.labels != null && this.desc.labels.trashed)
	{
		return 'https://drive.google.com/drive/trash';
	}
	else
	{
		return (this.desc.parents != null && this.desc.parents.length > 0) ?
			'https://drive.google.com/drive/folders/' +
			this.desc.parents[0].id : null;
	}
};

/**
 * Returns true if copy, export and print are not allowed for this file.
 */
DriveFile.prototype.getPublicUrl = function(fn)
{
	this.ui.drive.executeRequest({
		url: '/files/' + this.desc.id + '/permissions?supportsAllDrives=true'
	}, 
	mxUtils.bind(this, function(resp)
	{
		if (resp != null && resp.items != null)
		{
			for (var i = 0; i < resp.items.length; i++)
			{
				if (resp.items[i].id === 'anyoneWithLink' ||
					resp.items[i].id === 'anyone')
				{
					fn(this.desc.webContentLink);
					
					return;
				}
			}
		}
		
		fn(null);
	}), mxUtils.bind(this, function()
	{
		fn(null)
	}));
};

/**
 * Overridden to enable the autosave option in the document properties dialog
 * if realtime is not used.
 */
DriveFile.prototype.isAutosaveOptional = function()
{
	return true;
};

/**
 * Translates this point by the given vector.
 * 
 * @param {number} dx X-coordinate of the translation.
 * @param {number} dy Y-coordinate of the translation.
 */
DriveFile.prototype.isRenamable = function()
{
	return this.isEditable() && DrawioFile.prototype.isEditable.apply(this, arguments);
};

/**
 * Translates this point by the given vector.
 * 
 * @param {number} dx X-coordinate of the translation.
 * @param {number} dy Y-coordinate of the translation.
 */
DriveFile.prototype.isMovable = function()
{
	return this.isEditable();
};

/**
 * Translates this point by the given vector.
 * 
 * @param {number} dx X-coordinate of the translation.
 * @param {number} dy Y-coordinate of the translation.
 */
DriveFile.prototype.isTrashed = function()
{
	return this.desc.labels.trashed;
};

/**
 * Translates this point by the given vector.
 * 
 * @param {number} dx X-coordinate of the translation.
 * @param {number} dy Y-coordinate of the translation.
 */
DriveFile.prototype.save = function(revision, success, error, unloading, overwrite)
{
	DrawioFile.prototype.save.apply(this, [revision, mxUtils.bind(this, function()
	{
		this.saveFile(null, revision, success, error, unloading, overwrite);
	}), error, unloading, overwrite]);
};

/**
 * Translates this point by the given vector.
 * 
 * @param {number} dx X-coordinate of the translation.
 * @param {number} dy Y-coordinate of the translation.
 */
DriveFile.prototype.saveFile = function(title, revision, success, error, unloading, overwrite)
{
	try
	{
		if (!this.isEditable())
		{
			if (success != null)
			{
				success();
			}
		}
		else if (!this.savingFile)
		{
			// Sets shadow modified state during save
			this.savingFileTime = new Date();
			this.setShadowModified(false);
			this.savingFile = true;

			this.createSecret(mxUtils.bind(this, function(secret, token)
			{
				var doSave = mxUtils.bind(this, function(realOverwrite, realRevision)
				{
					try
					{
						var lastDesc = this.desc;
						
						if (this.sync != null)
						{
							this.sync.fileSaving();
						}
	
						this.ui.drive.saveFile(this, realRevision, mxUtils.bind(this, function(resp, savedData, pages, checksum)
						{
							try
							{
								this.savingFile = false;
								
								// Handles special case where resp is false eg
								// if the old file was converted to realtime
								if (resp != false)
								{
									// Checks for changes during save
									this.setModified(this.getShadowModified());
									
									if (revision)
									{
										this.lastAutosaveRevision = new Date().getTime();
									}
				
									// Adaptive autosave delay
									this.autosaveDelay = Math.round(Math.min(10000,
										Math.max(DriveFile.prototype.autosaveDelay,
											this.saveDelay)));
									this.desc = resp;
									
									// Shows possible errors but keeps the modified flag as the
									// file was saved but the cache entry could not be written
									if (token != null || !Editor.enableRealtimeCache)
									{
										this.fileSaved(savedData, lastDesc, mxUtils.bind(this, function()
										{
											this.contentChanged();
											
											if (success != null)
											{
												success(resp);
											}
										}), error, token, pages, checksum);
									}
									else if (success != null)
									{
										// TODO: Fix possible saving state never being reset
										success(resp);
									}
								}
								else if (error != null)
								{
									error(resp);
								}
							}
							catch (e)
							{
								this.savingFile = false;
								
								if (error != null)
								{
									error(e);
								}
								else
								{
									throw e;
								}
							}
						}), mxUtils.bind(this, function(err, desc)
						{
							try
							{
								this.savingFile = false;
								
								if (this.isConflict(err))
								{
									this.inConflictState = true;
									
									if (this.sync != null)
									{
										this.savingFile = true;
										
										this.sync.fileConflict(desc, mxUtils.bind(this, function()
										{
											// Adds random cool-off
											window.setTimeout(mxUtils.bind(this, function()
											{
												this.updateFileData();
												this.setShadowModified(false);
												doSave(realOverwrite, true);
											}), 100 + Math.random() * 500);
										}), mxUtils.bind(this, function()
										{
											this.savingFile = false;
							
											if (error != null)
											{
												error();
											}
										}));
									}
									else if (error != null)
									{
										error();
									}
								}
								else if (error != null)
								{
									error(err);
								}
							}
							catch (e)
							{
								this.savingFile = false;
					
								if (error != null)
								{
									error(e);
								}
								else
								{
									throw e;
								}
							}
						}), unloading, unloading, realOverwrite, null, secret);
					}
					catch (e)
					{
						this.savingFile = false;
						
						if (error != null)
						{
							error(e);
						}
						else
						{
							throw e;
						}
					}
				});
				
				doSave(overwrite, revision);				
			}));
		}
	}
	catch (e)
	{
		if (error != null)
		{
			error(e);
		}
		else
		{
			throw e;
		}
	}
};

/**
 * Shows a conflict dialog to the user.
 */
DriveFile.prototype.copyFile = function(success, error)
{
	if (!this.isRestricted())
	{
		this.makeCopy(mxUtils.bind(this, function()
		{
			if (this.ui.spinner.spin(document.body, mxResources.get('saving')))
			{
				try
				{
					this.save(true, success, error)
				}
				catch (e)
				{
					error(e);
				}
			}
		}), error, true);
	}
	else
	{
		DrawioFile.prototype.copyFile.apply(this, arguments);
	}	
};

/**
 * Shows a conflict dialog to the user.
 */
DriveFile.prototype.makeCopy = function(success, error, timestamp)
{
	if (this.ui.spinner.spin(document.body, mxResources.get('saving')))
	{
		// Uses copyFile internally which is a remote REST call with the advantage of keeping
		// the parents of the file in-place, but copies the remote file contents so needs to
		// be updated as soon as we have the ID.
		this.saveAs(this.ui.getCopyFilename(this, timestamp), mxUtils.bind(this, function(resp)
		{
			this.desc = resp;
			this.ui.spinner.stop();
			this.setModified(false);
			
			this.backupPatch = null;
			this.invalidChecksum = false;
			this.inConflictState = false;
			
			this.descriptorChanged();
			success();
		}), mxUtils.bind(this, function()
		{
			this.ui.spinner.stop();
			
			if (error != null)
			{
				error();
			}
		}));
	}
};

/**
 * Translates this point by the given vector.
 * 
 * @param {number} dx X-coordinate of the translation.
 * @param {number} dy Y-coordinate of the translation.
 */
DriveFile.prototype.saveAs = function(filename, success, error)
{
	this.ui.drive.copyFile(this.getId(), filename, success, error);
};

/**
 * Translates this point by the given vector.
 * 
 * @param {number} dx X-coordinate of the translation.
 * @param {number} dy Y-coordinate of the translation.
 */
DriveFile.prototype.rename = function(title, success, error)
{
	var rev = this.getCurrentRevisionId();
	
	this.ui.drive.renameFile(this.getId(), title, mxUtils.bind(this, function(desc)
	{
		if (!this.hasSameExtension(title, this.getTitle()))
		{
			this.desc = desc;

			if (this.sync != null)
			{
				this.sync.descriptorChanged(rev);
			}
			
			this.save(true, success, error);
		}
		else
		{
			this.desc = desc;
			this.descriptorChanged();
			
			if (this.sync != null)
			{
				this.sync.descriptorChanged(rev);
			}
			
			if (success != null)
			{
				success(desc);
			}
		}
	}), error);
};

/**
 * Translates this point by the given vector.
 * 
 * @param {number} dx X-coordinate of the translation.
 * @param {number} dy Y-coordinate of the translation.
 */
DriveFile.prototype.move = function(folderId, success, error)
{
	this.ui.drive.moveFile(this.getId(), folderId, mxUtils.bind(this, function(resp)
	{
		this.desc = resp;
		this.descriptorChanged();
		
		if (success != null)
		{
			success(resp);
		}
	}), error);
};

/**
 * Translates this point by the given vector.
 * 
 * @param {number} dx X-coordinate of the translation.
 * @param {number} dy Y-coordinate of the translation.
 */
DriveFile.prototype.share = function()
{
	this.ui.drive.showPermissions(this.getId(), this);
};

/**
 * Translates this point by the given vector.
 * 
 * @param {number} dx X-coordinate of the translation.
 * @param {number} dy Y-coordinate of the translation.
 */
DriveFile.prototype.getTitle = function()
{
	return this.desc.title;
};

/**
 * Translates this point by the given vector.
 * 
 * @param {number} dx X-coordinate of the translation.
 * @param {number} dy Y-coordinate of the translation.
 */
DriveFile.prototype.getHash = function()
{
	return 'G' + this.getId();
};

/**
 * Translates this point by the given vector.
 * 
 * @param {number} dx X-coordinate of the translation.
 * @param {number} dy Y-coordinate of the translation.
 */
DriveFile.prototype.getId = function()
{
	return this.desc.id;
};

/**
 * Translates this point by the given vector.
 * 
 * @param {number} dx X-coordinate of the translation.
 * @param {number} dy Y-coordinate of the translation.
 */
DriveFile.prototype.isEditable = function()
{
	return DrawioFile.prototype.isEditable.apply(this, arguments) &&
		this.desc.editable;
};

/**
 * Hook for subclassers.
 */
DriveFile.prototype.isSyncSupported = function()
{
	return true;
};

/**
 * Hook for subclassers.
 */
DriveFile.prototype.isRealtimeSupported = function()
{
	return true;
};

/**
 * Returns true if all changes should be sent out immediately.
 */
DriveFile.prototype.isRealtimeOptional = function()
{
	return this.sync != null && this.sync.isConnected();
};

/**
 * Returns true if all changes should be sent out immediately.
 */
DriveFile.prototype.setRealtimeEnabled = function(value, success, error)
{
	if (this.sync != null)
	{
		this.ui.drive.executeRequest({
			'url': '/files/' + this.getId() + '/properties?alt=json&supportsAllDrives=true',
			'method': 'POST',
			'contentType': 'application/json; charset=UTF-8',
			'params': {
				'key': 'collaboration',
				'value': (value) ? 'enabled' :
					((urlParams['fast-sync'] != '0') ?
						'disabled' : '')
			}
		}, mxUtils.bind(this, function()
		{
			this.loadDescriptor(mxUtils.bind(this, function(desc)
			{
				if (desc != null)
				{
					this.sync.descriptorChanged(this.getCurrentEtag());
					this.sync.updateDescriptor(desc);
					success();
				}
				else
				{
					error();
				}
			}), error);
		}), error);
	}
	else
	{
		error();
	}
};

/**
 * Returns true if all changes should be sent out immediately.
 */
DriveFile.prototype.isRealtimeEnabled = function()
{
	var collab = this.ui.drive.getCustomProperty(this.desc, 'collaboration');

	return (DrawioFile.prototype.isRealtimeEnabled.apply(this, arguments) &&
		collab != 'disabled') || (Editor.enableRealtime && collab == 'enabled');
};

/**
 * Hook for subclassers.
 */
DriveFile.prototype.isRevisionHistorySupported = function()
{
	return true;
};

/**
 * Hook for subclassers.
 */
DriveFile.prototype.getRevisions = function(success, error)
{
	this.ui.drive.executeRequest(
	{
		url: '/files/' + this.getId() + '/revisions'
	},
	mxUtils.bind(this, function(resp)
	{
		for (var i = 0; i < resp.items.length; i++)
		{
			(mxUtils.bind(this, function(item)
			{
				// Redirects title to originalFilename to
				// match expected descriptor interface
				item.title = item.originalFilename;
				
				item.getXml = mxUtils.bind(this, function(itemSuccess, itemError)
				{
					this.ui.drive.getXmlFile(item, mxUtils.bind(this, function(file)
		   			{
						itemSuccess(file.getData());
		   			}), itemError);
				});
				
				item.getUrl = mxUtils.bind(this, function(page)
				{
					return this.ui.getUrl(window.location.pathname + '?rev=' + item.id +
						'&chrome=0&nav=1&layers=1&edit=_blank' + ((page != null) ?
						'&page=' + page : '')) + window.location.hash;
				});
			}))(resp.items[i]);
		}
		
		success(resp.items);
	}), error);
};

/**
 * Adds the listener for automatically saving the diagram for local changes.
 */
DriveFile.prototype.getLatestVersion = function(success, error)
{
	this.ui.drive.getFile(this.getId(), success, error, true);
};

/**
 * Adds all listeners.
 */
DriveFile.prototype.getChannelId = function()
{
	var chan = this.ui.drive.getCustomProperty(this.desc, 'channel');
	
	if (chan != null)
	{
		chan = 'G-' + this.getId() + '.' + chan;
	}
	
	return chan;
};

/**
 * Gets the channel ID from the given descriptor.
 */
DriveFile.prototype.getChannelKey = function()
{
	return this.ui.drive.getCustomProperty(this.desc, 'key');
};

/**
 * Adds all listeners.
 */
DriveFile.prototype.getLastModifiedDate = function()
{
	return new Date(this.desc.modifiedDate);
};

/**
 * Adds all listeners.
 */
DriveFile.prototype.getDescriptor = function()
{
	return this.desc;
};

/**
* Updates the descriptor of this file with the one from the given file.
*/
DriveFile.prototype.setDescriptor = function(desc)
{
	this.desc = desc;
};

/**
 * Returns the checksum from the given descriptor.
 */
DriveFile.prototype.getDescriptorChecksum = function(desc)
{
	var value = this.ui.drive.getCustomProperty(desc, 'checksum');
	var secret = this.getDescriptorSecret(desc);
	var result = null;

	if (value != null && secret != null)
	{
		tokens = value.split(':');

		// Checks if checksum matches current secret
		if (tokens.length == 2 && tokens[0] == secret)
		{
			result = tokens[1];
		}
	}

	return result;
};

/**
 * Returns the secret from the given descriptor.
 */
DriveFile.prototype.getDescriptorSecret = function(desc)
{
	return this.ui.drive.getCustomProperty(desc, 'secret');
};

/**
 * Updates the revision ID on the given descriptor.
 */
DriveFile.prototype.setDescriptorRevisionId = function(desc, id)
{
	desc.headRevisionId = id;
};

/**
 * Returns the revision ID from the given descriptor.
 */
DriveFile.prototype.getDescriptorRevisionId = function(desc)
{
	return desc.headRevisionId;
};

/**
 * Adds all listeners.
 */
DriveFile.prototype.getDescriptorEtag = function(desc)
{
	return desc.etag;
};

/**
 * Adds the listener for automatically saving the diagram for local changes.
 */
DriveFile.prototype.setDescriptorEtag = function(desc, etag)
{
	desc.etag = etag;
};

/**
 * Adds the listener for automatically saving the diagram for local changes.
 */
DriveFile.prototype.loadPatchDescriptor = function(success, error)
{
	this.ui.drive.executeRequest(
	{	
		url: '/files/' + this.getId() + '?supportsAllDrives=true&fields=' + this.ui.drive.catchupFields
	},
	mxUtils.bind(this, function(desc)
	{
		success(desc);
	}), error);
};

/**
 * Adds the listener for automatically saving the diagram for local changes.
 */
DriveFile.prototype.patchDescriptor = function(desc, patch)
{
	desc.headRevisionId = patch.headRevisionId;
	desc.modifiedDate = patch.modifiedDate;
	
	DrawioFile.prototype.patchDescriptor.apply(this, arguments);
};

/**
 * Adds the listener for automatically saving the diagram for local changes.
 */
DriveFile.prototype.loadDescriptor = function(success, error)
{
	this.ui.drive.loadDescriptor(this.getId(), success, error);
};

/**
 * Are comments supported
 */
DriveFile.prototype.commentsSupported = function()
{
	return true;
};

/**
 * Get comments of the file
 */
DriveFile.prototype.getComments = function(success, error)
{
	var currentUser = this.ui.getCurrentUser();
	
	function driveCommentToDrawio(file, gComment, pCommentId)
	{
		if (gComment.deleted) return null; //skip deleted comments
		
		var comment = new DriveComment(file, gComment.commentId || gComment.replyId, gComment.content, 
				gComment.modifiedDate, gComment.createdDate, gComment.status == 'resolved',
				gComment.author.isAuthenticatedUser? currentUser :
				new DrawioUser(gComment.author.permissionId, gComment.author.emailAddress,
						gComment.author.displayName, gComment.author.picture.url), pCommentId);
		
		for (var i = 0; gComment.replies != null && i < gComment.replies.length; i++)
		{
			comment.addReplyDirect(driveCommentToDrawio(file, gComment.replies[i], gComment.commentId));
		}
		
		return comment;
	};
	
	this.ui.drive.executeRequest(
	{
		url: '/files/' + this.getId() + '/comments'
	},
	mxUtils.bind(this, function(resp)
	{
		var comments = [];
		
		for (var i = 0; i < resp.items.length; i++)
		{
			var comment = driveCommentToDrawio(this, resp.items[i]);
			
			if (comment != null) comments.push(comment);
		}
		
		success(comments);
	}), error);
};

/**
 * Add a comment to the file
 */
DriveFile.prototype.addComment = function(comment, success, error)
{
	var body = {'content': comment.content};
	
	this.ui.drive.executeRequest(
	{
		url: '/files/' + this.getId() + '/comments',
		method: 'POST',
		params: body
	},
	mxUtils.bind(this, function(resp)
	{
		success(resp.commentId); //pass comment id
	}), error);
};

/**
 * Can add a reply to a reply
 */
DriveFile.prototype.canReplyToReplies = function()
{
	return false;
};

/**
 * Can add comments (The permission to comment to this file)
 */
DriveFile.prototype.canComment = function()
{
	return this.desc.canComment;
};

/**
 * Get a new comment object
 */
DriveFile.prototype.newComment = function(content, user)
{
	return new DriveComment(this, null, content, Date.now(), Date.now(), false, user);
};
