/*
 * This file is part of muCommander, http://www.mucommander.com
 *
 * muCommander 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.
 *
 * muCommander 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 com.mucommander.job.impl;

import java.io.IOException;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.mucommander.commons.file.AbstractFile;
import com.mucommander.commons.file.FileFactory;
import com.mucommander.commons.file.FileOperation;
import com.mucommander.commons.file.archive.AbstractArchiveFile;
import com.mucommander.commons.file.archive.AbstractRWArchiveFile;
import com.mucommander.commons.file.protocol.local.LocalFile;
import com.mucommander.commons.file.util.FileSet;
import com.mucommander.job.FileJobAction;
import com.mucommander.job.FileJobState;
import com.mucommander.text.Translator;
import com.mucommander.ui.dialog.file.ProgressDialog;
import com.mucommander.ui.main.MainFrame;


/**
 * This job recursively copies a set of files. Directories are copied recursively.
 *
 * @author Maxence Bernard
 */
public class CopyJob extends AbstractCopyJob {
	private static final Logger LOGGER = LoggerFactory.getLogger(CopyJob.class);

    /** Destination file that is being copied, this value is updated every time #processFile() is called.
     * The value can be used by subclasses that override processFile should they need to work on the destination file. */
    protected AbstractFile currentDestFile;

    /** Operating mode : COPY_MODE or DOWNLOAD_MODE */
    private TransferMode mode;

    public enum TransferMode {
        COPY,
        DOWNLOAD
    }
	
	
    /**
     * Creates a new CopyJob without starting it.
     *
     * @param progressDialog dialog which shows this job's progress
     * @param mainFrame mainFrame this job has been triggered by
     * @param files files which are going to be copied
     * @param destFolder destination folder where the files will be copied
     * @param newName the new filename in the destination folder, can be <code>null</code> in which case the original filename will be used.
     * @param mode mode in which CopyJob is to operate: {@link #COPY_MODE} or {@link #DOWNLOAD_MODE}.
     * @param fileExistsAction default action to be performed when a file already exists in the destination, see {@link com.mucommander.ui.dialog.file.FileCollisionDialog} for allowed values
     */
    public CopyJob(ProgressDialog progressDialog, MainFrame mainFrame, FileSet files, AbstractFile destFolder, String newName, TransferMode mode, int fileExistsAction) {
        super(progressDialog, mainFrame, files, destFolder, newName, fileExistsAction);

        this.mode = mode;
        this.errorDialogTitle = Translator.get(mode==TransferMode.DOWNLOAD?"download_dialog.error_title":"copy_dialog.error_title");
    }



    ////////////////////////////////////
    // TransferFileJob implementation //
    ////////////////////////////////////

