/*******************************************************************************
 * Copyright (c) 2006, 2016 Wind River Systems 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:
 *     Wind River Systems - initial API and implementation
 *******************************************************************************/
package org.eclipse.cdt.dsf.concurrent;

import java.util.concurrent.Executor;

import org.eclipse.cdt.dsf.internal.DsfPlugin;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.MultiStatus;

/**
 * A request monitor that is used for multiple activities. We are told the
 * number of activities that constitutes completion. When our {@link #done()} is
 * called that many times, the request is considered complete.
 *
 * The usage is as follows: <code><pre>
 *     final CountingRequestMonitor countingRm = new CountingRequestMonitor(fExecutor, null) {
 *         public void handleCompleted() {
 *             System.out.println("All complete, errors=" + !getStatus().isOK());
 *         }
 *     };
 *
 *     int count = 0;
 *     for (int i : elements) {
 *         service.call(i, countingRm);
 *         count++;
 *     }
 *
 *     countingRm.setDoneCount(count);
 * </pre></code>
 *
 * @since 1.0
 */
public class CountingRequestMonitor extends RequestMonitor {

	/**
	 * Counter tracking the remaining number of times that the done() method
	 * needs to be called before this request monitor is actually done.
	 */
	private int fDoneCounter;

	/**
	 * Flag indicating whether the initial count has been set on this monitor.
	 */
	private boolean fInitialCountSet = false;

	public CountingRequestMonitor(Executor executor, RequestMonitor parentRequestMonitor) {
		super(executor, parentRequestMonitor);
		super.setStatus(new DsfMultiStatus(DsfPlugin.PLUGIN_ID, 0, "", null) { //$NON-NLS-1$
			@Override
			public String getMessage() {
				StringBuilder message = new StringBuilder();
				IStatus[] children = getChildren();
				for (int i = 0; i < children.length; i++) {
					message.append(children[i].getMessage());
					if (i + 1 < children.length) {
						message.append(" ,"); //$NON-NLS-1$
					}
				}
				return message.toString();
			}
		});
	}

	/**
	 * Sets the number of times that this request monitor needs to be called
	 * before this monitor is truly considered done.  This method must be called
	 * exactly once in the life cycle of each counting request monitor.
	 * @param count Number of times that done() has to be called to mark the request
	 * monitor as complete.  If count is '0', then the counting request monitor is
	 * marked as done immediately.
	 */
	public synchronized void setDoneCount(int count) {
		assert !fInitialCountSet;
		fInitialCountSet = true;
		fDoneCounter += count;
		if (fDoneCounter <= 0) {
			assert fDoneCounter == 0; // Mismatch in the count.
			super.done();
		}
	}

	/**
	 * Called to indicate that one of the calls using this monitor is finished.
	 * Only when we've been called the number of times corresponding to the
	 * completion count will this request monitor will be considered complete.
	 * This method can be called before {@link #setDoneCount(int)}; in that
	 * case, we simply bump the count that tracks the number of times we've been
	 * called. The monitor will not move into the completed state until after
	 * {@link #setDoneCount(int)} has been called (or during if the given
	 * completion count is equal to the number of times {@link #done()} has
	 * already been called.)
	 */
	@Override
	public synchronized void done() {
		fDoneCounter--;
		if (fInitialCountSet && fDoneCounter <= 0) {
			assert fDoneCounter == 0; // Mismatch in the count.
			super.done();
		}
	}

	@Override
	public String toString() {
		return "CountingRequestMonitor: " + getStatus().toString(); //$NON-NLS-1$
	}

	@Override
	public synchronized void setStatus(IStatus status) {
		if ((getStatus() instanceof MultiStatus)) {
			((MultiStatus) getStatus()).add(status);
		}
	}
}
