/*******************************************************************************
 * Copyright (c) 2005, 2007 Freescale, Inc.
 * 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:
 *     Freescale, Inc. - initial API and implementation
 *******************************************************************************/

package org.eclipse.cdt.debug.internal.ui.views.memory;

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

import org.eclipse.cdt.debug.internal.core.CMemoryBlockRetrievalExtension;
import org.eclipse.debug.core.model.IMemoryBlockRetrieval;
import org.eclipse.debug.ui.IDebugUIConstants;
import org.eclipse.jface.dialogs.TrayDialog;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.ModifyEvent;
import org.eclipse.swt.events.ModifyListener;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.events.VerifyEvent;
import org.eclipse.swt.events.VerifyListener;
import org.eclipse.swt.graphics.FontMetrics;
import org.eclipse.swt.graphics.GC;
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.Shell;
import org.eclipse.ui.PlatformUI;

/**
 * Dialog CDT puts up when adding a memory monitor to the memory view for a 
 * debug target that supports memory spaces. 
 * <p>
 * It differs from the platform one in that you can enter an expression or
 * an address + memory space pair.
 *  
 * @since 3.2
 */
public class AddMemoryBlockDialog extends TrayDialog implements ModifyListener, SelectionListener {

	private Combo fAddressInput;
	private Button fAddressRadio;
	private Combo fMemorySpaceInput;
	private Combo fExpressionInput;
	private String fExpression;
	private Button fExpressionRadio;
	private String fAddress;
	private String fMemorySpace;
	private boolean fEnteredExpression;	// basically, which of the two radio buttons was selected when OK was hit		
	private CMemoryBlockRetrievalExtension fMemRetrieval;

	private static List<String> sAddressHistory = new ArrayList<String>();
	private static List<String> sExpressionHistory = new ArrayList<String>();
	
	private static boolean sDefaultToExpression = true;

	public AddMemoryBlockDialog(Shell parentShell,
			IMemoryBlockRetrieval memRetrieval) {
		super(parentShell);

		setShellStyle(getShellStyle() | SWT.RESIZE);

		if (memRetrieval instanceof CMemoryBlockRetrievalExtension) {
			fMemRetrieval = (CMemoryBlockRetrievalExtension)memRetrieval;  
		}
	}

	/* (non-Javadoc)
	 * @see org.eclipse.jface.dialogs.Dialog#createDialogArea(org.eclipse.swt.widgets.Composite)
	 */
	@Override
	protected Control createDialogArea(Composite parent) {
		PlatformUI.getWorkbench().getHelpSystem().setHelp(
				parent,
				IDebugUIConstants.PLUGIN_ID
				+ ".MonitorMemoryBlockDialog_context"); //$NON-NLS-1$

		// The button bar will work better if we make the parent composite
		// a single column grid layout. For the widgets we add, we want a 
		// a two-column grid, so we just create a sub composite for that.
		GridLayout gridLayout = new GridLayout();
		parent.setLayout(gridLayout);
		GridData gridData = new GridData(GridData.FILL_BOTH);
		parent.setLayoutData(gridData);
		Composite composite = new Composite(parent, SWT.None);
		gridLayout = new GridLayout();
		gridLayout.numColumns = 2;
		composite.setLayout(gridLayout);
		gridData = new GridData(GridData.FILL_BOTH);
		composite.setLayoutData(gridData);
		parent = composite;  // for all our widgets, the two-column composite is the real parent

		fExpressionRadio = new Button(parent, SWT.RADIO);
		final int radioButtonWidth = fExpressionRadio.computeSize(SWT.DEFAULT, SWT.DEFAULT).x;
		fExpressionRadio.setText(Messages.AddMemBlockDlg_enterExpression);
		gridData = new GridData();
		gridData.horizontalSpan = 2;
		fExpressionRadio.setLayoutData(gridData);
		fExpressionRadio.addSelectionListener(this);
		
		fExpressionInput = new Combo(parent, SWT.BORDER);
		gridData = new GridData(GridData.FILL_HORIZONTAL);
		gridData.horizontalSpan = 2;
		gridData.horizontalIndent = radioButtonWidth;
		fExpressionInput.setLayoutData(gridData);

		fAddressRadio = new Button(parent, SWT.RADIO);
		fAddressRadio.setText(Messages.AddMemBlockDlg_enterAddrAndMemSpace);
		gridData = new GridData(GridData.FILL_HORIZONTAL);
		gridData.horizontalSpan = 2;
		fAddressRadio.setLayoutData(gridData);
		fAddressRadio.addSelectionListener(this);

		fMemorySpaceInput = new Combo(parent, SWT.BORDER | SWT.READ_ONLY);
		gridData = new GridData();
		gridData.horizontalIndent = radioButtonWidth; 
		fMemorySpaceInput.setLayoutData(gridData);
		fMemorySpaceInput.addSelectionListener(this);		

		fAddressInput = new Combo(parent, SWT.BORDER);
		gridData = new GridData(GridData.FILL_HORIZONTAL);
		GC gc = new GC(fAddressInput);
		FontMetrics fm = gc.getFontMetrics();
		// Give enough room for a 64 bit hex address (25 is a guess at the combobox selector) 
		gridData.minimumWidth = gridData.minimumWidth = 18 * fm.getAverageCharWidth() + 25;
		gc.dispose();
		fAddressInput.setLayoutData(gridData);
		fAddressInput.addModifyListener(this);
		fAddressInput.addVerifyListener(new VerifyListener() {
			// limit entry to hex or decimal 
			public void verifyText(VerifyEvent e) {
				e.doit = false;
				final char c = e.character; 
				if (Character.isDigit(c) || ('a' <= c && c <= 'f') || ('A' <= c && c <= 'F') ||
					c == 'x' ||	Character.isISOControl(e.character)) {
					e.doit = true;
				}
			}
		});

		// Populate the memory space combobox with the available spaces
		if (fMemRetrieval != null) {
			String [] memorySpaces = fMemRetrieval.getMemorySpaces();
			for (int i = 0; i < memorySpaces.length; i++)
				fMemorySpaceInput.add(memorySpaces[i]);

			if (memorySpaces.length > 0)
				fMemorySpaceInput.select(0);
		}

		// add the history into the combo boxes 
		String[] history = getHistory(sExpressionHistory);
		for (int i = 0; i < history.length; i++)
			fExpressionInput.add(history[i]);

		history = getHistory(sAddressHistory);
		for (int i = 0; i < history.length; i++)
			fAddressInput.add(history[i]);

		fExpressionInput.addModifyListener(this);

		if (sDefaultToExpression) {
			fExpressionRadio.setSelection(true);
			fAddressRadio.setSelection(false);
			fExpressionInput.setFocus();
		}
		else {
			fAddressRadio.setSelection(false);
			fAddressRadio.setSelection(true);
			fAddressInput.setFocus();
		}
			
		return parent;
	}