    /**
     * Copies recursively the given file or folder. 
     *
     * @param file the file or folder to move
     * @param recurseParams destination folder where the given file will be copied (null for top level files)
     * 
     * @return <code>true</code> if the file has been copied.
     */
    @Override
    protected boolean processFile(AbstractFile file, Object recurseParams) {
        // Stop if interrupted
        if (getState() == FileJobState.INTERRUPTED)
            return false;
		
        // Destination folder
        AbstractFile destFolder = recurseParams==null ? baseDestFolder : (AbstractFile)recurseParams;
		
        // Is current file in base folder ?
        boolean isFileInBaseFolder = files.indexOf(file)!=-1;

        // Determine filename in destination
        String destFileName = isFileInBaseFolder && newName!=null ? newName : file.getName();

        // Create destination AbstractFile instance
        AbstractFile destFile = createDestinationFile(file, destFolder, destFileName);
        if (destFile == null)
            return false;

        currentDestFile = destFile;

        // Do nothing if file is a symlink and either the source or the destination
        // is non-local (skip file and return)
        if (file.isSymlink() && (
                !file.hasAncestor(LocalFile.class) ||
                !destFile.hasAncestor(LocalFile.class))) {
            return true;
        }

        destFile = checkForCollision(file, destFolder, destFile, false);
        if (destFile == null)
            return false;

        if (file.isSymlink()) {
            return tryCopySymlinkFile(file, destFile);
        }

        // Copy directory recursively
        if (file.isDirectory()) {
            // Create the folder in the destination folder if it doesn't exist
            if (!destFile.exists() || !destFile.isDirectory()) {
                // Loop for retry
                do {
                    try {
                        destFile.mkdir();
                    }
                    catch(IOException e) {
                        // Unable to create folder
                        int ret = showErrorDialog(errorDialogTitle, Translator.get("cannot_create_folder", destFileName));
                        // Retry loops
                        if (ret==FileJobAction.RETRY)
                            continue;
                        // Cancel or close dialog return false
                        return false;
                        // Skip continues
                    }
                    break;
                } while(true);
            }
			
            // and copy each file in this folder recursively
            do {		// Loop for retry
                try {
                    // for each file in folder...
                    for (AbstractFile subFile : file.ls()) {
                        if (getState() == FileJobState.INTERRUPTED)
                            break;

                        // Notify job that we're starting to process this file (needed for recursive calls to processFile)
                        nextFile(subFile);
                        processFile(subFile, destFile);
                    }

                    // Set currentDestFile back to the enclosing folder in case an overridden processFile method
                    // needs to work with the folder after calling super.processFile.
                    currentDestFile = destFile;

                    // Only when finished with folder, set destination folder's date to match the original folder one
                    if (destFile.isFileOperationSupported(FileOperation.CHANGE_DATE)) {
                        try {
                            destFile.changeDate(file.getDate());
                        }
                        catch (IOException e) {
                            LOGGER.debug("failed to change the date of "+destFile, e);
                            // Fail silently
                        }
                    }

                    return true;
                }
                catch(IOException e) {
                    // file.ls() failed
                    int ret = showErrorDialog(errorDialogTitle, Translator.get("cannot_read_folder", file.getName()));
                    // Retry loops
                    if (ret==FileJobAction.RETRY)
                        continue;
                    // Cancel, skip or close dialog returns false
                    return false;
                }
            } while(true);
        }
        // File is a regular file, copy it
        else  {
            // Copy the file
            return tryCopyFile(file, destFile, append, errorDialogTitle);
        }
    }



    // This job modifies baseDestFolder and its subfolders
    @Override
    protected boolean hasFolderChanged(AbstractFile folder) {
        return baseDestFolder.isParentOf(folder);
    }


    ////////////////////////
    // Overridden methods //
    ////////////////////////

    @Override
    protected void jobCompleted() {
        super.jobCompleted();

        // If the destination files are located inside an archive, optimize the archive file
        AbstractArchiveFile archiveFile = baseDestFolder.getParentArchive();
        if(archiveFile!=null && archiveFile.isArchive() && archiveFile.isWritable())
            optimizeArchive((AbstractRWArchiveFile)archiveFile);

        // If this job corresponds to a 'local copy' of a single file and in the same directory,
        // select the copied file in the active table after this job has finished (and hasn't been cancelled)
        if(files.size()==1 && newName!=null && baseDestFolder.equalsCanonical(files.elementAt(0).getParent())) {
            // Resolve new file instance now that it exists: some remote files do not immediately update file attributes
            // after creation, we need to get an instance that reflects the newly created file attributes
            selectFileWhenFinished(FileFactory.getFile(baseDestFolder.getAbsolutePath(true)+newName));
        }
    }

    @Override
    public String getStatusString() {
        if(isCheckingIntegrity())
            return super.getStatusString();
        
        if(isOptimizingArchive)
            return Translator.get("optimizing_archive", archiveToOptimize.getName());

        return Translator.get(mode==TransferMode.DOWNLOAD?"download_dialog.downloading_file":"copy_dialog.copying_file", getCurrentFilename());
    }
}
