/*
 * 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.archive.AbstractArchiveFile;
import com.mucommander.commons.file.archive.AbstractRWArchiveFile;
import com.mucommander.commons.file.util.FileSet;
import com.mucommander.core.desktop.DesktopManager;
import com.mucommander.desktop.AbstractTrash;
import com.mucommander.job.FileJobAction;
import com.mucommander.job.FileJobState;
import com.mucommander.text.Translator;
import com.mucommander.ui.dialog.DialogAction;
import com.mucommander.ui.dialog.InformationDialog;
import com.mucommander.ui.dialog.file.ProgressDialog;
import com.mucommander.ui.main.MainFrame;
import com.mucommander.ui.main.WindowManager;

/**
 * This class is responsible for deleting a set of files. This job can operate in two modes, depending on the boolean
 * value specified in the constructor:
 * <ul>
 *  <li>moveToTrash enabled: files are moved to the trash returned by {@link DesktopManager#getTrash()}.
 *  <li>moveToTrash disabled: files are permanently deleted, i.e deleted files cannot be recovered. In this mode,
 * folders are deleted recursively
 * </ul>
 *
 * @author Maxence Bernard
 */
public class DeleteJob extends FileJob {
	private static final Logger LOGGER = LoggerFactory.getLogger(DeleteJob.class);
	
    /** Title used for error dialogs */
    private String errorDialogTitle;

    /** If true, files will be moved to the trash instead of being deleted */
    private boolean moveToTrash;

    /** Trash instance, null if moveToTrash is false */
    private AbstractTrash trash;

    /** The archive that contains the deleted files (may be null) */ 
    private AbstractRWArchiveFile archiveToOptimize;

    /** True when an archive is being optimized */
    private boolean isOptimizingArchive;


    /**
     * Creates a new DeleteJob 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 deleted
     * @param moveToTrash if true, files will be moved to the trash, if false they will be permanently deleted.
     * Should be true only if a trash is available on the current platform.
     */
    public DeleteJob(ProgressDialog progressDialog, MainFrame mainFrame, FileSet files, boolean moveToTrash) {
        super(progressDialog, mainFrame, files);

        this.errorDialogTitle = Translator.get("delete_dialog.error_title");

        this.moveToTrash = moveToTrash;
        if(moveToTrash)
            trash = DesktopManager.getTrash();
    }

    /**
     * Deletes the given file, either by moving it to the trash (if {@link #moveToTrash} is true) or by deleting the
     * file directly.
     *
     * @param file the file to delete
     * @throws IOException if an error occurred while deleting the file
     */
    private void deleteFile(AbstractFile file) throws IOException {
        if(moveToTrash)
            trash.moveToTrash(file, this::showErrorToMoveToTrash);
        else
            file.delete();
    }

    /**
     * Invokes an error dialog saying that move to trash has failed.
     */
    private void showErrorToMoveToTrash() {
        InformationDialog.showErrorDialog(WindowManager.getCurrentMainFrame(), Translator.get("delete_dialog.move_to_trash.option"), Translator.get("delete_dialog.move_to_trash.failed"));
    }

    ////////////////////////////
    // FileJob implementation //
    ////////////////////////////

    /**
     * Deletes recursively the given file or folder. 
     *
     * @param file the file or folder to delete
     * @param recurseParams not used
     * 
     * @return <code>true</code> if the file has been completely deleted.
     */
    @Override
    protected boolean processFile(AbstractFile file, Object recurseParams) {
        if (getState() == FileJobState.INTERRUPTED)
            return false;

        // Delete files recursively, only if trash is not used.
        DialogAction ret;
        if(!moveToTrash && file.isDirectory()) {

            // Important: symlinks must *not* be followed -- following symlinks could have disastrous effects.
            if(!file.isSymlink()) {
                do {		// Loop for retry
                    // Delete each file in this folder
                    try {
                        AbstractFile[] subFiles = file.ls();
                        for(int i=0; i<subFiles.length && getState() != FileJobState.INTERRUPTED; i++) {
                            // Notify job that we're starting to process this file (needed for recursive calls to processFile)
                            nextFile(subFiles[i]);
                            processFile(subFiles[i], null);
                        }
                        break;
                    }
                    catch(IOException e) {
                        LOGGER.debug("IOException caught", e);

                        ret = showErrorDialog(errorDialogTitle, Translator.get("cannot_read_file", file.getAbsolutePath()));
                        // Retry loops
                        if(ret==FileJobAction.RETRY)
                            continue;
                        // Cancel, skip or close dialog returns false
                        return false;
                    }
                } while(true);
            }
        }
        // Return now if the job was interrupted, so that we do not attempt to delete this folder
        if (getState() == FileJobState.INTERRUPTED)
            return false;

        do {		// Loop for retry
            try {
                deleteFile(file);

                return true;
            }
            catch(IOException e) {
                LOGGER.debug("IOException caught", e);

                ret = showErrorDialog(errorDialogTitle,
                                      Translator.get(file.isDirectory()?"cannot_delete_folder":"cannot_delete_file", file.getName())
                                      );
                // Retry loops
                if(ret==FileJobAction.RETRY)
                    continue;
                // Cancel, skip or close dialog returns false
                return false;
            }
        } while(true);
    }

    // This job modifies baseFolder and subfolders
    @Override
    protected boolean hasFolderChangedImpl(AbstractFile folder) {
        return getBaseSourceFolder().isParentOf(folder);
    }


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

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

        if(moveToTrash)
            trash.waitForPendingOperations();
    }

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

        // If the source files are located inside an archive, optimize the archive file
        AbstractArchiveFile archiveFile = getBaseSourceFolder().getParentArchive();

        if(archiveFile!=null && archiveFile.isArchive() && archiveFile.isWritable()) {
            while(true) {
                try {
                    archiveToOptimize = ((AbstractRWArchiveFile)archiveFile);
                    isOptimizingArchive = true;

                    archiveToOptimize.optimizeArchive();

                    break;
                }
                catch(IOException e) {
                    if(showErrorDialog(errorDialogTitle, Translator.get("error_while_optimizing_archive", archiveFile.getName()))==FileJobAction.RETRY)
                        continue;

                    break;
                }
            }

            isOptimizingArchive = true;
        }
    }

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

        return Translator.get("delete.deleting_file", getCurrentFilename());
    }
}
