/*******************************************************************************
 *  Copyright (c) 2009 QNX Software 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:
 *      QNX Software Systems - initial API and implementation
 *      Freescale Semiconductor
 *******************************************************************************/
package org.eclipse.cdt.launch.internal.ui;


import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;

import org.eclipse.cdt.launch.internal.MultiLaunchConfigurationDelegate;
import org.eclipse.cdt.launch.internal.MultiLaunchConfigurationDelegate.LaunchElement;
import org.eclipse.cdt.launch.internal.MultiLaunchConfigurationDelegate.LaunchElement.EPostLaunchAction;
import org.eclipse.cdt.launch.ui.ComboControlledStackComposite;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.debug.core.DebugPlugin;
import org.eclipse.debug.core.ILaunchConfiguration;
import org.eclipse.debug.core.ILaunchConfigurationType;
import org.eclipse.debug.core.ILaunchManager;
import org.eclipse.debug.internal.ui.DebugUIPlugin;
import org.eclipse.debug.internal.ui.launchConfigurations.LaunchConfigurationFilteredTree;
import org.eclipse.debug.internal.ui.launchConfigurations.LaunchConfigurationManager;
import org.eclipse.debug.internal.ui.launchConfigurations.LaunchGroupFilter;
import org.eclipse.debug.ui.ILaunchGroup;
import org.eclipse.jface.dialogs.IDialogConstants;
import org.eclipse.jface.dialogs.TitleAreaDialog;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.jface.viewers.TreeViewer;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.jface.viewers.ViewerFilter;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.ModifyEvent;
import org.eclipse.swt.events.ModifyListener;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Combo;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Text;
import org.eclipse.swt.widgets.Tree;
import org.eclipse.ui.dialogs.FilteredTree;
import org.eclipse.ui.dialogs.PatternFilter;



/**
 * Dialog to select launch configuration(s)
 */
public class MultiLaunchConfigurationSelectionDialog extends TitleAreaDialog implements ISelectionChangedListener {
	private ViewerFilter[] fFilters = null;
	private ISelection fSelection;
	private ILaunchGroup[] launchGroups;
	private String mode;
	private EPostLaunchAction action = EPostLaunchAction.NONE;
	private Object actionParam;
	private boolean isDefaultMode;
	private ViewerFilter emptyTypeFilter;
	private IStructuredSelection fInitialSelection;
	private ComboControlledStackComposite fStackComposite;
	private Label fDelayAmountLabel;
	private Text fDelayAmountWidget; // in seconds
	private boolean fForEditing; // true if dialog was opened to edit an entry, otherwise it was opened to add one
	
	public MultiLaunchConfigurationSelectionDialog(Shell shell, String initMode, boolean forEditing) {
		super(shell);
		LaunchConfigurationManager manager = DebugUIPlugin.getDefault().getLaunchConfigurationManager();
		launchGroups = manager.getLaunchGroups();
		mode = initMode;
		fForEditing = forEditing;
		fFilters = null;
		setShellStyle(getShellStyle() | SWT.RESIZE);
		emptyTypeFilter = new ViewerFilter() {
			public boolean select(Viewer viewer, Object parentElement, Object element) {
				if (element instanceof ILaunchConfigurationType) {
					try {
						ILaunchConfigurationType type = (ILaunchConfigurationType) element;
						return getLaunchManager().getLaunchConfigurations(type).length > 0;
					} catch (CoreException e) {
						return false;
					}
				}
				return true;
			}
		};
	}

	protected ILaunchManager getLaunchManager() {
		return DebugPlugin.getDefault().getLaunchManager();
	}

	protected Control createContents(Composite parent) {
		Control x = super.createContents(parent);
		validate();
		setErrorMessage(null);
		return x;
	}

