/*
 * 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.desktop;

import com.mucommander.commons.file.AbstractFile;
import com.mucommander.text.Translator;
import com.mucommander.ui.dialog.InformationDialog;
import com.mucommander.ui.main.WindowManager;

import java.util.List;
import java.util.Vector;

/**
 * QueuedTrash is an {@link AbstractTrash} which moves files to the trash asynchroneously.
 *
 * <p>
 * When {@link #moveToTrash(com.mucommander.commons.file.AbstractFile)} is called, the file is added to a queue.
 * The file is not moved to the trash immediately: the trash will wait a period of {@link #QUEUE_PERIOD} milliseconds
 * for additional files to be added. If files were added during that period, the trash will wait another period and
 * so on. When no more files are added were added during the period, {@link #moveToTrash(java.util.Vector)} is called
 * with the list of queued files to move to the trash.
 * </p>
 *
 * <p>
 * This mechanism allows to group calls to the underlying trash. It is effective when the atomic operation
 * of moving a file to the trash has a high cost and {@link #moveToTrash(com.mucommander.commons.file.AbstractFile)} is called
 * repeatedly. One thing to note is since the move is performed asynchronously,
 * {@link #moveToTrash(com.mucommander.commons.file.AbstractFile)} returns immediately without waiting for the file to be moved,
 * {@link #waitForPendingOperations()} can be used to wait for the files to have effectively been moved.
 * </p>
 *
 * @author Maxence Bernard
 */
public abstract class QueuedTrash extends AbstractTrash {

    /** Contains the files that are waiting to be moved to the trash */
    private final static List<AbstractFile> queuedFiles = new Vector<AbstractFile>();

    /** Use to synchronize access to the trash */
    protected final static Object moveToTrashLock = new Object();

    /** Thread that performs the actual job of moving files to the trash */
    protected static Thread moveToTrashThread;

    /** Amount of time in millisecondes to wait for additional files before moving them to the trash */
    protected final static int QUEUE_PERIOD = 1000;


    /**
     * Moves the {@link AbstractFile} instances contained in the given <code>Vector</code> to the trash.
     * Returns <code>true</code> if all files were moved successfully.
     *
     * @param queuedFiles a Vector of AbstractFile to move to the trash
     * @return true if all files were moved successfully
     */
    protected abstract boolean moveToTrash(List<AbstractFile> queuedFiles);


    //////////////////////////////////
    // AbstractTrash implementation //
    //////////////////////////////////

    /**
     * Implementation notes: this method adds the given file to the queue of files to be moved to the trash and returns
     * immediately, i.e. without waiting for the file to be moved. The specified file will only be added to the queue if
     * {@link #canMoveToTrash(com.mucommander.commons.file.AbstractFile)} returned <code>true</code> for it.
     * Since the actual move is performed asynchronously, this method has no way of
     * knowing if the file was successfully moved to the trash. So this method will return <code>true</code> if the
     * given file has been scheduled to be moved to the trash, but it may end up failing to be moved for whatever reason.
     */
    @Override
    public boolean moveToTrash(AbstractFile file) {
        if(!canMoveToTrash(file))
            return false;

        synchronized(moveToTrashLock) {
            // Queue the given file
            queuedFiles.add(file);

            // Create a new thread and start it if one isn't already running
            if(moveToTrashThread ==null) {
                moveToTrashThread = new MoveToTrashThread();
                moveToTrashThread.start();
            }
        }

        return true;
    }

    @Override
    public void waitForPendingOperations() {
        synchronized(moveToTrashLock) {
            if(moveToTrashThread!=null) {
                try {
                    // Wait until moveToTrashThread wakes this thread up
                    moveToTrashLock.wait();
                }
                catch(InterruptedException e) {
                }
            }
        }
    }

    ///////////////////
    // Inner classes //
    ///////////////////

    /**
     * Performs the actual job of moving files to the trash.
     *
     * <p>The thread starts by waiting {@link com.mucommander.desktop.osx.OSXTrash#QUEUE_PERIOD} milliseconds before moving them to give additional
     * files a chance to be queued and regrouped as a single call to {@link QueuedTrash#moveToTrash(java.util.List)}.
     * If more files were queued during that period, the thread will wait an additional {@link com.mucommander.desktop.osx.OSXTrash# QUEUE_PERIOD},
     * and so on.<p>
     */
    private class MoveToTrashThread extends Thread {

        @Override
        public void run() {
            // Loops until no files were added during the sleep period
            int queueSize;
            do {
                queueSize = queuedFiles.size();

                try {
                    Thread.sleep(QUEUE_PERIOD);
                }
                catch(InterruptedException e) {}
            }
            while(queueSize!=queuedFiles.size());

            synchronized(moveToTrashLock) {     // Files can't be added to queue while files are moved to trash
                if(!moveToTrash(queuedFiles))
                    InformationDialog.showErrorDialog(WindowManager.getCurrentMainFrame(), Translator.get("delete_dialog.move_to_trash.option"), Translator.get("delete_dialog.move_to_trash.failed"));

                queuedFiles.clear();
                // Wake up any thread waiting for this thread to be finished
                moveToTrashLock.notify();
                moveToTrashThread = null;
            }
        }
    }
}
