/*******************************************************************************
 * Copyright (c) 2006, 2014 Wind River Systems and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 * 
 * Contributors:
 *     Wind River Systems - initial API and implementation
 *     Eugene Ostroukhov (NVIDIA) - new done(IStatus) method
 *     Alvaro Sanchez-Leon (Ericsson) - Trace unsuccessful DSF Request Monitor at done() (Bug 453618)
 *******************************************************************************/
package org.eclipse.cdt.dsf.concurrent;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Executor;
import java.util.concurrent.RejectedExecutionException;

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

/**
 * Used to monitor the result of an asynchronous request.  Because of the
 * asynchronous nature of DSF code, a very large number of methods needs to 
 * signal the result of an operation through a call-back.  This class is the base 
 * class for such call backs.  
 * <p>
 * The intended use of this class, is that a client who is calling an asynchronous 
 * method, will sub-class RequestMonitor, and implement the method {@link #handleCompleted()}, 
 * or any of the other <code>handle...</code> methods, in order to interpret the 
 * results of the request.  The object implementing the asynchronous method is required 
 * to call the {@link #done()} method on the request monitor object that it received 
 * as an argument.  
 * </p>
 * <p>
 * The severity of the {@link IStatus> returned by #getStatus() can be used to 
 * determine the success or failure of the asynchronous operation.  By convention
 * the error codes returned by asynchronous method should be interpreted as follows:
 * <ul>
 * <li>OK and INFO - Result is a success.  In DataRequestMonitor, getData() should
 *     return a value.</li>
 * <li>WARNING - Acceptable error condition (getData() may return null).  Where for
 *     example user tried to retrieve variable data, but the program resumed in the
 *     mean time and an event will be generated shortly which will clear the variables
 *     view.</li>
 * <li>ERROR - An error condition that should probably be reported to the user.</li>
 * <li>CANCEL - The request was canceled, and the asynchronous method was not
 *     completed.</li>
 * </ul>  
 * </p>
 * <p>
 * The RequestMonitor constructor accepts an optional "parent" request monitor.  If a 
 * parent monitor is specified, it will automatically be invoked by this monitor when 
 * the request is completed.  The parent option is useful when implementing a method 
 * which is asynchronous (and accepts a request monitor as an argument) and which itself 
 * calls another asynchronous method to complete its operation.  For example, in the 
 * request monitor implementation below, the implementation only needs to override 
 * <code>handleSuccess()</code>, because the base implementation will handle notifying the 
 * parent <code>rm</code> in case the <code>getIngredients()</code> call fails. 
 * <pre>
 *     public void createCupCakes(final DataRequestMonitor<CupCake[]> rm) {
 *         getIngredients(new DataRequestMonitor<Ingredients>(fExecutor, rm) {
 *                 public void handleSuccess() {
 *                     rm.setData( new CupCake(getData().getFlour(), getData().getSugar(), 
 *                                             getData().getBakingPowder()));
 *                     rm.done();  
 *                 }
 *             });
 *     }
 * </pre>
 * </p>
 * 
 * @since 1.0
 */
@ThreadSafe
public class RequestMonitor extends DsfExecutable {
    
    /**
     * Interface used by RequestMonitor to notify when a given request monitor
     * is canceled.  
     * 
     * @see RequestMonitor
     */
    public static interface ICanceledListener {
        
        /**
         * Called when the given request monitor is canceled.
         */
        public void requestCanceled(RequestMonitor rm);
    }

    /** 
     * The executor that will be used in order to invoke the handler of the results
     * of the request.
     */
    private final Executor fExecutor;
    
    /**
     * The request monitor which was used to call into the method that created this 
     * monitor.  
     */
    private final RequestMonitor fParentRequestMonitor;

    private List<ICanceledListener> fCancelListeners;
    
    /**
     * Status 
     */
    private IStatus fStatus = Status.OK_STATUS;
    private boolean fCanceled = false;
    private boolean fDone = false;

    private final ICanceledListener fCanceledListener;
    
	/**
	 * This field is never read by any code; its purpose is strictly to assist
	 * developers debug DPF code. Developer can select this field in the
	 * Variables view and see a monitor backtrace in the details pane. See
	 * {@link DsfExecutable#DEBUG_MONITORS}.
	 * 
	 * <p>
	 * This field is set only when tracing is enabled.
	 */
	@SuppressWarnings("unused")
	private String fMonitorBacktrace;