	protected Control createDialogArea(Composite parent2) {
		Composite comp = (Composite) super.createDialogArea(parent2);
		
		// title bar 
		getShell().setText(fForEditing ? LaunchMessages.getString("MultiLaunchConfigurationSelectionDialog.13") : LaunchMessages.getString("MultiLaunchConfigurationSelectionDialog.12")); //$NON-NLS-1$ //$NON-NLS-2$
		
		// dialog message area (not title bar)
		setTitle(fForEditing ? LaunchMessages.getString("MultiLaunchConfigurationSelectionDialog.15") : LaunchMessages.getString("MultiLaunchConfigurationSelectionDialog.14")); //$NON-NLS-1$ //$NON-NLS-2$
		
		fStackComposite = new ComboControlledStackComposite(comp, SWT.NONE);
		HashMap<String, ILaunchGroup> modes = new HashMap<String, ILaunchGroup>();
		for (ILaunchGroup launchGroup : launchGroups) {
			if (!modes.containsKey(launchGroup.getMode())) {
				modes.put(launchGroup.getMode(), launchGroup);
			}
		}
		if (this.mode.equals(MultiLaunchConfigurationDelegate.DEFAULT_MODE)) {
			try {
				this.mode = "run"; //$NON-NLS-1$
				ILaunchConfiguration[] configs = getSelectedLaunchConfigurations();
				if (configs.length > 0) {
					// we care only about the first selected element
					for (Iterator<String> iterator = modes.keySet().iterator(); iterator.hasNext();) {
						String mode = iterator.next();
						if (configs[0].supportsMode(mode)) {
							this.mode = mode;
							break;
						}
					}
				}
			} catch (Exception e) {
			}
		} 
		for (Iterator<String> iterator = modes.keySet().iterator(); iterator.hasNext();) {
			String mode = iterator.next();
			ILaunchGroup launchGroup = modes.get(mode);
			LaunchConfigurationFilteredTree fTree = new LaunchConfigurationFilteredTree(fStackComposite.getStackParent(), SWT.MULTI
			        | SWT.H_SCROLL | SWT.V_SCROLL | SWT.BORDER, new PatternFilter(), launchGroup, fFilters);
			String label = mode;
			fStackComposite.addItem(label, fTree);
			fTree.createViewControl();
			ViewerFilter[] filters = fTree.getViewer().getFilters();
			for (ViewerFilter viewerFilter : filters) {
				if (viewerFilter instanceof LaunchGroupFilter) {
					fTree.getViewer().removeFilter(viewerFilter);
				}
			}
			fTree.getViewer().addFilter(emptyTypeFilter);
			fTree.getViewer().addSelectionChangedListener(this);
			if (launchGroup.getMode().equals(this.mode)) {
				fStackComposite.setSelection(label);
			}
			if (fInitialSelection!=null) {
				
				fTree.getViewer().setSelection(fInitialSelection, true);
			}
		}
		fStackComposite.setLabelText(LaunchMessages.getString("MultiLaunchConfigurationSelectionDialog.4")); //$NON-NLS-1$
		fStackComposite.pack();
		Rectangle bounds = fStackComposite.getBounds();
		// adjust size
		GridData data = ((GridData) fStackComposite.getLayoutData());
		if (data == null) {
			data = new GridData(GridData.FILL_BOTH);
			fStackComposite.setLayoutData(data);
		}
		data.heightHint = Math.max(convertHeightInCharsToPixels(15), bounds.height);
		data.widthHint = Math.max(convertWidthInCharsToPixels(40), bounds.width);
		fStackComposite.getCombo().addSelectionListener(new SelectionAdapter() {
			public void widgetSelected(SelectionEvent e) {
				mode = ((Combo) e.widget).getText();
			}
		});
		// "Use default mode" checkbox. Use a parent composite to provide consistent left-side padding
		Composite checkboxComp = new Composite(comp, SWT.NONE);
		checkboxComp.setLayout(new GridLayout(1, false));
		checkboxComp.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
		Button checkBox = new Button(checkboxComp, SWT.CHECK);
		checkBox.setText(LaunchMessages.getString("MultiLaunchConfigurationSelectionDialog.5")); //$NON-NLS-1$
		checkBox.addSelectionListener(new SelectionAdapter() {
			public void widgetSelected(SelectionEvent e) {
				isDefaultMode = ((Button) e.widget).getSelection();
			}
		});
		checkBox.setSelection(isDefaultMode);
		
		createPostLaunchControl(comp);
		return comp;
	}

	private void createPostLaunchControl(Composite parent) {
		Composite comp = new Composite(parent, SWT.NONE);
		comp.setLayout(new GridLayout(4, false));
		comp.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
		Label label = new Label(comp, SWT.NONE);
		label.setText(LaunchMessages.getString("MultiLaunchConfigurationSelectionDialog.8")); //$NON-NLS-1$
		Combo combo = new Combo(comp, SWT.READ_ONLY);
		combo.add(LaunchElement.actionEnumToStr(EPostLaunchAction.NONE));
		combo.add(LaunchElement.actionEnumToStr(EPostLaunchAction.WAIT_FOR_TERMINATION));
		combo.add(LaunchElement.actionEnumToStr(EPostLaunchAction.DELAY));
		combo.addSelectionListener(new SelectionAdapter() {
			public void widgetSelected(SelectionEvent e) {
				final String actionStr = ((Combo) e.widget).getText();
				action = MultiLaunchConfigurationDelegate.LaunchElement.strToActionEnum(actionStr);
				showHideDelayAmountWidgets();
				validate();
			}
		});
		combo.setText(MultiLaunchConfigurationDelegate.LaunchElement.actionEnumToStr(action));
		
		fDelayAmountLabel = new Label(comp, SWT.NONE);
		fDelayAmountLabel.setText(LaunchMessages.getString("MultiLaunchConfigurationSelectionDialog.9")); //$NON-NLS-1$
		
		fDelayAmountWidget = new Text(comp, SWT.SINGLE | SWT.BORDER);
		GridData gridData = new GridData();
		gridData.widthHint = convertWidthInCharsToPixels(8);
		fDelayAmountWidget.setLayoutData(gridData);
		fDelayAmountWidget.addModifyListener(new ModifyListener(){
			public void modifyText(ModifyEvent e) {
				String text = ((Text)e.widget).getText();
				try {
					actionParam = new Integer(Integer.parseInt(text));
				}
				catch (NumberFormatException exc) {
					actionParam = null;
				}
				validate();
			}
		});
		if (actionParam instanceof Integer) {
			fDelayAmountWidget.setText(((Integer)actionParam).toString());	
		}
		
		showHideDelayAmountWidgets();
	}

