/*******************************************************************************
 * Copyright (c) 2000, 2015 IBM Corporation and others.
 *
 * This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License 2.0
 * which accompanies this distribution, and is available at
 * https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *     Brian de Alwis (MTI) - bug 432826: accumulate task-name too
 *******************************************************************************/
package org.eclipse.jface.operation;

import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IProgressMonitorWithBlocking;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.ProgressMonitorWrapper;
import org.eclipse.jface.dialogs.Dialog;
import org.eclipse.swt.widgets.Display;

/**
 * A progress monitor that accumulates <code>setTaskName</code>,
 * <code>worked</code> and <code>subtask</code> calls in the following way by
 * wrapping a standard progress monitor:
 * <ul>
 * <li>When a <code>setTaskName</code>, <code>worked</code> or
 * <code>subtask</code> call occurs the first time, the progress monitor posts a
 * runnable into the asynchronous SWT event queue.</li>
 * <li>Subsequent calls to <code>setTaskName</code>, <code>worked</code> or
 * <code>subtask</code> do not post a new runnable as long as a previous
 * runnable still exists in the SWT event queue. In this case, the progress
 * monitor just updates the internal state of the runnable that waits in the SWT
 * event queue for its execution. If no runnable exists, a new one is created
 * and posted into the event queue.
 * </ul>
 * <p>
 * This class is internal to the framework; clients outside JFace should not use
 * this class.
 * </p>
 */
/* package */class AccumulatingProgressMonitor extends ProgressMonitorWrapper {

    /**
     * The display.
     */
    private Display display;

    /**
     * The collector, or <code>null</code> if none.
     */
    private Collector collector;

    private String currentTask = ""; //$NON-NLS-1$

    private class Collector implements Runnable {
		private String taskName;

        private String subTask;

        private double worked;

        private IProgressMonitor monitor;

		/**
		 * Create a new collector.
		 *
		 * @param taskName
		 * @param subTask
		 * @param work
		 * @param monitor
		 */
		public Collector(String taskName, String subTask, double work,
				IProgressMonitor monitor) {
			this.taskName = taskName;
			this.subTask = subTask;
			this.worked = work;
			this.monitor = monitor;
		}

		/**
		 * Set the task name
		 *
		 * @param name
		 */
		public void setTaskName(String name) {
			this.taskName = name;
		}

        /**
         * Add worked to the work.
         * @param workedIncrement
         */
        public void worked(double workedIncrement) {
            this.worked = this.worked + workedIncrement;
        }

        /**
         * Set the subTask name.
         * @param subTaskName
         */
        public void subTask(String subTaskName) {
            this.subTask = subTaskName;
        }

        /**
         * Run the collector.
         */
        @Override
		public void run() {
            clearCollector(this);
			if (taskName != null) {
				monitor.setTaskName(taskName);
			}
            if (subTask != null) {
				monitor.subTask(subTask);
			}
            if (worked > 0) {
				monitor.internalWorked(worked);
			}
        }
    }

    /**
     * Creates an accumulating progress monitor wrapping the given one
     * that uses the given display.
     *
     * @param monitor the actual progress monitor to be wrapped
     * @param display the SWT display used to forward the calls
     *  to the wrapped progress monitor
     */
    public AccumulatingProgressMonitor(IProgressMonitor monitor, Display display) {
        super(monitor);
        Assert.isNotNull(display);
        this.display = display;
    }

    @Override
	public void beginTask(final String name, final int totalWork) {
        synchronized (this) {
            collector = null;
        }
        display.asyncExec(() -> {
		    currentTask = name;
		    getWrappedProgressMonitor().beginTask(name, totalWork);
		});
    }

	/**
	 * Clears the collector object used to accumulate work and subtask calls if
	 * it matches the given one.
	 *
	 * @param collectorToClear
	 */
	private synchronized void clearCollector(Collector collectorToClear) {
		// Check if the accumulator is still using the given collector.
		// If not, don't clear it.
		if (this.collector == collectorToClear) {
			this.collector = null;
		}
	}

	/**
	 * Creates a collector object to accumulate work and subtask calls.
	 *
	 * @param subTask
	 * @param work
	 */
	private void createCollector(String taskName, String subTask, double work) {
		collector = new Collector(taskName, subTask, work,
				getWrappedProgressMonitor());
		display.asyncExec(collector);
	}

    @Override
	public void done() {
        synchronized (this) {
            collector = null;
        }
        display.asyncExec(() -> getWrappedProgressMonitor().done());
    }

	@Override
	public synchronized void internalWorked(final double work) {
		if (collector == null) {
			createCollector(null, null, work);
		} else {
			collector.worked(work);
		}
	}

	@Override
	public synchronized void setTaskName(final String name) {
		currentTask = name;
		if (collector == null) {
			createCollector(name, null, 0);
		} else {
			collector.setTaskName(name);
		}
	}

	@Override
	public synchronized void subTask(final String name) {
		if (collector == null) {
			createCollector(null, name, 0);
		} else {
			collector.subTask(name);
		}
	}

    @Override
	public synchronized void worked(int work) {
        internalWorked(work);
    }

    @Override
	public void clearBlocked() {

        //If this is a monitor that can report blocking do so.
        //Don't bother with a collector as this should only ever
        //happen once and prevent any more progress.
        final IProgressMonitor pm = getWrappedProgressMonitor();
        if (!(pm instanceof IProgressMonitorWithBlocking)) {
			return;
		}

        display.asyncExec(() -> {
		    ((IProgressMonitorWithBlocking) pm).clearBlocked();
		    Dialog.getBlockedHandler().clearBlocked();
		});
    }

    @Override
	public void setBlocked(final IStatus reason) {
        //If this is a monitor that can report blocking do so.
        //Don't bother with a collector as this should only ever
        //happen once and prevent any more progress.
        final IProgressMonitor pm = getWrappedProgressMonitor();
        if (!(pm instanceof IProgressMonitorWithBlocking)) {
			return;
		}

        display.asyncExec(() -> {
		    ((IProgressMonitorWithBlocking) pm).setBlocked(reason);
		    //Do not give a shell as we want it to block until it opens.
		    Dialog.getBlockedHandler().showBlocked(pm, reason, currentTask);
		});
    }
}