	/**
	 * Constructor with an optional parent monitor.
	 * 
	 * @param executor
	 *            This executor will be used to invoke the runnable that will
	 *            allow processing the completion code of this request monitor.
	 *            I.e., the runnable will call {@link #handleCompleted()}.
	 * @param parentRequestMonitor
	 *            An optional parent request monitor. By default, our completion
	 *            handlers invoke the parent monitor's <code>done</code> method,
	 *            thus allowing monitors to be daisy chained. If this request is
	 *            unsuccessful, its status is set into the parent monitor.
	 *            Parameter may be null.
	 */
    public RequestMonitor(Executor executor, RequestMonitor parentRequestMonitor) {
        fExecutor = executor;
        fParentRequestMonitor = parentRequestMonitor;
        
        // If the parent rm is not null, add ourselves as a listener so that 
        // this request monitor will automatically be canceled when the parent
        // is canceled.
        if (fParentRequestMonitor != null) {
            fCanceledListener = new ICanceledListener() {
                @Override
                public void requestCanceled(RequestMonitor rm) {
                    cancel();
                }
            };

            fParentRequestMonitor.addCancelListener(fCanceledListener);
        } else {
            fCanceledListener = null;
        }
        
        if (DEBUG_MONITORS) {
        	createMonitorBacktrace();
        }
    }
    
    /** 
     * Sets the status of the result of the request.  If status is OK, this 
     * method does not need to be called.
     * 
     * @see #done(IStatus)
     */
    public synchronized void setStatus(IStatus status) { 
        assert isCanceled() || status.getSeverity() != IStatus.CANCEL; 
        fStatus = status; 
    }
    
    /** Returns the status of the completed method. */
    public synchronized IStatus getStatus() {
        if (isCanceled()) {
            return Status.CANCEL_STATUS;
        }
        return fStatus; 
    }

	/**
	 * Sets this request monitor as canceled and calls the cancel listeners if
	 * any.
	 * <p>
	 * Note: Calling cancel() does not automatically complete the
	 * RequestMonitor. The asynchronous call still has to call done().
	 * </p>
	 * <p>
	 * Note: logically a request should only be canceled by the client that
	 * issued the request in the first place. After a request is canceled, the
	 * method that is fulfilling the request may call
	 * {@link #setStatus(IStatus)} with severity of <code>IStatus.CANCEL</code>
	 * to indicate that it recognized that the given request was canceled and it
	 * did not perform the given operation.
	 * </p>
	 * <p>
	 * Canceling a monitor effectively cancels all descendant monitors, by
	 * virtue of the default implementation of {@link #isCanceled()}, which
	 * checks not only its own state but that of its parent. However, only the
	 * cancel listeners of the monitor directly canceled will be called.
	 * </p>
	 */
    public void cancel() {
        ICanceledListener[] listeners = null;
        synchronized (this) {
            // Check to make sure the request monitor wasn't previously canceled.
            if (!fCanceled) {
                fCanceled = true;
                if (fCancelListeners != null) {
                    listeners = fCancelListeners.toArray(new ICanceledListener[fCancelListeners.size()]);
                }
            }
        }

        // Call the listeners outside of a synchronized section to reduce the 
        // risk of deadlocks.
        if (listeners != null) {
            for (ICanceledListener listener : listeners) {
                listener.requestCanceled(this);
            }
        }
    }
    
    /**
     * Returns whether the request was canceled.  Even if the request is
     * canceled by the client, the implementor handling the request should 
     * still call {@link #done()} in order to complete handling 
     * of the request monitor. 
     * 
     * <p>
     * A request monitor is considered canceled if either it or its parent was canceled.
     * </p> 
     */
    public boolean isCanceled() {
        boolean canceled = false;
        
        // Avoid holding onto this lock while calling parent RM, which may 
        // acquire other locks (bug 329488).
        synchronized(this) {
            canceled = fCanceled;
        }
        return canceled || (fParentRequestMonitor != null && fParentRequestMonitor.isCanceled());
    }
    
    /**
     * Adds the given listener to list of listeners that are notified when this 
     * request monitor is directly canceled.
     */
    public synchronized void addCancelListener(ICanceledListener listener) {
        if (fCancelListeners == null) {
            fCancelListeners = new ArrayList<ICanceledListener>(1);
        }
        fCancelListeners.add(listener);
    }

    /**
     * Removes the given listener from the list of listeners that are notified 
     * when this request monitor is directly canceled.
     */
    public synchronized void removeCancelListener(ICanceledListener listener) {
        if (fCancelListeners != null) {
            fCancelListeners.remove(listener);
        }
    }