	/* (non-Javadoc)
	 * @see org.eclipse.jface.window.Window#configureShell(org.eclipse.swt.widgets.Shell)
	 */
	@Override
	protected void configureShell(Shell newShell) {
		super.configureShell(newShell);

		// use the same title used by the platform dialog
		newShell.setText(Messages.AddMemBlockDlg_MonitorMemory);
	}

	/* (non-Javadoc)
	 * @see org.eclipse.jface.dialogs.Dialog#okPressed()
	 */
	@Override
	protected void okPressed() {
		fExpression = fExpressionInput.getText();
		fAddress = fAddressInput.getText();
		fMemorySpace = fMemorySpaceInput.getText();

		// add to HISTORY list; add to the platform dialog's for the expression
		if (fExpression.length() > 0)
			addHistory(sExpressionHistory, fExpression);
		if (fAddress.length() > 0)
			addHistory(sAddressHistory, fAddress);

		fEnteredExpression = fExpressionRadio.getSelection();
		super.okPressed();
	}

	/* (non-Javadoc)
	 * @see org.eclipse.swt.events.ModifyListener#modifyText(org.eclipse.swt.events.ModifyEvent)
	 */
	public void modifyText(ModifyEvent e) {
		// if user enters text into either the address field or the expression one, automatically
		// select its associated radio button (and deselect the other, these are mutually exclusive) 
		if (e.widget == fAddressInput ||
			e.widget == fExpressionInput) {

			fAddressRadio.setSelection(e.widget != fExpressionInput);
			fExpressionRadio.setSelection(e.widget == fExpressionInput);
			sDefaultToExpression = (e.widget == fExpressionInput);
		}
	}

	/* (non-Javadoc)
	 * @see org.eclipse.jface.dialogs.TrayDialog#createButtonBar(org.eclipse.swt.widgets.Composite)
	 */
	@Override
	protected Control createButtonBar(Composite parent) {
		return super.createButtonBar(parent);
	}

	public String getExpression() {
		return fExpression;
	}

	public String getAddress() {
		return fAddress;
	}

	public String getMemorySpace() {
		return fMemorySpace;
	}

	public boolean enteredExpression() {
		return fEnteredExpression;
	}
	
	private static void addHistory(List<String> list, String item)	{		
		if (!list.contains(item))
			list.add(0, item);

		if (list.size() > 5)
			list.remove(list.size()-1);
	}
	
	private static String[] getHistory(List<String> list)	{
		return list.toArray(new String[list.size()]);
	}

	/* (non-Javadoc)
	 * @see org.eclipse.swt.events.SelectionListener#widgetDefaultSelected(org.eclipse.swt.events.SelectionEvent)
	 */
	public void widgetDefaultSelected(SelectionEvent e) {
		// TODO Auto-generated method stub
		
	}

	/* (non-Javadoc)
	 * @see org.eclipse.swt.events.SelectionListener#widgetSelected(org.eclipse.swt.events.SelectionEvent)
	 */
	public void widgetSelected(SelectionEvent e) {
		// if user selects a memory space, select its associated radio button (and deselect the 
		// other, these are mutually exclusive) 
		if (e.widget == fExpressionRadio) {
			fExpressionRadio.setSelection(true);
			fAddressRadio.setSelection(false);
			fExpressionInput.setFocus();
		}
		else {
			fExpressionRadio.setSelection(false);
			fAddressRadio.setSelection(true);
			fAddressInput.setFocus();
		}
		
		sDefaultToExpression = (e.widget == fExpressionInput);
	}
}
