/*****************************************************************
 * Copyright (c) 2011, 2014 Texas Instruments 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:
 *     Winnie Lai (Texas Instruments) - Individual Element Number Format (Bug 202556)
 *     Marc Khouzam (Ericsson) - Base available formats on each element (Bug 439624)
 *****************************************************************/
package org.eclipse.cdt.dsf.debug.ui.viewmodel.numberformat;

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

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.debug.ui.viewmodel.IDebugVMConstants;
import org.eclipse.cdt.dsf.debug.ui.viewmodel.actions.VMHandlerUtils;
import org.eclipse.cdt.dsf.ui.concurrent.SimpleDisplayExecutor;
import org.eclipse.cdt.dsf.ui.viewmodel.IVMContext;
import org.eclipse.cdt.dsf.ui.viewmodel.IVMNode;
import org.eclipse.cdt.dsf.ui.viewmodel.IVMProvider;
import org.eclipse.cdt.dsf.ui.viewmodel.update.ICacheEntry;
import org.eclipse.cdt.dsf.ui.viewmodel.update.ICachingVMProviderExtension2;
import org.eclipse.debug.internal.ui.viewers.model.provisional.IPresentationContext;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.action.ActionContributionItem;
import org.eclipse.jface.action.ContributionItem;
import org.eclipse.jface.action.IContributionItem;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.ITreeSelection;
import org.eclipse.jface.viewers.TreePath;
import org.eclipse.swt.SWT;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Menu;
import org.eclipse.swt.widgets.MenuItem;

/**
 * Dynamic menu contribution that shows available number formats in the current
 * selection of the view.
 * 
 * @since 2.2
 */
public class ElementNumberFormatsContribution extends NumberFormatsContribution {

	static class SelectFormatAction extends Action {
		private final IElementFormatProvider fProvider;
		private final IPresentationContext fContext;
		private final IVMNode[] fNodes;
		private final Object fViewerInput;
		private final TreePath[] fElementPaths;
		private final String fFormatId;

		SelectFormatAction(IElementFormatProvider provider, IPresentationContext context, IVMNode[] nodes,
				Object viewerInput, TreePath[] elementPaths, String formatId) {
			super(formatId == null ? MessagesForNumberFormat.ElementNumberFormatContribution_RestoreToPreference_label : 
				                     FormattedValueVMUtil.getFormatLabel(formatId),
					formatId == null ? AS_PUSH_BUTTON : AS_RADIO_BUTTON);
			fProvider = provider;
			fContext = context;
			fNodes = nodes;
			fViewerInput = viewerInput;
			fElementPaths = elementPaths;
			fFormatId = formatId;
		}

		@Override
		public void run() {
			if (fFormatId == null) {
				fProvider.setActiveFormat(fContext, fNodes, fViewerInput, fElementPaths, fFormatId);
				return;
			}
			if (isChecked()) {
				fProvider.setActiveFormat(fContext, fNodes, fViewerInput, fElementPaths, fFormatId);
			}
		}
	}

	protected static IContributionItem[] NO_ITEMS = new IContributionItem[] {
		new ContributionItem() {
			@Override
			public void fill(Menu menu, int index) {
				MenuItem item = new MenuItem(menu, SWT.NONE);
				item.setEnabled(false);
				item.setText(MessagesForNumberFormat.NumberFormatContribution_EmptyFormatsList_label);
			}
	
			@Override
			public boolean isEnabled() {
				return false;
			}
		}
	};