    /**
     * Marks this request as completed.  Once this method is called, the
     * monitor submits a runnable to the DSF Executor to call the 
     * <code>handle...</code> methods.  
     * <p>
     * Note: Only one <code>done</code> method should be called and only once, 
     * for every request issued. Even if the request was canceled.
     * </p>  
     */
    public synchronized void done() {
        setSubmitted();
        
        if (fDone) {
            throw new IllegalStateException("RequestMonitor: " + this + ", done() method called more than once");  //$NON-NLS-1$//$NON-NLS-2$
        }
        fDone = true;
        
        // This RequestMonitor is done, it can no longer be canceled.
        // We must clear the list of cancelListeners because it causes a
        // circular reference between parent and child requestMonitor, which
        // causes a memory leak.
        fCancelListeners = null;
        
        if (fParentRequestMonitor != null) {
            fParentRequestMonitor.removeCancelListener(fCanceledListener);
        }
        
		if (DEBUG_MONITORS && !isSuccess()) {
			IStatus status = getStatus();
			try {
				throw new Exception();
			} catch (Exception e) {
				DsfPlugin.getDefault().getLog().log(new Status(status.getSeverity(), DsfPlugin.PLUGIN_ID, status.getCode(), status.getMessage() + "\n\nMonitor Back Trace:\n" + fMonitorBacktrace.toString(), e)); //$NON-NLS-1$
			}
		}
        
        try {
            fExecutor.execute(new DsfRunnable() {
                @Override
                public void run() {
                    RequestMonitor.this.handleCompleted();
                }
                @Override
                public String toString() {
                    return "Completed: " + RequestMonitor.this.toString(); //$NON-NLS-1$
                }
            });
        } catch (RejectedExecutionException e) {
            handleRejectedExecutionException();
        }
    }
    
    /**
     * Sets status and marks request monitor as completed.
     * 
     * <p>
     * Note: Only one <code>done</code> method should be called and only once, 
     * for every request issued. Even if the request was canceled.
     * </p>  
     * 
     * @param status Request processing status
     * @see #done()
     * @see #setStatus(IStatus)
     * @since 2.3
     */
    public synchronized void done(IStatus status) {
        setStatus(status);
        done();
    }

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

	/**
	 * Checks whether the given request monitor completed with success or
	 * failure result. If the request monitor was canceled it is considered a
	 * failure, regardless of the status. If the status has a severity higher
	 * than INFO (i.e., WARNING, ERROR or CANCEL), it is considered a failure.
	 */
    public boolean isSuccess() {
        return !isCanceled() && getStatus().getSeverity() <= IStatus.INFO; 
    }

	/**
	 * First tier handler for the completion of the request. By default, the
	 * {@link #done()} method drives this method on the executor specified at
	 * construction time. By default, this handler merely calls a more
	 * specialized handler, which in turn may call an even more specialized
	 * handler, and so on, thus giving a subclass the ability to
	 * compartmentalize its completion logic by overriding specific handlers.
	 * All handlers are named <code>handleXxxxx</code>. More specifically, the
	 * base implementation calls {@link #handleSuccess()} if the request
	 * succeeded, and calls {@link #handleFailure()} otherwise. <br>
	 * 
	 * The complete hierarchy of handlers is as follows: <br>
	 * <pre>
	 * + handleCompleted 
	 *   - handleSuccess 
	 *   + handleFailure 
	 *     - handleCancel
	 *     + handleErrororWarning 
	 *       - handleError 
	 *       - handleWarning
	 * </pre>
	 * 
	 * <p>
	 * Note: Sub-classes may override this method.
	 */
    @ConfinedToDsfExecutor("fExecutor")
    protected void handleCompleted() {
        if (isSuccess()) {
            handleSuccess();
        } else {
            handleFailure();
        } 
    }
    
    /**
     * Default handler for a successful the completion of a request.  If this 
     * monitor has a parent monitor that was configured by the constructor, that 
     * parent monitor is notified.  Otherwise this method does nothing. 
     * {@link #handleFailure()} or cancel otherwise.
     * <br>
     * Note: Sub-classes may override this method.
     */
    @ConfinedToDsfExecutor("fExecutor")
    protected void handleSuccess() {
        if (fParentRequestMonitor != null) {
            fParentRequestMonitor.done();
        }
    }
    
    /**
     * The default implementation of a cancellation or an error result of a 
     * request.  The implementation delegates to {@link #handleCancel()} and
     * {@link #handleErrorOrWarning()} as needed.
     * <br>
     * Note: Sub-classes may override this method.
     */
    @ConfinedToDsfExecutor("fExecutor")
    protected void handleFailure() {
        assert !isSuccess();
        
        if (isCanceled()) {
            handleCancel();
        } else {
            if (getStatus().getSeverity() == IStatus.CANCEL) {
                DsfPlugin.getDefault().getLog().log(new Status(
                    IStatus.ERROR, DsfPlugin.PLUGIN_ID, IDsfStatusConstants.INTERNAL_ERROR, "Request monitor: '" + this + "' resulted in a cancel status: " + getStatus() + ", even though the request is not set to cancel.", null)); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
            }
            handleErrorOrWarning();
        } 
    }
    
