/*******************************************************************************
 * Copyright (c) 2009 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
 *******************************************************************************/
package org.eclipse.cdt.dsf.debug.service.command;

import java.util.ArrayList;
import java.util.List;

import org.eclipse.cdt.dsf.concurrent.DataRequestMonitor;
import org.eclipse.cdt.dsf.concurrent.DsfExecutor;
import org.eclipse.cdt.dsf.concurrent.DsfRunnable;
import org.eclipse.cdt.dsf.concurrent.ImmediateExecutor;

/**
 * A command control which delays the <strong>results</strong> of commands 
 * sent to a command control, as well as events from the command control.  
 * The delay is specified in the constructor using a number of executor 
 * dispatch cycles.
 * 
 * @since 2.1
 */
public class BufferedCommandControl implements ICommandControl {

    private DsfExecutor fExecutor;
    private ICommandControl fControlDelegate;
    private int fDepth;
    
    private ICommandListener fCommandListener = new ICommandListener() {
        public void commandQueued(ICommandToken token) {
            for (ICommandListener processor : fCommandProcessors) {
                processor.commandQueued(token);
            }
        };
            
        public void commandRemoved(ICommandToken token) {
            for (ICommandListener processor : fCommandProcessors) {
                processor.commandRemoved(token);
            }
        };
            
        public void commandSent(final ICommandToken token) {
            for (ICommandListener processor : fCommandProcessors) {
                processor.commandSent(token);
            }
        };
        
        public void commandDone(final ICommandToken token, final ICommandResult result) {
            buffer(fDepth, new DsfRunnable() {
                public void run() {
                    for (ICommandListener processor : fCommandProcessors) {
                        processor.commandDone(token, result);
                    }
                };
            });
        };
    };

    private IEventListener fEventListener = new IEventListener() {
        public void eventReceived(final Object output) {
            buffer(fDepth, new DsfRunnable() {
                public void run() {
                    for (IEventListener processor : fEventProcessors) {
                        processor.eventReceived(output);
                    }                    
                };
            });
        }
    };
    
    private final List<ICommandListener> fCommandProcessors = new ArrayList<ICommandListener>();
    private final List<IEventListener>   fEventProcessors = new ArrayList<IEventListener>();
    
    public BufferedCommandControl(ICommandControl controlDelegate, DsfExecutor executor, int depth) {
        fControlDelegate = controlDelegate;
        fExecutor = executor;
        fDepth = depth;
        assert fDepth > 0;
    }
    
    public void addCommandListener(ICommandListener listener) {
        if (fCommandProcessors.isEmpty()) {
            fControlDelegate.addCommandListener(fCommandListener);
        }
        fCommandProcessors.add(listener);
    }


    public void removeCommandListener(ICommandListener listener) {
        fCommandProcessors.remove(listener);
        if (fCommandProcessors.isEmpty()) {
            fControlDelegate.removeCommandListener(fCommandListener);
        }
    }

    public void addEventListener(IEventListener listener) {
        if (fEventProcessors.isEmpty()) {
            fControlDelegate.addEventListener(fEventListener);
        }
        fEventProcessors.add(listener);
    }

    public void removeEventListener(IEventListener listener) {
        fEventProcessors.remove(listener);
        if (fEventProcessors.isEmpty()) {
            fControlDelegate.removeEventListener(fEventListener);
        }
    }

    public <V extends ICommandResult> ICommandToken queueCommand(final ICommand<V> command, final DataRequestMonitor<V> rm) {
        return fControlDelegate.queueCommand(
            command, 
            new DataRequestMonitor<V>(ImmediateExecutor.getInstance(), rm) {
                @Override
                protected void handleCompleted() {
                    buffer(fDepth, new DsfRunnable() {
                        public void run() {
                            rm.setData(getData());
                            rm.setStatus(getStatus());
                            rm.done();
                        }
                    });
                }
            });
    }

    public void removeCommand(ICommandToken token) {
        fControlDelegate.removeCommand(token);
    }
    
    private void buffer(final int depth, final DsfRunnable runnable) {
        if (depth == 0) {
            runnable.run();
        } else {
            fExecutor.execute(new DsfRunnable() {
                public void run() {
                    buffer(depth - 1, runnable);
                }
            });
        }
    }

}
