/*******************************************************************************
 * Copyright (c) 2008, 2009 Nokia 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:
 *     Nokia - initial implementation. Oct. 2008
 *******************************************************************************/
package org.eclipse.cdt.tests.dsf.concurrent;

import static org.junit.Assert.assertTrue;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.ExecutionException;

import junit.framework.AssertionFailedError;

import org.eclipse.cdt.dsf.concurrent.DsfRunnable;
import org.eclipse.cdt.dsf.concurrent.RequestMonitor;
import org.eclipse.cdt.dsf.concurrent.Sequence;
import org.eclipse.cdt.tests.dsf.TestDsfExecutor;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.swt.widgets.Display;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;

/**
 * Test whether a step in a sequence can control the progress monitor. 
 */
public class DsfSequenceProgressTests {
    private List<Throwable> fExceptions = Collections.synchronizedList(new ArrayList<Throwable>());
    TestDsfExecutor fExecutor;
    
    @Before 
    public void startExecutor() throws ExecutionException, InterruptedException {
        fExecutor = new TestDsfExecutor();
    }   
    
    @After 
    public void shutdownExecutor() throws ExecutionException, InterruptedException {
        fExecutor.submit(new DsfRunnable() { public void run() {
            fExecutor.shutdown();
        }}).get();
        if (fExecutor.exceptionsCaught()) {
            Throwable[] exceptions = fExecutor.getExceptions();
            throw new ExecutionException(exceptions[0]);
        }
        fExecutor = null;
    }
    
    // Create a counter for tracking number of steps performed and steps 
    // rolled back.
    class IntegerHolder { int fInteger; }
    final IntegerHolder stepCounter = new IntegerHolder();
    final IntegerHolder rollBackCounter = new IntegerHolder();

    class SleepStep extends Sequence.Step {
    	
        @Override
		public int getTicks() {
			return 6;
		}
        
		@Override public void execute(RequestMonitor requestMonitor) {
        	stepCounter.fInteger++;
        	
        	sleep(getTicks(), requestMonitor, null);
        	
            requestMonitor.done(); 
        }
		
        @Override public void rollBack(RequestMonitor requestMonitor) {
            rollBackCounter.fInteger++;

        	sleep(1, null, null);

        	requestMonitor.done(); 
        }
        
    }
    
    class SleepStepWithProgress extends Sequence.StepWithProgress {
    	
        @Override
		public int getTicks() {
			return 6;
		}
        
        private final static int SUB_TICKS = 3;
        
		@Override 
		public void execute(RequestMonitor rm, IProgressMonitor pm) {
        	stepCounter.fInteger++;
        	
        	
            // step has its own sub-progress ticks which take the total ticks
        	// of this step and divides them into subticks
        	pm.beginTask(getTaskName() + ": ", SUB_TICKS);
        	sleep(SUB_TICKS, rm, pm);
        	
            rm.done(); 
            pm.done();
        }
		
        @Override 
        public void rollBack(RequestMonitor rm) {
            rollBackCounter.fInteger++;

        	sleep(2, null, null);
        	rm.done(); 
        }
        
    }
    
    @Test
    /**
     * It's better to run this as a manual interactive test. Run this as a JUnit
     * plugin test.<br>
     * <br>
     * In the test workbench, watch the progress bar in the Progress View.<br>
     * <br>
     * During execution of a StepWithProgress, you should see the progress bar
     * is growing and you can have more responsive cancel.<br>
     * <br>
     * Meanwhile, during execution of a step without progress, you should see
     * that progress bar does not grow and cancel does not work until end of the
     * step.<br>
     * <br>
     * Also watch that when you cancel the progress bar during the execution of
     * the sequence, you should see that "Rollback.." appears in the progress bar 
     * label.<br>
     */    
    public void sequenceProgressTest() throws InterruptedException, ExecutionException {

        final Sequence.Step[] steps = new Sequence.Step[] {
        	
            new SleepStepWithProgress() {
				@Override
				public String getTaskName() {
					return "StepWithProgress #1";
				}},

            new SleepStepWithProgress() {
				@Override
				public String getTaskName() {
					return "StepWithProgress #2";
				}},

			new SleepStep() {
				@Override
				public String getTaskName() {
					return "Step #3";
				}},

			new SleepStep() {
				@Override
				public String getTaskName() {
					return "Step #4";
				}},
        };
    	

        fExceptions.clear();
        
        Job myJob = new Job("Run test sequence") {

			@Override
			protected IStatus run(IProgressMonitor monitor) {
		        // Create and start.
		        Sequence sequence = new Sequence(fExecutor, monitor, "Run my sequence", "Rollback my sequence") {
		            @Override public Step[] getSteps() { return steps; }
		        };
		        fExecutor.execute(sequence);
		     
		        // Block and wait for sequence to complete.
		        try {
		            sequence.get();
		        } catch (InterruptedException e) {
					// ignore here. 
				} catch (ExecutionException e) {
					// Expected exception, ignore here. 
				} finally {
					try {
						System.out.println("StepCounter: " + stepCounter.fInteger);
						System.out.println("RollBackCounter: " + rollBackCounter.fInteger);
						
						if (sequence.isCancelled())
							assertTrue(
									"Wrong number of steps were rolled back after cancellation.", 
									stepCounter.fInteger == rollBackCounter.fInteger);
						else {
							assertTrue(
									"Wrong number of steps executed.", 
									stepCounter.fInteger == steps.length);
							assertTrue(
									"Some steps are mistakenly rolled back", 
									rollBackCounter.fInteger == 0);
						}
						
			            // Check state from Future interface
			            assertTrue(sequence.isDone());
					} catch (AssertionFailedError e) {
						fExceptions.add(e);
					}
		        }
				return null;
			}};
        
		myJob.schedule();
		
		// Wait for the job to finish
		waitForJob(myJob);
		
		// now throw any assertion errors.
		if (fExceptions.size() > 0)
			throw (AssertionFailedError)fExceptions.get(0);

    }

    private static void sleep(int seconds, RequestMonitor rm, IProgressMonitor pm) {
    	try {
    		for (int i = 0; i < seconds; i++) {
    		    if (pm != null)
    		        pm.subTask("subStep - " + (i+1));
    		               
    		    Thread.sleep(1000);
    			
    			if (pm != null) {
    				pm.worked(1);

    				if (pm.isCanceled()) {
    					return;
    				}
    			}
    			
    			if (rm != null && rm.isCanceled()) {
    				return;
    			}
    		}
    		
		} catch (InterruptedException e) {
			// ignore
		}
    }
    
    // Wait for a job to finish without possible blocking of UI thread.
    //
    private static void waitForJob(Job job) {
		Display display = Display.getCurrent();
		while (true) {
			IStatus status = job.getResult();
			if (status != null)
				break;
			if (display != null) {
				while (display.readAndDispatch()) ;
			}
			try {
				Thread.sleep(50);
			} catch (InterruptedException e) {
				job.cancel();
				break;
			}
		}
	}
}