    /**
     * The default implementation of an error or warning result of a request.  
     * The implementation delegates to {@link #handleError()} and
     * {@link #handleWarning()} as needed.
     * <br>
     * Note: Sub-classes may override this method.
     */    
    @ConfinedToDsfExecutor("fExecutor")
    protected void handleErrorOrWarning() {
        if (getStatus().getSeverity() == IStatus.ERROR) {
            handleError();
        } else {
            handleWarning();
        }
    }
    
    /**
     * The default implementation of an error result of a request.  If this 
     * monitor has a parent monitor that was configured by the constructor, that 
     * parent monitor is configured with a new status containing this error.
     * Otherwise the error is logged.  
     * <br>
     * Note: Sub-classes may override this method.
     */    
    @ConfinedToDsfExecutor("fExecutor")
    protected void handleError() {
        if (fParentRequestMonitor != null) {
            fParentRequestMonitor.setStatus(getStatus());
            fParentRequestMonitor.done();
        } else {
            MultiStatus logStatus = new MultiStatus(DsfPlugin.PLUGIN_ID, IDsfStatusConstants.INTERNAL_ERROR, "Request for monitor: '" + toString() + "' resulted in an error.", null); //$NON-NLS-1$ //$NON-NLS-2$
            logStatus.merge(getStatus());
            DsfPlugin.getDefault().getLog().log(logStatus);
        }
    }
    
    /**
     * The default implementation of an error result of a request.  If this 
     * monitor has a parent monitor that was configured by the constructor, that 
     * parent monitor is configured with a new status containing this warning.
     * Otherwise the warning is logged.  
     * <br>
     * Note: Sub-classes may override this method.
     */    
    @ConfinedToDsfExecutor("fExecutor")
    protected void handleWarning() {
        if (fParentRequestMonitor != null) {
            fParentRequestMonitor.setStatus(getStatus());
            fParentRequestMonitor.done();
        }        
    }

	/**
	 * Default completion handler for a canceled request. If this monitor was
	 * constructed with a parent monitor, the status is propagated up to it.
	 * Otherwise this method does nothing. <br>
	 * Note: Sub-classes may override this method.
	 */
    @ConfinedToDsfExecutor("fExecutor")
    protected void handleCancel() {
        if (fParentRequestMonitor != null) {
            if (getStatus().getSeverity() == IStatus.CANCEL && !fParentRequestMonitor.isCanceled()) {
                fParentRequestMonitor.setStatus(new Status(
                    IStatus.ERROR, DsfPlugin.PLUGIN_ID, IDsfStatusConstants.INTERNAL_ERROR, "Sub-request " + toString() + " was canceled and not handled.'", null)); //$NON-NLS-1$ //$NON-NLS-2$
            } else {
                fParentRequestMonitor.setStatus(getStatus());
            }
            fParentRequestMonitor.done();
        }
    }
    
    /**
     * Default handler for when the executor supplied in the constructor 
     * rejects the runnable that is submitted invoke this request monitor.
     * This usually happens only when the executor is shutting down.
     * <p>
     * The default handler creates a new error status for the rejected 
     * execution and propagates it to the client or logs it.
     */
    protected void handleRejectedExecutionException() {
        IStatus rejectedStatus = new Status(IStatus.ERROR, DsfPlugin.PLUGIN_ID, IDsfStatusConstants.INTERNAL_ERROR, "Request for monitor: '" + toString() + "' resulted in a rejected execution exception.", null); //$NON-NLS-1$ //$NON-NLS-2$
        if (!getStatus().isOK()) {
            DsfMultiStatus multiStatus = new DsfMultiStatus(DsfPlugin.PLUGIN_ID, 0, "Composite status", null); //$NON-NLS-1$
            multiStatus.merge(getStatus());
            multiStatus.merge(rejectedStatus);
            rejectedStatus = multiStatus;
        }
        if (fParentRequestMonitor != null) {
            fParentRequestMonitor.setStatus(rejectedStatus);
            fParentRequestMonitor.done();
        } else {
            DsfPlugin.getDefault().getLog().log(rejectedStatus);
        }
    }
    
	/**
	 * Instrument this object with a backtrace of the monitors this instance is
	 * chained to. See {@link DsfExecutable#DEBUG_MONITORS}
	 */
    private void createMonitorBacktrace() {
    	StringBuilder str = new StringBuilder();
    	for (RequestMonitor nextrm = this; nextrm != null; nextrm = nextrm.fParentRequestMonitor) {
    		final StackTraceElement[] stackTraceElems = (nextrm.fCreatedAt != null) ? nextrm.fCreatedAt.fStackTraceElements : null;
	    	if (stackTraceElems != null && stackTraceElems.length > 0) 
	    	{
	    		str.append(stackTraceElems[0] + "\n"); //$NON-NLS-1$
	    	}
	    	else {
	    		str.append("<unknown>\n"); //$NON-NLS-1$
	    	}
    	}
    		
    	fMonitorBacktrace = str.toString();
    }
}