	private void showHideDelayAmountWidgets() {
		final boolean visible = action == EPostLaunchAction.DELAY;
		fDelayAmountLabel.setVisible(visible);
		fDelayAmountWidget.setVisible(visible);
	}

	public ILaunchConfiguration[] getSelectedLaunchConfigurations() {
		List<ILaunchConfiguration> configs = new ArrayList<ILaunchConfiguration>(); 
		if (fSelection != null && !fSelection.isEmpty()) {
			for (Iterator<?> iter = ((IStructuredSelection)fSelection).iterator(); iter.hasNext();) {
				Object selection = iter.next();
				if (selection instanceof ILaunchConfiguration) {
					configs.add((ILaunchConfiguration)selection);
				}
			}
		}
		return configs.toArray(new ILaunchConfiguration[configs.size()]);
	}

	public String getMode() {
		return isDefaultMode ? MultiLaunchConfigurationDelegate.DEFAULT_MODE : mode;
	}
	
	public EPostLaunchAction getAction(){
		return action;
	}

	public Object getActionParam(){
		return actionParam;
	}

	public static MultiLaunchConfigurationSelectionDialog createDialog(Shell shell, String groupId, boolean forEditing) {
		return new MultiLaunchConfigurationSelectionDialog(shell, groupId, forEditing);
	}

	/* (non-Javadoc)
	 * @see org.eclipse.jface.viewers.ISelectionChangedListener#selectionChanged(org.eclipse.jface.viewers.SelectionChangedEvent)
	 */
	public void selectionChanged(SelectionChangedEvent event) {
		
		// This listener gets called for a selection change in the launch
		// configuration viewer embedded in the dialog. Problem is, there are
		// numerous viewers--one for each platform debug ILaunchGroup (run,
		// debug, profile). These viewers are stacked, so only one is ever
		// visible to the user. During initialization, we get a selection change
		// notification for every viewer. We need to ignore all but the one that
		// matters--the visible one.
		
		Tree topTree = null;
		final Control topControl = fStackComposite.getTopControl();
		if (topControl instanceof FilteredTree) {
			final TreeViewer viewer = ((FilteredTree)topControl).getViewer();
			if (viewer != null) {
				topTree = viewer.getTree();
			}
		}
		if (topTree == null) {
			return;
		}
		
		boolean selectionIsForVisibleViewer = false;
		final Object src = event.getSource();
		if (src instanceof Viewer) {
			final Control viewerControl = ((Viewer)src).getControl();
			if (viewerControl == topTree) {
				selectionIsForVisibleViewer = true;
			}
		}
		
		if (!selectionIsForVisibleViewer) {
			return;
		}
		
		fSelection = event.getSelection();
		validate();
	}

	protected void validate() {
		Button ok_button = getButton(IDialogConstants.OK_ID);
		boolean isValid = true;
		if (getSelectedLaunchConfigurations().length < 1) {
			setErrorMessage(LaunchMessages.getString("MultiLaunchConfigurationSelectionDialog.7")); //$NON-NLS-1$
			isValid = false;
		} else {
			setErrorMessage(null);
		}
		
		if (isValid) {
			if (fForEditing) {
				// must have only one selection
				if (getSelectedLaunchConfigurations().length > 1) {
					setErrorMessage(LaunchMessages.getString("MultiLaunchConfigurationSelectionDialog.11")); //$NON-NLS-1$
					isValid = false;
				}
			}
		}

		if (isValid) {
			if (action == EPostLaunchAction.DELAY) {
				isValid = (actionParam instanceof Integer) && ((Integer)actionParam > 0);
				setErrorMessage(isValid ? null : LaunchMessages.getString("MultiLaunchConfigurationSelectionDialog.10")); //$NON-NLS-1$
			}
		}
		
		if (ok_button != null) 
			ok_button.setEnabled(isValid);
	}

	public void setInitialSelection(LaunchElement el) {
		action = el.getAction();
		actionParam = el.getActionParam();
		isDefaultMode = el.getMode().equals(MultiLaunchConfigurationDelegate.DEFAULT_MODE);
	    fInitialSelection = new StructuredSelection(el.getData());   
	    fSelection = fInitialSelection;
    }
}
