/*******************************************************************************
 * Copyright (c) 2008, 2011 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
 *******************************************************************************/
//#ifdef exercises
package org.eclipse.cdt.examples.dsf.dataviewer;
//#else
//#package org.eclipse.cdt.examples.dsf.dataviewer.answers;
//#endif

import java.util.Arrays;
import java.util.List;
import java.util.Set;

import org.eclipse.cdt.dsf.concurrent.CountingRequestMonitor;
import org.eclipse.cdt.dsf.concurrent.DataRequestMonitor;
import org.eclipse.cdt.dsf.concurrent.ImmediateExecutor;
import org.eclipse.cdt.dsf.concurrent.Query;
import org.eclipse.jface.viewers.IStructuredContentProvider;
import org.eclipse.jface.viewers.TableViewer;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;

/**
 * Data viewer based on a table, which reads data using synchronous methods.
 * <p>
 * This viewer implements the {@link IStructuredContentProvider} interface 
 * which is used by the JFace TableViewer class to populate a Table.  This 
 * interface contains one principal methods for reading data {@link #getElements(Object)}, 
 * which synchronously returns an array of elements.  In order to implement 
 * this method using the asynchronous data generator, this provider uses the 
 * {@link Query} object. 
 * </p>
 */
public class SyncDataViewer 
    implements IStructuredContentProvider, IDataGenerator.Listener 
{
    // The viewer and generator that this content provider using.
    final private TableViewer fViewer;
    final private IDataGenerator fDataGenerator;
    
    public SyncDataViewer(TableViewer viewer, IDataGenerator generator) {
        fViewer = viewer;
        fDataGenerator = generator;
        fDataGenerator.addListener(this);
    }
    
    public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
        // Not used
    }

    
    public Object[] getElements(Object inputElement) {
        
        // Create the query object for reading data count. 
        Query<Integer> countQuery = new Query<Integer>() {
            @Override
            protected void execute(DataRequestMonitor<Integer> rm) {
                fDataGenerator.getCount(rm);
            }
        };
        
        // Submit the query to be executed.  A query implements a runnable
        // interface and it has to be executed in order to do its work.
        ImmediateExecutor.getInstance().execute(countQuery);
        int count = 0;
        
        // Block until the query completes, which will happen when the request
        // monitor of the execute() method is marked done.
        try {
            count = countQuery.get();
        } catch (Exception e) { 
            // InterruptedException and ExecutionException can be thrown here.
            // ExecutionException containing a CoreException will be thrown 
            // if an error status is set to the Query's request monitor.
            return new Object[0]; 
        } 

        final int finalCount = count; 
        Query<List<Integer>> valueQuery = new Query<List<Integer>>() {
            @Override
            protected void execute(final DataRequestMonitor<List<Integer>> rm) {
                final Integer[] retVal = new Integer[finalCount];
                final CountingRequestMonitor crm = new CountingRequestMonitor(
                    ImmediateExecutor.getInstance(), rm) 
                {
                    @Override
                    protected void handleSuccess() {
                        rm.setData(Arrays.asList(retVal));
                        rm.done();
                    };
                };
                for (int i = 0; i < finalCount; i++) {
                    final int finalI = i;
                    fDataGenerator.getValue(
                        i, 
                        new DataRequestMonitor<Integer>(
                            ImmediateExecutor.getInstance(), crm) 
                        {
                            @Override
                            protected void handleSuccess() {
                                retVal[finalI] = getData();
                                crm.done();
                            }
                        });
                }
                crm.setDoneCount(finalCount);
            }
        };
        ImmediateExecutor.getInstance().execute(valueQuery);
        try {
            return valueQuery.get().toArray(new Integer[0]);
        } catch (Exception e) { 
        }
        return new Object[0];
    }

    public void dispose() {
        fDataGenerator.removeListener(this);
    }

    public void countChanged() {
        // For any event from the generator, refresh the whole viewer.
        refreshViewer();
    }
    
    public void valuesChanged(Set<Integer> indexes) {
        // For any event from the generator, refresh the whole viewer.
        refreshViewer();
    }
    
    private void refreshViewer() {
        //#ifdef exercises
        // TODO Exercise 5 - Add a call to getElements() to force a deadlock.
        //#else
//#        getElements(null);
        //#endif
        
        // This method may be called on any thread, switch to the display 
        // thread before calling the viewer.
        Display display = fViewer.getControl().getDisplay(); 
        display.asyncExec( new Runnable() {
            public void run() {
                if (!fViewer.getControl().isDisposed()) {
                    fViewer.refresh();
                }
            }
        });
    }
    
    /**
     * The entry point for the example.
     * @param args Program arguments.
     */
    public static void main(String[] args) {
        // Create the shell to hold the viewer.
        Display display = new Display();
        Shell shell = new Shell(display, SWT.SHELL_TRIM);
        shell.setLayout(new GridLayout());
        GridData data = new GridData(GridData.FILL_BOTH);
        shell.setLayoutData(data);
        Font font = new Font(display, "Courier", 10, SWT.NORMAL);

        // Create the table viewer.
        TableViewer tableViewer = new TableViewer(shell, SWT.BORDER);
        tableViewer.getControl().setLayoutData(data);

        // Create the data generator.
        //#ifdef exercises
        // TODO Exercise 5 - Use the DataGeneratorWithExecutor() instead.
        final IDataGenerator generator = new DataGeneratorWithThread();
        //#else
//#        final IDataGenerator generator = new DataGeneratorWithExecutor();     
        //#endif
        
        // Create the content provider which will populate the viewer.
        SyncDataViewer contentProvider = 
            new SyncDataViewer(tableViewer, generator);
        tableViewer.setContentProvider(contentProvider);
        tableViewer.setInput(new Object());

        // Open the shell and service the display dispatch loop until user
        // closes the shell.
        shell.open();
        while (!shell.isDisposed()) {
            if (!display.readAndDispatch())
                display.sleep();
        }
        
        // The IDataGenerator.shutdown() method is asynchronous, this requires
        // using a query again in order to wait for its completion.
        Query<Object> shutdownQuery = new Query<Object>() {
            @Override
            protected void execute(DataRequestMonitor<Object> rm) {
                generator.shutdown(rm);
            }
        };
        ImmediateExecutor.getInstance().execute(shutdownQuery);
        try {
            shutdownQuery.get();
        } catch (Exception e) {}
        
        // Shut down the display.
        font.dispose();    
        display.dispose();
    }
    
}
