/*
 * Syncany, www.syncany.org
 * Copyright (C) 2011-2016 Philipp C. Heckel <philipp.heckel@gmail.com>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package org.syncany.plugins.transfer;

import java.io.File;
import java.util.Map;

import org.syncany.plugins.transfer.files.DatabaseRemoteFile;
import org.syncany.plugins.transfer.files.MultichunkRemoteFile;
import org.syncany.plugins.transfer.files.RemoteFile;
import org.syncany.plugins.transfer.files.SyncanyRemoteFile;

/**
 * The transfer manager synchronously connects to the remote storage. It is
 * responsible for file upload, download and deletion.
 *
 * <p>All its operations are strictly <b>synchronous</b> and throw a
 * {@code StorageException} if they fail. The implementations have to make sure
 * that
 * <ul>
 *   <li>the repository is not corrupted, e.g. duplicate files or corrupt files
 *   <li>files matching the specified file format are complete, i.e. fully uploaded
 *   <li>methods that need an established connections re-connect if necessary
 * </ul>
 *
 * <p>A transfer manager may organize files according to their type or name as
 * it is optimal for the given storage. {@link RemoteFile}s can be classified
 * by their sub-type. For network-transfer optimization reasons, it might be
 * useful to place {@link MultichunkRemoteFile}s and {@link DatabaseRemoteFile}s
 * in a separate sub-folder on the remote storage.
 *
 * @author Philipp C. Heckel <philipp.heckel@gmail.com>
 */
public interface TransferManager {
	/**
	 * Establish a connection with the remote storage.
	 *
	 * <p>This method does not validate the correctness of the repository and
	 * it does not create any folders. The former is done by {@link #test()}, the
	 * latter is done by {@link #init(boolean)}.
	 *
	 * @throws StorageException If the connection fails due to no Internet connection,
	 *         authentication errors, etc.
	 */
	public void connect() throws StorageException;

	/**
	 * Disconnect from the remote storage.
	 *
	 * @throws StorageException If the connection fails due to no Internet connection,
	 *         authentication errors, etc.
	 */
	public void disconnect() throws StorageException;

	/**
	 * Initialize remote storage. This method is called to set up a new repository.
	 *
	 * @param  createIfRequired true if the method should handle repo creation
	 * @throws StorageException If the connection drops, or any other
	 *         exception occurs.
	 */
	public void init(boolean createIfRequired) throws StorageException;

	/**
	 * Download an existing remote file to the local disk.
	 *
	 * <p>The file is either downloaded completely or nothing at all. In the latter
	 * case, a {@code StorageException} is thrown.
	 *
	 * <p>Implementations must make sure that if a file matches the specified name
	 * schema, it must be complete and consistent.
	 *
	 * <p>If remoteFile does not exist, a {@link StorageFileNotFoundException} is thrown.
	 *
	 * @param remoteFile Existing source file on the remote storage.
	 *        The only required property of the remote file is the name.
	 * @param localFile Not existing local file to which the remote file is
	 *        going to be downloaded.
	 * @throws StorageException If the connection fails due to no Internet connection,
	 *         authentication errors, etc.
	 */
	public void download(RemoteFile remoteFile, File localFile) throws StorageException;

	/**
	 * Update an existing local file to the online storage.
	 *
	 * <p>The file is either uploaded completely or nothing at all. In the latter
	 * case, a {@code StorageException} is thrown.
	 *
	 * <p>Implementations must make sure that if a file matches the specified name
	 * schema, it must be complete and consistent.
	 *
	 * <p>Implementations must NOT upload a file if it already exists and has
	 * the same file size as the local file.
	 *
	 * @param localFile Existing local file that is going to be uploaded.
	 * @param remoteFile Not existing destination file on the remote storage.
	 *        The only required property of the remote file is the name.
	 * @throws StorageException If the connection fails due to no Internet connection,
	 *         authentication errors, etc.
	 */
	public void upload(File localFile, RemoteFile remoteFile) throws StorageException;

	/**
	 * Moves an existing file in the online storage.
	 *
	 * <p> If the sourceFile does not exists, a {@link StorageMoveException} is thrown.
	 *
	 * @param source Existing remote file that is to be moved.
	 * @param target Destination for the remote file.
	 * @throws StorageException If the connection fails due to no Internet connection,
	 *         authentication errors, etc.
	 */
	public void move(RemoteFile sourceFile, RemoteFile targetFile) throws StorageException;

	/**
	 * Deletes an existing file from the remote storage permanently.
	 *
	 * <p>In case the remote file does not exist, it returns immediately without
	 * any notice. If the file cannot be deleted or the connection breaks,
	 * a {@code StorageException} is thrown.
	 *
	 * @param remoteFile Existing remote file to be deleted.
	 *        The only required property of the remote file is the name.
	 * @throws StorageException If the connection fails due to no Internet connection,
	 *         authentication errors, etc
	 */
	public boolean delete(RemoteFile remoteFile) throws StorageException;