	@Override
	protected IContributionItem[] getContributionItems() {
		ISelection selection = VMHandlerUtils.getSelection(fServiceLocator);
		if (selection == null || selection.isEmpty() || selection instanceof ITreeSelection == false) {
			return NO_ITEMS;
		}
		
		IVMProvider provider = VMHandlerUtils.getVMProviderForSelection(selection);
		if (provider instanceof IElementFormatProvider == false) {
			return NO_ITEMS;
		}
		
		IPresentationContext context = provider.getPresentationContext();		
		Object viewerInput = VMHandlerUtils.getViewerInput(context);
		TreePath[] elementPaths = ((ITreeSelection) selection).getPaths();
		List<String> availableFormats = getAvailableFormats(provider, viewerInput, elementPaths);
		if (availableFormats.size() == 0) {
			return NO_ITEMS;
		}

		IVMNode[] nodes = new IVMNode[elementPaths.length];
		final List<SelectFormatAction> actions = new ArrayList<SelectFormatAction>(availableFormats.size());
		for (String formatId : availableFormats) {
			actions.add(new SelectFormatAction((IElementFormatProvider) provider,
					context, nodes, viewerInput, elementPaths, formatId));
		}

		final String[] elementActiveFormats = new String[elementPaths.length];
		CountingRequestMonitor crm = new CountingRequestMonitor(SimpleDisplayExecutor.getSimpleDisplayExecutor(Display.getDefault()), null) {
			@Override
			protected void handleCompleted() {
				String activeFormat = null;
				for (int i = 0; i < elementActiveFormats.length; i++) {
					if (i == 0) {
						activeFormat = elementActiveFormats[i];
					} else if (activeFormat != null
							&& activeFormat.equals(elementActiveFormats[i]) == false) {
						activeFormat = null;
						break;
					}
				}
				if (activeFormat != null) {
					for (int i = 0; i < actions.size(); i++) {
						if (activeFormat.equals(actions.get(i).fFormatId)) {
							actions.get(i).setChecked(true);
							break;
						}
					}
				}
			}
		};
		for (int i = 0; i < elementPaths.length; i++) {
			Object segment = elementPaths[i].getLastSegment();
			if (segment instanceof IVMContext) {
				nodes[i] = ((IVMContext) segment).getVMNode();
			} else {
				nodes[i] = null;
			}
			final int index = i;
			((IElementFormatProvider) provider).getActiveFormat(context, nodes[i], viewerInput, elementPaths[i],
					new DataRequestMonitor<String>(ImmediateExecutor.getInstance(), crm) {
				@Override
				protected void handleSuccess() {
					elementActiveFormats[index] = this.getData();
					super.handleSuccess();
				}
			});
		}
		crm.setDoneCount(elementPaths.length);
		int count = actions.size();
		IContributionItem[] items = new IContributionItem[count];
		for (int i = 0; i < items.length; i++) {
			items[i] = new ActionContributionItem(actions.get(i));
		}
		return items;
	}

	/**
	 * Get the available formats for all elements in the selection.  If all elements have the same
	 * available formats, return that list; if not, return the default list.
	 */
	private List<String> getAvailableFormats(IVMProvider provider, Object viewerInput, TreePath[] paths) {
		if (provider instanceof ICachingVMProviderExtension2) {
			ICachingVMProviderExtension2 cachingProvider = (ICachingVMProviderExtension2)provider;

			String[] formats = null;
			for (TreePath path : paths) {
				IVMNode node = VMHandlerUtils.getVMNode(viewerInput, path);
				if (node != null) {
					ICacheEntry cacheEntry = cachingProvider.getCacheEntry(node, viewerInput, path);
					if (cacheEntry != null && cacheEntry.getProperties() != null) {
						String[] entryFormats = (String[]) cacheEntry.getProperties().get(IDebugVMConstants.PROP_FORMATTED_VALUE_AVAILABLE_FORMATS);
						if (entryFormats == null) {
							// At least one element has no formats.  Use the default ones.
							return FORMATS;
						}
						if (formats == null) {
							// First set of formats
							formats = entryFormats;
						} else {
							// Found another set of formats.  Make sure it is the same as the set we already have.
							// If not, return the default set of formats.
							if (!Arrays.equals(formats, entryFormats)) {
								return FORMATS;
							}
						}
					}
				}
			}
			if (formats != null) {
				return Arrays.asList(formats);
			}
		}
		return FORMATS;
	}
}