	/**
	 * Retrieves a list of all files in the remote repository, filtered by
	 * the type of the desired file, i.e. by a sub-class of {@link RemoteFile}.
	 *
	 * @param remoteFileClass Filter class: <tt>RemoteFile</tt> or a sub-type thereof
	 * @return Returns a list of remote files. In the map, the key is the file name,
	 *         the value the entire {@link RemoteFile} object.
	 * @throws StorageException If the connection fails due to no Internet connection,
	 *         authentication errors, etc
	 */
	public <T extends RemoteFile> Map<String, T> list(Class<T> remoteFileClass) throws StorageException;

	/**
	 * Tests whether the repository parameters are valid. In particular, the method tests
	 * whether a target (folder, bucket, etc.) exists or, if not, whether it can be created.
	 * It furthermore tests whether a repository at the target already exists by checking if the
	 * {@link SyncanyRemoteFile} exists.
	 *
	 * <p>The relevant result is determined by the following methods:
	 *
	 * <ul>
	 *  <li>{@link #testTargetExists()}: Tests whether the target exists.</li>
	 *  <li>{@link #testTargetCanWrite()}: Tests whether the target is writable.</li>
	 *  <li>{@link #testTargetCanCreate()}: Tests whether the target can be created if it does not
	 *      exist already. This is only called if <tt>testCreateTarget</tt> is set.</li>
	 *  <li>{@link #testRepoFileExists()}: Tests whether the repo file exists.</li>
	 * </ul>
	 *
	 * @return Returns the result of testing the repository.
	 * @param testCreateTarget If <tt>true</tt>, the test will test if the target can be created in case
	 *        it does not exist. If <tt>false</tt>, this test will be skipped.
	 * @see StorageTestResult
	 */
	public StorageTestResult test(boolean testCreateTarget);

	/**
	 * Tests whether the target path/folder <b>exists</b>. This might be done by listing the parent path/folder
	 * or by retrieving metadata about the target. The method returns <tt>true</tt> if the target exists,
	 * <tt>false</tt> otherwise.
	 *
	 * <p>This method is called by the {@link #test(boolean)} method (only during repository initialization
	 * or initial connection).
	 *
	 * @return Returns <tt>true</tt> if the target exists, <tt>false</tt> otherwise
	 * @throws StorageException If the test cannot be performed, e.g. due to a connection failure
	 */
	public boolean testTargetExists() throws StorageException;

	/**
	 * Tests whether the target path/folder is <b>writable</b> by the application. This method may either
	 * check the write permissions of the target or actually write a test file to check write access. If the
	 * target does not exist, <tt>false</tt> is returned. If the target exists and is writable, <tt>true</tt>
	 * is returned.
	 *
	 * <p>This method is called by the {@link #test(boolean)} method (only during repository initialization
	 * or initial connection).
	 *
	 * @return Returns <tt>true</tt> if the target can be written to, <tt>false</tt> otherwise
	 * @throws StorageException If the test cannot be performed, e.g. due to a connection failure
	 */
	public boolean testTargetCanWrite() throws StorageException;

	/**
	 * Tests whether the target path/folder <b>can be created</b> (if it <b>does not exist already</b>). This method
	 * may either check the permissions of the parent path/folder or actually create and delete the target to
	 * determine create permissions.
	 *
	 * <p>If the target already exists, the method returns <tt>true</tt>. If it does not, but it can be created
	 * (according to tests of this method), it also returns <tt>true</tt>. In all other cases, <tt>false</tt> is returned.
	 *
	 * <p>This method is called by the {@link #test(boolean)} method, <b>but only if</b> the <tt>testCreateTarget</tt> flag
	 * is set to <tt>true</tt>!
	 *
	 * @return Returns <tt>true</tt> if the target can be created or already exists, <tt>false</tt> otherwise
	 * @throws StorageException If the test cannot be performed, e.g. due to a connection failure
	 */
	public boolean testTargetCanCreate() throws StorageException;

	/**
	 * Tests whether the <b>repository file exists</b> (see {@link SyncanyRemoteFile}). This method is called by the {@link #test()} method
	 * (only during repository initialization (or initial connection).
	 *
	 * <p>This method is called by the {@link #test(boolean)} method (only during repository initialization
	 * or initial connection).
	 *
	 * @return Returns <tt>true</tt> if the repository is valid, <tt>false</tt> otherwise
	 * @throws StorageException If the test cannot be performed, e.g. due to a connection failure
	 */
	public boolean testRepoFileExists() throws StorageException;

	/**
	 * Return the path for a concrete {@link org.syncany.plugins.transfer.files.RemoteFile} implementation as it is stored on the
	 * remote side
	 * *
	 * @param remoteFileClass The class to provide the path for
	 * @return A string pointing to the folder where a file is stored
	 */
	public String getRemoteFilePath(Class<? extends RemoteFile> remoteFileClass);
	
}
