/*******************************************************************************
 * Copyright (c) 2004, 2016 QNX Software Systems and others.
 *
 * This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License 2.0
 * which accompanies this distribution, and is available at
 * https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contributors:
 * QNX Software Systems - Initial API and implementation
 * Nokia - https://bugs.eclipse.org/bugs/show_bug.cgi?id=145606
 * QNX Software Systems - Catchpoints support https://bugs.eclipse.org/bugs/show_bug.cgi?id=226689
 *  Scott Tepavich (WindRiver) -  Fixed bad reference to messages.properties string (Bug 393178)
 * Jonah Graham (Kichwa Coders) - Create "Add Line Breakpoint (C/C++)" action (Bug 464917)
 *******************************************************************************/
package org.eclipse.cdt.debug.internal.ui.breakpoints;

import java.io.File;
import java.util.ArrayList;
import java.util.List;

import org.eclipse.cdt.debug.core.CDIDebugModel;
import org.eclipse.cdt.debug.core.model.ICAddressBreakpoint;
import org.eclipse.cdt.debug.core.model.ICBreakpoint;
import org.eclipse.cdt.debug.core.model.ICEventBreakpoint;
import org.eclipse.cdt.debug.core.model.ICFunctionBreakpoint;
import org.eclipse.cdt.debug.core.model.ICLineBreakpoint;
import org.eclipse.cdt.debug.core.model.ICWatchpoint;
import org.eclipse.cdt.debug.core.model.ICWatchpoint2;
import org.eclipse.cdt.debug.internal.core.breakpoints.CEventBreakpoint;
import org.eclipse.cdt.debug.internal.ui.preferences.ComboFieldEditor;
import org.eclipse.cdt.debug.ui.CDebugUIPlugin;
import org.eclipse.cdt.debug.ui.breakpoints.CBreakpointUIContributionFactory;
import org.eclipse.cdt.debug.ui.breakpoints.ICBreakpointContext;
import org.eclipse.cdt.debug.ui.breakpoints.ICBreakpointsUIContribution;
import org.eclipse.cdt.debug.ui.preferences.ReadOnlyFieldEditor;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IMarker;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IAdaptable;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.Path;
import org.eclipse.debug.core.DebugPlugin;
import org.eclipse.debug.core.model.IBreakpoint;
import org.eclipse.debug.core.model.IDebugElement;
import org.eclipse.debug.core.model.IDebugModelProvider;
import org.eclipse.debug.core.model.ILineBreakpoint;
import org.eclipse.debug.ui.DebugUITools;
import org.eclipse.debug.ui.contexts.IDebugContextProvider;
import org.eclipse.jface.layout.GridDataFactory;
import org.eclipse.jface.preference.BooleanFieldEditor;
import org.eclipse.jface.preference.FieldEditor;
import org.eclipse.jface.preference.FieldEditorPreferencePage;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.jface.preference.IntegerFieldEditor;
import org.eclipse.jface.preference.StringFieldEditor;
import org.eclipse.jface.util.PropertyChangeEvent;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.window.Window;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.MouseAdapter;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.layout.FillLayout;
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.FileDialog;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Text;
import org.eclipse.ui.IWorkbenchPropertyPage;
import org.eclipse.ui.dialogs.FilteredResourcesSelectionDialog;
import org.eclipse.ui.model.IWorkbenchAdapter;

/**
 * The preference page used to present the properties of a breakpoint as preferences. A CBreakpointPreferenceStore is used to interface between this page and
 * the breakpoint.
 */
public class CBreakpointPropertyPage extends FieldEditorPreferencePage implements IWorkbenchPropertyPage {

	private Composite fEventBPComposite;
	private Composite fEventArgsComposite;
	private List<FieldEditor> fEventArgsFEs = null;

	class BreakpointIntegerFieldEditor extends IntegerFieldEditor {

		public BreakpointIntegerFieldEditor(String name, String labelText, Composite parent) {
			super(name, labelText, parent);
			setErrorMessage(BreakpointsMessages.getString("CBreakpointPropertyPage.0")); //$NON-NLS-1$
		}

		/**
		 * @see IntegerFieldEditor#checkState()
		 */
		@Override
		protected boolean checkState() {
			Text control = getTextControl();
			if (!control.isEnabled()) {
				clearErrorMessage();
				return true;
			}
			return super.checkState();
		}

		/**
		 * Overrode here to be package visible.
		 */
		@Override
		protected void refreshValidState() {
			super.refreshValidState();
		}

		/**
		 * Only store if the text control is enabled
		 *
		 * @see FieldEditor#doStore()
		 */
		@Override
		protected void doStore() {
			Text text = getTextControl();
			if (text.isEnabled()) {
				super.doStore();
			}
		}

		/**
		 * Clears the error message from the message line if the error message is the error message from this field editor.
		 */
		@Override
		protected void clearErrorMessage() {
			if (getPage() != null) {
				String message = getPage().getErrorMessage();
				if (message != null) {
					if (getErrorMessage().equals(message)) {
						super.clearErrorMessage();
					}
				} else {
					super.clearErrorMessage();
				}
			}
		}
	}

	class BreakpointStringFieldEditor extends StringFieldEditor {

		public BreakpointStringFieldEditor(String name, String labelText, Composite parent) {
			super(name, labelText, parent);
		}

		@Override
		protected void doFillIntoGrid(Composite parent, int numColumns) {
			super.doFillIntoGrid(parent, numColumns);
			// also validate on mouse clicks, eg. middle-mouse-paste
			Text textControl = getTextControl();
			textControl.addMouseListener(new MouseAdapter() {
				@Override
				public void mouseUp(MouseEvent e) {
					valueChanged();
				}
			});
		}

		/**
		 * @see StringFieldEditor#checkState()
		 */
		@Override
		protected boolean checkState() {
			Text control = getTextControl();
			if (!control.isEnabled()) {
				clearErrorMessage();
				return true;
			}
			return super.checkState();
		}

		@Override
		protected void doStore() {
			Text text = getTextControl();
			if (text.isEnabled()) {
				super.doStore();
			}
		}

		@Override
		protected void doLoad() {
			String value = getPreferenceStore().getString(getPreferenceName());
			setStringValue(value);
		}

		/**
		 * @see FieldEditor#refreshValidState()
		 */
		@Override
		protected void refreshValidState() {
			super.refreshValidState();
		}

		/**
		 * Clears the error message from the message line if the error message is the error message from this field editor.
		 */
		@Override
		protected void clearErrorMessage() {
			if (getPage() != null) {
				String message = getPage().getErrorMessage();
				if (message != null) {
					if (getErrorMessage().equals(message)) {
						super.clearErrorMessage();
					}
				} else {
					super.clearErrorMessage();
				}
			}
		}
	}

	class BreakpointFileNameFieldEditor extends BreakpointStringFieldEditor {

		private Composite composite;

		public BreakpointFileNameFieldEditor(String name, String labelText, Composite parent) {
			super(name, labelText, parent);
		}

		@Override
		protected void adjustForNumColumns(int numColumns) {
			super.adjustForNumColumns(numColumns);
			((GridData) composite.getLayoutData()).horizontalSpan = numColumns;
		}

		@Override
		protected void doFillIntoGrid(Composite parent, int numColumns) {
			super.doFillIntoGrid(parent, numColumns);

			composite = new Composite(parent, SWT.NONE);
			composite.setLayoutData(GridDataFactory.fillDefaults().grab(true, false).span(getNumberOfControls(), 1)
					.align(SWT.END, SWT.FILL).create());
			composite.setLayout(new FillLayout());
			Button browseWorkspace = new Button(composite, SWT.PUSH);
			browseWorkspace.setText(BreakpointsMessages.getString("CBreakpointPropertyPage.workspace_button")); //$NON-NLS-1$
			browseWorkspace.setFont(parent.getFont());
			browseWorkspace.addSelectionListener(new SelectionAdapter() {
				@Override
				public void widgetSelected(SelectionEvent evt) {
					FilteredResourcesSelectionDialog dialog = new FilteredResourcesSelectionDialog(getShell(), false,
							ResourcesPlugin.getWorkspace().getRoot(), IResource.FILE);
					String text = getTextControl().getText();
					IPath path = Path.fromOSString(text);
					String filename;
					if (path.segmentCount() > 0) {
						filename = path.segment(path.segmentCount() - 1);
					} else {
						filename = "*.c"; //$NON-NLS-1$
					}
					dialog.setInitialPattern(filename);
					if (dialog.open() == Window.OK) {
						Object[] result = dialog.getResult();
						if (result.length == 0)
							return;
						if (result[0] instanceof IFile) {
							IFile file = (IFile) result[0];
							IPath location = file.getRawLocation();
							if (location != null) {
								String newValue = location.makeAbsolute().toOSString();
								setStringValue(newValue);
							}
						}
					}
				}
			});

			Button browseFileSystem = new Button(composite, SWT.PUSH);
			browseFileSystem.setText(BreakpointsMessages.getString("CBreakpointPropertyPage.file_system_button")); //$NON-NLS-1$
			browseFileSystem.setFont(parent.getFont());
			browseFileSystem.addSelectionListener(new SelectionAdapter() {
				@Override
				public void widgetSelected(SelectionEvent evt) {
					FileDialog dialog = new FileDialog(getShell(), SWT.OPEN | SWT.SHEET);
					dialog.setFileName(getTextControl().getText());
					String newValue = dialog.open();
					if (newValue != null) {
						setStringValue(newValue);
					}
				}
			});
		}

		@Override
		protected boolean doCheckState() {
			// Check that the file name supplied is absolute and exists so that we can
			// associate it to an IResource later for creating a breakpoint marker.
			String stringValue = getStringValue();
			if (stringValue == null) {
				return false;
			}
			File sourceFile = new File(stringValue);
			if (!sourceFile.isAbsolute() || !sourceFile.exists() || !sourceFile.isFile()) {
				return false;
			}

			return super.doCheckState();
		}

	}

	class WatchpointRangeFieldEditor extends IntegerFieldEditor {

		private static final String DISABLED_VALUE = "0"; //$NON-NLS-1$
		private Button fCheckbox;
		private boolean fWasSelected;

		public WatchpointRangeFieldEditor(String name, String labelText, Composite parent) {
			super(name, labelText, parent);
		}

		@Override
		protected void doFillIntoGrid(Composite parent, int numColumns) {
			getCheckboxControl(parent);
			super.doFillIntoGrid(parent, numColumns);
		}

		private Button getCheckboxControl(Composite parent) {
			if (fCheckbox == null) {
				Composite inner = new Composite(parent, SWT.NULL);
				final GridLayout layout = new GridLayout(2, false);
				layout.marginWidth = 0;
				inner.setLayout(layout);
				fCheckbox = new Button(inner, SWT.CHECK);
				fCheckbox.setFont(parent.getFont());
				fCheckbox.setText(getLabelText());
				// create and hide label from base class
				Label label = getLabelControl(inner);
				label.setText(""); //$NON-NLS-1$
				label.setVisible(false);
				fCheckbox.addSelectionListener(new SelectionAdapter() {
					@Override
					public void widgetSelected(SelectionEvent e) {
						boolean isSelected = fCheckbox.getSelection();
						valueChanged(fWasSelected, isSelected);
						fWasSelected = isSelected;
					}
				});
			} else {
				checkParent(fCheckbox.getParent(), parent);
			}
			return fCheckbox;
		}

		@Override
		protected boolean checkState() {
			if (fCheckbox != null && !fCheckbox.getSelection()) {
				clearErrorMessage();
				return true;
			}
			return super.checkState();
		}

		@Override
		public Label getLabelControl(Composite parent) {
			final Label label = getLabelControl();
			if (label == null) {
				return super.getLabelControl(parent);
			} else {
				checkParent(label.getParent(), parent);
			}
			return label;
		}

		@Override
		protected void doLoad() {
			if (getTextControl() != null && fCheckbox != null && getLabelControl() != null) {
				oldValue = getPreferenceStore().getString(getPreferenceName());
				boolean enabled = !DISABLED_VALUE.equals(oldValue);
				getTextControl().setText(enabled ? oldValue : ""); //$NON-NLS-1$
				fCheckbox.setSelection(enabled);
				fWasSelected = enabled;
				getTextControl().setEnabled(enabled);
				getLabelControl().setEnabled(enabled);
			}
		}

		@Override
		protected void doStore() {
			if (fCheckbox != null && !fCheckbox.getSelection()) {
				getPreferenceStore().setValue(getPreferenceName(), DISABLED_VALUE);
			} else {
				Text text = getTextControl();
				if (text != null) {
					getPreferenceStore().setValue(getPreferenceName(), text.getText().trim());
				}
			}
		}

		@Override
		public int getIntValue() throws NumberFormatException {
			if (fCheckbox != null && !fCheckbox.getSelection()) {
				return 0;
			} else {
				return super.getIntValue();
			}
		}

		protected void valueChanged(boolean oldValue, boolean newValue) {
			if (oldValue != newValue) {
				valueChanged();
				fireStateChanged(VALUE, oldValue, newValue);
				getTextControl().setEnabled(newValue);
				getLabelControl().setEnabled(newValue);
			}
		}

	}

	class WatchpointMemorySpaceFieldEditor extends ComboFieldEditor {

		private static final String DISABLED_VALUE = ""; //$NON-NLS-1$
		private Button fCheckbox;
		private boolean fWasSelected;

		public WatchpointMemorySpaceFieldEditor(String name, String labelText, String[] memorySpaces,
				Composite parent) {
			super(name, labelText, makeArray2D(memorySpaces), parent);
		}

		@Override
		protected void doFillIntoGrid(Composite parent, int numColumns) {
			getCheckboxControl(parent);
			super.doFillIntoGrid(parent, numColumns);
		}

		private Button getCheckboxControl(Composite parent) {
			if (fCheckbox == null) {
				Composite inner = new Composite(parent, SWT.NULL);
				final GridLayout layout = new GridLayout(2, false);
				layout.marginWidth = 0;
				inner.setLayout(layout);
				fCheckbox = new Button(inner, SWT.CHECK);
				fCheckbox.setFont(parent.getFont());
				fCheckbox.setText(getLabelText());
				// create and hide label from base class
				Label label = getLabelControl(inner);
				label.setText(""); //$NON-NLS-1$
				label.setVisible(false);
				fCheckbox.addSelectionListener(new SelectionAdapter() {
					@Override
					public void widgetSelected(SelectionEvent e) {
						boolean isSelected = fCheckbox.getSelection();
						valueChanged(fWasSelected, isSelected);
						fWasSelected = isSelected;
					}
				});
			} else {
				checkParent(fCheckbox.getParent(), parent);
			}
			return fCheckbox;
		}

		@Override
		public Label getLabelControl(Composite parent) {
			final Label label = getLabelControl();
			if (label == null) {
				return super.getLabelControl(parent);
			} else {
				checkParent(label.getParent(), parent);
			}
			return label;
		}

		@Override
		protected void doLoad() {
			super.doLoad();
			if (fCheckbox != null && getLabelControl() != null) {
				String value = getPreferenceStore().getString(getPreferenceName());
				boolean enabled = !DISABLED_VALUE.equals(value);
				fCheckbox.setSelection(enabled);
				fWasSelected = enabled;
				getComboBoxControl().setEnabled(enabled);
				getLabelControl().setEnabled(enabled);
			}
		}

		@Override
		protected void doStore() {
			if (fCheckbox != null && !fCheckbox.getSelection()) {
				getPreferenceStore().setValue(getPreferenceName(), DISABLED_VALUE);
			} else {
				super.doStore();
			}
		}

		protected void valueChanged(boolean oldValue, boolean newValue) {
			if (oldValue != newValue) {
				fireStateChanged(VALUE, oldValue, newValue);
				getComboBoxControl().setEnabled(newValue);
				getLabelControl().setEnabled(newValue);
			}
		}

	}

	private String[][] makeArray2D(String[] array) {
		String[][] array2d = new String[array.length][];
		for (int i = 0; i < array.length; i++) {
			array2d[i] = new String[2];
			array2d[i][0] = array2d[i][1] = array[i];
		}
		return array2d;
	}

	private String[][] joinToArray2D(String[] labels, String[] values) {
		String[][] array2d = new String[labels.length][];
		for (int i = 0; i < labels.length; i++) {
			array2d[i] = new String[2];
			array2d[i][0] = labels[i];
			array2d[i][1] = values[i];
		}
		return array2d;
	}

	class LabelFieldEditor extends ReadOnlyFieldEditor {
		private String fValue;

		public LabelFieldEditor(Composite parent, String title, String value) {
			super(title, title, parent);
			fValue = value;
		}

		@Override
		protected void doLoad() {
			if (textField != null) {
				textField.setText(fValue);
			}
		}

		@Override
		protected void doLoadDefault() {
			// nothing
		}

	}

	private BooleanFieldEditor fEnabled;

	private BreakpointStringFieldEditor fCondition;

	private Text fIgnoreCountTextControl;

	private BreakpointFileNameFieldEditor fFileEditor;
	private BreakpointIntegerFieldEditor fLineEditor;
	private BreakpointIntegerFieldEditor fIgnoreCount;

	private IAdaptable fElement;

	/**
	 * Indicates if the page currently aims to create
	 * a breakpoint that already exits.
	 */
	private boolean fDuplicateBreakpoint;

	/**
	 * The preference store used to interface between the breakpoint and the
	 * breakpoint preference page.  This preference store is initialized only
	 * when the preference store cannot be retrieved from the preference
	 * dialog's element.
	 * @see #getPreferenceStore()
	 */
	private CBreakpointPreferenceStore fCBreakpointPreferenceStore;

	/**
	 * Constructor for CBreakpointPropertyPage.
	 *
	 * @param breakpoint
	 */
	public CBreakpointPropertyPage() {
		super(GRID);
		noDefaultAndApplyButton();
		//		Control control = getControl();
		//		fCBreakpointPreferenceStore = new CBreakpointPreferenceStore();
	}

	@Override
	protected void createFieldEditors() {
		ICBreakpoint breakpoint = getBreakpoint();
		createMainLabel(breakpoint);
		createContributedFieldEditors(breakpoint, ICBreakpointsUIContribution.BREAKPOINT_LABELS);
		createTypeSpecificLabelFieldEditors(breakpoint);
		createEnabledField(getFieldEditorParent());
		createConditionEditor(getFieldEditorParent());
		createIgnoreCountEditor(getFieldEditorParent());
		createContributedFieldEditors(breakpoint, ICBreakpointsUIContribution.BREAKPOINT_EDITORS);
	}

	private void createMainLabel(ICBreakpoint breakpoint) {
		String label = getBreakpointMainLabel(breakpoint);
		addField(createLabelEditor(getFieldEditorParent(),
				BreakpointsMessages.getString("CBreakpointPropertyPage.breakpointType_label"), //$NON-NLS-1$
				label));
	}

	/**
	 * Method createTypeSpecificLabelFieldEditors.
	 *
	 * @param breakpoint
	 */
	private void createTypeSpecificLabelFieldEditors(ICBreakpoint breakpoint) {

		if (breakpoint instanceof ICFunctionBreakpoint) {
			createFunctionEditor(getFieldEditorParent());
		} else if (breakpoint instanceof ICAddressBreakpoint) {
			String title = BreakpointsMessages.getString("CBreakpointPropertyPage.address_label"); //$NON-NLS-1$

			String address = getPreferenceStore().getString(ICLineBreakpoint.ADDRESS);
			if (address == null || address.trim().length() == 0) {
				address = BreakpointsMessages.getString("CBreakpointPropertyPage.address_valueNotAvailable_label"); //$NON-NLS-1$
			}
			addField(createLabelEditor(getFieldEditorParent(), title, address));
		} else if (breakpoint instanceof ICWatchpoint) {
			IResource resource = getResource();
			if (resource != null) {
				IProject project = resource.getProject();
				if (project != null) {
					addField(createLabelEditor(getFieldEditorParent(),
							BreakpointsMessages.getString("CBreakpointPropertyPage.project_label"), project.getName())); //$NON-NLS-1$
				}
			}
			String filename = getPreferenceStore().getString(ICBreakpoint.SOURCE_HANDLE);
			if (filename != null && !filename.isEmpty()) {
				addField(createLabelEditor(getFieldEditorParent(),
						BreakpointsMessages.getString("CBreakpointPropertyPage.sourceHandle_label"), filename)); //$NON-NLS-1$
			}
			createWatchExpressionEditor(getFieldEditorParent());
			createWatchMemorySpaceEditor(getFieldEditorParent());
			createWatchRangeEditor(getFieldEditorParent());
			createWatchTypeEditors(getFieldEditorParent());

		} else if (breakpoint instanceof ILineBreakpoint) {
			createFileLineNumberEditor(getFieldEditorParent());
		} else if (breakpoint instanceof CEventBreakpoint) {
			createEventBreakpointEditor(breakpoint, ICBreakpointsUIContribution.BREAKPOINT_LABELS);
		}
	}

	private String getBreakpointMainLabel(ICBreakpoint breakpoint) {
		if (breakpoint instanceof ICWatchpoint && breakpoint.getMarker() != null) {
			// For an existing breakpoint, calculate watchpoint label based
			// on read/write type.
			boolean isReadType = getPreferenceStore().getBoolean(ICWatchpoint.READ);
			boolean isWriteType = getPreferenceStore().getBoolean(ICWatchpoint.WRITE);
			if (isReadType && !isWriteType) {
				return BreakpointsMessages.getString("CBreakpointPropertyPage.breakpointType_watchpoint_read_label"); //$NON-NLS-1$
			} else if (!isReadType && isWriteType) {
				return BreakpointsMessages.getString("CBreakpointPropertyPage.breakpointType_watchpoint_label"); //$NON-NLS-1$
			} else {
				return BreakpointsMessages.getString("CBreakpointPropertyPage.breakpointType_watchpoint_access_label"); //$NON-NLS-1$
			}
		}

		IWorkbenchAdapter labelProvider = getElement().getAdapter(IWorkbenchAdapter.class);
		if (labelProvider != null) {
			return labelProvider.getLabel(getElement());
		}
		// default main label is the label of marker type for the breakpoint
		return CDIDebugModel.calculateMarkerType(breakpoint);
	}

	protected void createFunctionEditor(Composite parent) {

		ICBreakpoint breakpoint = getBreakpoint();
		String title = BreakpointsMessages.getString("CBreakpointPropertyPage.function_label"); //$NON-NLS-1$
		if (breakpoint == null || breakpoint.getMarker() == null) {
			BreakpointStringFieldEditor expressionEditor = new BreakpointStringFieldEditor(ICLineBreakpoint.FUNCTION,
					title, parent);
			expressionEditor.setErrorMessage(
					BreakpointsMessages.getString("CBreakpointPropertyPage.function_value_errorMessage")); //$NON-NLS-1$
			expressionEditor.setEmptyStringAllowed(false);
			addField(expressionEditor);
		} else {
			String function = getPreferenceStore().getString(ICLineBreakpoint.FUNCTION);
			if (function == null) {
				function = BreakpointsMessages.getString("CBreakpointPropertyPage.function_valueNotAvailable_label"); //$NON-NLS-1$
			}
			addField(createLabelEditor(getFieldEditorParent(),
					BreakpointsMessages.getString("CBreakpointPropertyPage.function_label"), function)); //$NON-NLS-1$
		}
	}

	protected void createFileLineNumberEditor(Composite parent) {
		String title = BreakpointsMessages.getString("CBreakpointPropertyPage.sourceHandle_label"); //$NON-NLS-1$
		ICBreakpoint breakpoint = getBreakpoint();

		boolean isNewBreakpoint = breakpoint == null || breakpoint.getMarker() == null;
		String fileName = getPreferenceStore().getString(ICBreakpoint.SOURCE_HANDLE);
		boolean isFilenameEditable = fileName != null && fileName.isEmpty();

		if (isNewBreakpoint && isFilenameEditable) {
			fFileEditor = new BreakpointFileNameFieldEditor(ICLineBreakpoint.SOURCE_HANDLE, title, parent);
			fFileEditor.setErrorMessage(BreakpointsMessages.getString("CBreakpointPropertyPage.fileName_errorMessage")); //$NON-NLS-1$
			fFileEditor.setEmptyStringAllowed(false);
			addField(fFileEditor);
		} else {
			if (fileName != null) {
				addField(createLabelEditor(parent, title, fileName));
			}
		}

		int lNumber = getPreferenceStore().getInt(IMarker.LINE_NUMBER);
		if (lNumber > 0 || isNewBreakpoint) {
			if (lNumber < 1) {
				lNumber = 1;
			}
			getPreferenceStore().setValue(IMarker.LINE_NUMBER, lNumber);
			createLineNumberEditor(parent);
		}
	}

	protected void createLineNumberEditor(Composite parent) {
		String title = BreakpointsMessages.getString("CBreakpointPropertyPage.lineNumber_label"); //$NON-NLS-1$
		fLineEditor = new BreakpointIntegerFieldEditor(IMarker.LINE_NUMBER, title, parent);
		fLineEditor.setValidRange(1, Integer.MAX_VALUE);
		fLineEditor.setErrorMessage(BreakpointsMessages.getString("CBreakpointPropertyPage.lineNumber_errorMessage")); //$NON-NLS-1$
		addField(fLineEditor);
	}

	protected void createWatchExpressionEditor(Composite parent) {
		ICBreakpoint breakpoint = getBreakpoint();
		if (breakpoint == null || breakpoint.getMarker() == null) {
			BreakpointStringFieldEditor expressionEditor = new BreakpointStringFieldEditor(ICWatchpoint.EXPRESSION,
					BreakpointsMessages.getString("CBreakpointPropertyPage.watchpoint_expression_label"), //$NON-NLS-1$
					parent);
			expressionEditor.setErrorMessage(
					BreakpointsMessages.getString("CBreakpointPropertyPage.watchpoint_expression_errorMessage")); //$NON-NLS-1$
			expressionEditor.setEmptyStringAllowed(false);
			addField(expressionEditor);
		} else {
			addField(createLabelEditor(parent,
					BreakpointsMessages.getString("CBreakpointPropertyPage.watchpoint_expression_label"), //$NON-NLS-1$
					getPreferenceStore().getString(ICWatchpoint.EXPRESSION)));
		}
	}

	protected void createWatchMemorySpaceEditor(Composite parent) {
		ICBreakpoint breakpoint = getBreakpoint();
		if (breakpoint == null || breakpoint.getMarker() == null) {
			// XXX: In pre-CDI removal this set up additional field by getting memory space
			// from ICDIMemorySpaceManagement. post-CDT removal memory space is still displayed
			// (else below) but cannot be set on new breakpoints
		} else {
			String memorySpace = getPreferenceStore().getString(ICWatchpoint2.MEMORYSPACE);
			if (memorySpace != null && memorySpace.length() != 0) {
				addField(createLabelEditor(parent,
						BreakpointsMessages.getString("CBreakpointPropertyPage.watchpoint_memorySpace_label"), //$NON-NLS-1$
						getPreferenceStore().getString(ICWatchpoint2.MEMORYSPACE)));
			}
		}
	}

	protected void createWatchRangeEditor(Composite parent) {
		ICBreakpoint breakpoint = getBreakpoint();
		if (breakpoint == null || breakpoint.getMarker() == null) {
			addField(new WatchpointRangeFieldEditor(ICWatchpoint2.RANGE,
					BreakpointsMessages.getString("CBreakpointPropertyPage.watchpoint_range_label"), //$NON-NLS-1$
					parent));
		} else {
			addField(createLabelEditor(parent,
					BreakpointsMessages.getString("CBreakpointPropertyPage.watchpoint_range_label"), //$NON-NLS-1$
					getPreferenceStore().getString(ICWatchpoint2.RANGE)));
		}
	}

	protected void createWatchTypeEditors(Composite parent) {
		// Edit read/write options only when creating the breakpoint.
		ICBreakpoint breakpoint = getBreakpoint();
		if (breakpoint != null && breakpoint.getMarker() == null) {
			addField(new BooleanFieldEditor(ICWatchpoint.READ,
					BreakpointsMessages.getString("CBreakpointPropertyPage.watchpointType_read_label"), //$NON-NLS-1$
					parent));
			addField(new BooleanFieldEditor(ICWatchpoint.WRITE,
					BreakpointsMessages.getString("CBreakpointPropertyPage.watchpointType_write_label"), //$NON-NLS-1$
					parent));
		}
	}

	protected void createEnabledField(Composite parent) {
		fEnabled = new BooleanFieldEditor(ICBreakpoint.ENABLED,
				BreakpointsMessages.getString("CBreakpointPropertyPage.enabled_label"), parent); //$NON-NLS-1$
		addField(fEnabled);
	}

	protected void createConditionEditor(Composite parent) {
		fCondition = new BreakpointStringFieldEditor(ICBreakpoint.CONDITION,
				BreakpointsMessages.getString("CBreakpointPropertyPage.condition_label"), parent); //$NON-NLS-1$
		fCondition.setEmptyStringAllowed(true);
		fCondition.setErrorMessage(
				BreakpointsMessages.getString("CBreakpointPropertyPage.condition_invalidValue_message")); //$NON-NLS-1$
		addField(fCondition);
	}

	protected void createIgnoreCountEditor(Composite parent) {
		fIgnoreCount = new BreakpointIntegerFieldEditor(ICBreakpoint.IGNORE_COUNT,
				BreakpointsMessages.getString("CBreakpointPropertyPage.ignoreCount_label"), parent); //$NON-NLS-1$
		fIgnoreCount.setValidRange(0, Integer.MAX_VALUE);
		fIgnoreCountTextControl = fIgnoreCount.getTextControl(parent);
		fIgnoreCountTextControl.setEnabled(getPreferenceStore().getInt(ICBreakpoint.IGNORE_COUNT) >= 0);
		addField(fIgnoreCount);
	}

	protected FieldEditor createLabelEditor(Composite parent, String title, String value) {
		return new LabelFieldEditor(parent, title, value);
	}

	@Override
	public boolean isValid() {
		// Don't allow to create a duplicate breakpoint
		return super.isValid() && !fDuplicateBreakpoint;
	}

	@Override
	public void propertyChange(PropertyChangeEvent event) {
		super.propertyChange(event);

		ICBreakpoint currentBp = getBreakpoint();
		if (!(currentBp instanceof ICFunctionBreakpoint) && !(currentBp instanceof ICAddressBreakpoint)
				&& !(currentBp instanceof ICWatchpoint) && !(currentBp instanceof ICEventBreakpoint)) {
			// Check for duplication of line breakpoints
			if (event.getProperty().equals(FieldEditor.VALUE)) {
				if (super.isValid()) {
					// For every change, if all the fields are valid
					// we then check if we are dealing with a duplicate
					// breakpoint.
					boolean oldValue = fDuplicateBreakpoint;
					fDuplicateBreakpoint = isDuplicateBreakpoint();
					if (oldValue != fDuplicateBreakpoint) {
						if (fDuplicateBreakpoint) {
							setErrorMessage(BreakpointsMessages
									.getString("CBreakpointPropertyPage.breakpoint_already_exists_errorMessage")); //$NON-NLS-1$
						} else {
							setErrorMessage(null);
						}
						// update container state
						if (getContainer() != null) {
							getContainer().updateButtons();
						}
						// update page state
						updateApplyButton();
					}
				}
			}
		}
	}

	private boolean isDuplicateBreakpoint() {
		String source = null;
		if (fFileEditor != null) {
			source = fFileEditor.getStringValue();
		} else {
			// If the source file is not editable, we should fetch
			// it from the preference store
			source = getPreferenceStore().getString(ICBreakpoint.SOURCE_HANDLE);
		}

		// We only check duplicates for Breakpoints with lines numbers
		if (fLineEditor == null) {
			return false;
		}

		int line = fLineEditor.getIntValue();

		// Look for any breakpoint that has the same source file and line number as what
		// is currently being inputed.  Careful not to compare with the current breakpoint
		// in the case of modifying the breakpoint properties of an existing breakpoint; in
		// that case we of course have this particular bp at this file and line.
		ICBreakpoint currentBp = getBreakpoint();
		IBreakpoint[] breakpoints = DebugPlugin.getDefault().getBreakpointManager().getBreakpoints();
		for (IBreakpoint bp : breakpoints) {
			if (!bp.equals(currentBp) && bp instanceof ICBreakpoint) {
				IMarker marker = bp.getMarker();
				if (marker != null) {
					String markerFile = marker.getAttribute(ICBreakpoint.SOURCE_HANDLE, ""); //$NON-NLS-1$
					int markerLine = marker.getAttribute(IMarker.LINE_NUMBER, -1);
					if (source.equals(markerFile) && line == markerLine) {
						// Woops, we already have another breakpoint at this file:line
						return true;
					}
				}
			}
		}
		return false;
	}

	protected ICBreakpoint getBreakpoint() {
		IAdaptable element = getElement();
		if (element instanceof ICBreakpoint) {
			return (ICBreakpoint) element;
		} else if (element instanceof ICBreakpointContext) {
			return ((ICBreakpointContext) element).getBreakpoint();
		} else {
			return element.getAdapter(ICBreakpoint.class);
		}
	}

	protected Object getDebugContext() {
		IDebugContextProvider provider = getElement().getAdapter(IDebugContextProvider.class);
		if (provider != null) {
			ISelection selection = provider.getActiveContext();
			if (selection instanceof IStructuredSelection) {
				return ((IStructuredSelection) selection).getFirstElement();
			}
			return null;
		}
		return DebugUITools.getDebugContext();
	}

	protected IResource getResource() {
		IAdaptable element = getElement();
		if (element instanceof ICBreakpoint) {
			IMarker marker = ((ICBreakpoint) element).getMarker();
			if (marker != null) {
				return marker.getResource();
			}
		} else if (element instanceof ICBreakpointContext) {
			return ((ICBreakpointContext) element).getResource();
		}
		return null;
	}

	@Override
	public IPreferenceStore getPreferenceStore() {
		IAdaptable element = getElement();
		if (element instanceof ICBreakpointContext) {
			return ((ICBreakpointContext) element).getPreferenceStore();
		}

		if (fCBreakpointPreferenceStore == null) {
			CBreakpointContext bpContext = element instanceof CBreakpointContext ? (CBreakpointContext) element : null;
			fCBreakpointPreferenceStore = new CBreakpointPreferenceStore(bpContext, null);
		}
		return fCBreakpointPreferenceStore;
	}

	@Override
	public boolean performCancel() {
		IPreferenceStore store = getPreferenceStore();
		if (store instanceof CBreakpointPreferenceStore) {
			((CBreakpointPreferenceStore) store).setCanceled(true);
		}
		return super.performCancel();
	}

	@Override
	public boolean performOk() {
		IPreferenceStore store = getPreferenceStore();
		if (store instanceof CBreakpointPreferenceStore) {
			((CBreakpointPreferenceStore) store).setCanceled(false);
		}
		return super.performOk();
	}

	/* (non-Javadoc)
	 * @see org.eclipse.ui.IWorkbenchPropertyPage#getElement()
	 */
	@Override
	public IAdaptable getElement() {
		return fElement;
	}

	/* (non-Javadoc)
	 * @see org.eclipse.ui.IWorkbenchPropertyPage#setElement(org.eclipse.core.runtime.IAdaptable)
	 */
	@Override
	public void setElement(IAdaptable element) {
		if (element instanceof ICBreakpoint) {
			fElement = new CBreakpointContext((ICBreakpoint) element, null);
		} else {
			fElement = element;
		}
	}

	protected String[] getDebugModelIds() {
		String[] debugModelIds = null;
		Object debugContext = getDebugContext();
		IDebugModelProvider debugModelProvider = (IDebugModelProvider) DebugPlugin.getAdapter(debugContext,
				IDebugModelProvider.class);
		if (debugModelProvider != null) {
			debugModelIds = debugModelProvider.getModelIdentifiers();
		} else if (debugContext instanceof IDebugElement) {
			debugModelIds = new String[] { ((IDebugElement) debugContext).getModelIdentifier() };
		}
		return debugModelIds;
	}

	private void createEventBreakpointEditor(ICBreakpoint breakpoint, String conMainElement) {
		boolean bAddEventType = true;
		Composite parent = getFieldEditorParent();
		String[] debugModelIds = getDebugModelIds();
		try {
			ICBreakpointsUIContribution[] cons;
			CBreakpointUIContributionFactory factory = CBreakpointUIContributionFactory.getInstance();
			IPreferenceStore prefStore = getPreferenceStore();
			if (prefStore instanceof CBreakpointPreferenceStore) {
				cons = factory.getBreakpointUIContributions(debugModelIds, breakpoint,
						((CBreakpointPreferenceStore) prefStore).getAttributes());
			} else {
				cons = factory.getBreakpointUIContributions(breakpoint);
			}

			setupEventTypeFieldEditor(cons, breakpoint, conMainElement, parent);
			for (ICBreakpointsUIContribution con : cons) {
				if (conMainElement.equals(con.getMainElement())) {
					FieldEditor fieldEditor = null;
					if (breakpoint.getMarker() == null && con.getId().equals(ICEventBreakpoint.EVENT_TYPE_ID)) {
						continue;
					} else if (con.getMarkerType().equalsIgnoreCase(ICEventBreakpoint.C_EVENT_BREAKPOINT_MARKER)) {
						if (breakpoint.getMarker() == null) {
							setupArgsComposite(parent);
							fieldEditor = con.getFieldEditor(con.getId(), con.getLabel() + ":", fEventArgsComposite); //$NON-NLS-1$
							if (fieldEditor != null) {
								addEditorToComposite(fieldEditor);
								fEventArgsComposite.setVisible(true);
							} else {
								fEventArgsComposite.setVisible(false);
							}
						} else {
							if (con.getId().equals(ICEventBreakpoint.EVENT_TYPE_ID)) {
								if (bAddEventType == true)
									bAddEventType = false;
								else
									continue;
							}
							fieldEditor = con.getFieldEditor(con.getId(), con.getLabel() + ":", parent); //$NON-NLS-1$
						}
					}

					if (fieldEditor != null) {
						addField(fieldEditor);
					}
				}
			}
		} catch (CoreException ce) {
			CDebugUIPlugin.log(ce);
		}
	}

	/**
	 * Creates field editors contributed using breakpointUIContribution extension point
	 */
	private void createContributedFieldEditors(ICBreakpoint breakpoint, String conMainElement) {
		Composite parent = getFieldEditorParent();
		String[] debugModelIds = getDebugModelIds();
		try {
			ICBreakpointsUIContribution[] cons;
			CBreakpointUIContributionFactory factory = CBreakpointUIContributionFactory.getInstance();
			IPreferenceStore prefStore = getPreferenceStore();
			if (prefStore instanceof CBreakpointPreferenceStore) {
				cons = factory.getBreakpointUIContributions(debugModelIds, breakpoint,
						((CBreakpointPreferenceStore) prefStore).getAttributes());
			} else {
				cons = factory.getBreakpointUIContributions(breakpoint);
			}

			for (ICBreakpointsUIContribution con : cons) {
				if (conMainElement.equals(con.getMainElement())) {
					if (con.getMarkerType().equals(ICEventBreakpoint.C_EVENT_BREAKPOINT_MARKER)) {
						continue;
					}
					FieldEditor fieldEditor = con.getFieldEditor(con.getId(), con.getLabel() + ":", parent); //$NON-NLS-1$
					if (fieldEditor != null) {
						addField(fieldEditor);
					}
				}
			}
		} catch (CoreException ce) {
			CDebugUIPlugin.log(ce);
		}
	}

	private void setupEventTypeFieldEditor(ICBreakpointsUIContribution[] cons, ICBreakpoint breakpoint,
			String conMainElement, Composite parent) {
		String id = null;
		ArrayList<String> eventTypeValueList = new ArrayList<>();
		ArrayList<String> eventTypeLabelList = new ArrayList<>();

		// The filter of the debugModelIds should already be done.
		for (ICBreakpointsUIContribution con : cons) {
			if (conMainElement.equals(con.getMainElement())) {
				if (breakpoint instanceof CEventBreakpoint && breakpoint.getMarker() == null
						&& con.getId().equals(ICEventBreakpoint.EVENT_TYPE_ID)) {
					id = con.getId();
					for (String value : con.getPossibleValues()) {
						eventTypeValueList.add(value);
						eventTypeLabelList.add(con.getLabelForValue(value));
					}
				}
			}
		}
		if (eventTypeValueList.size() != 0) {
			EventTypeFieldEditor fieldEditor = new EventTypeFieldEditor(id,
					BreakpointsMessages.getString("CBreakpointPropertyPage.eventType_label"), //$NON-NLS-1$
					eventTypeLabelList.toArray(new String[eventTypeLabelList.size()]),
					eventTypeValueList.toArray(new String[eventTypeValueList.size()]), parent, breakpoint);
			addField(fieldEditor);
			setupArgsComposite(parent);
			fieldEditor.initializeComboBox(getPreferenceStore(), this);
		}
	}

	void addEditorToComposite(FieldEditor fieldEditor) {
		if (fEventArgsFEs == null) {
			fEventArgsFEs = new ArrayList<>();
		}
		fEventArgsFEs.add(fieldEditor);
	}

	void cleanEditorsFromComposite() {
		if (fEventArgsFEs != null) {
			for (FieldEditor editor : fEventArgsFEs) {
				editor.setPreferenceStore(null);
				editor.setPage(null);
			}
		}
	}

	void setupArgsComposite(Composite parent) {
		if (fEventArgsComposite != null) {
			cleanEditorsFromComposite();
			fEventArgsComposite.dispose();
			fEventArgsComposite = null;
		}
		if (fEventBPComposite == null || fEventBPComposite.isDisposed()) {
			fEventBPComposite = new Composite(parent, SWT.NONE);
			fEventBPComposite.setLayout(parent.getLayout());
			fEventBPComposite.setLayoutData(new GridData(SWT.FILL, SWT.NONE, true, false));
			GridDataFactory.defaultsFor(fEventBPComposite).grab(true, false).span(2, 1).applyTo(fEventBPComposite);
		}
		fEventArgsComposite = new Composite(fEventBPComposite, SWT.NONE);
		fEventArgsComposite.setLayout(fEventBPComposite.getLayout());
		GridDataFactory.defaultsFor(fEventArgsComposite).grab(true, false).span(2, 1).applyTo(fEventArgsComposite);
		GridData gridData = (GridData) fEventArgsComposite.getLayoutData();
		gridData.horizontalIndent = 10;
		fEventArgsComposite.setLayoutData(gridData);
		fEventArgsComposite.setVisible(false);
	}

	private void displayEventArgs(ICBreakpoint breakpoint, Composite parent) {
		boolean result = false;
		String[] debugModelIds = getDebugModelIds();

		try {
			ICBreakpointsUIContribution[] cons;
			CBreakpointUIContributionFactory factory = CBreakpointUIContributionFactory.getInstance();
			IPreferenceStore prefStore = getPreferenceStore();
			if (prefStore instanceof CBreakpointPreferenceStore) {
				cons = factory.getBreakpointUIContributions(debugModelIds, breakpoint,
						((CBreakpointPreferenceStore) prefStore).getAttributes());
			} else {
				cons = factory.getBreakpointUIContributions(breakpoint);
			}
			for (ICBreakpointsUIContribution con : cons) {
				if (con.getMarkerType().equalsIgnoreCase(ICEventBreakpoint.C_EVENT_BREAKPOINT_MARKER)
						&& !con.getId().equals(ICEventBreakpoint.EVENT_TYPE_ID)) {
					setupArgsComposite(parent);
					FieldEditor fieldEditor = con.getFieldEditor(con.getId(), con.getLabel() + ":", //$NON-NLS-1$
							fEventArgsComposite);
					if (fieldEditor != null) {
						fieldEditor.setPreferenceStore(getPreferenceStore());
						fieldEditor.setPage(this);
						addEditorToComposite(fieldEditor);
						addField(fieldEditor);
						result = true;
					}
				}
			}
		} catch (CoreException ce) {
			CDebugUIPlugin.log(ce);
		}

		if (fEventArgsComposite != null && !fEventArgsComposite.isDisposed()) {
			fEventArgsComposite.setVisible(result);
			fEventArgsComposite.layout();
			fEventBPComposite.layout();
		}
	}

	class EventTypeFieldEditor extends ComboFieldEditor {

		final Combo fCombo;
		final Composite fParent;
		final ICBreakpoint fBreakpoint;

		class EventTypeSelectionListener implements SelectionListener {
			@Override
			public void widgetSelected(SelectionEvent e) {
				doStore();
				displayEventArgs(fBreakpoint, fParent);
				fParent.layout();
			}

			@Override
			public void widgetDefaultSelected(SelectionEvent e) {
				widgetSelected(e);
			}
		}

		public EventTypeFieldEditor(String name, String labelText, String[] eventTypesLabels, String[] eventTypesValues,
				Composite parent, ICBreakpoint breakpoint) {
			super(name, labelText, joinToArray2D(eventTypesLabels, eventTypesValues), parent);
			fBreakpoint = breakpoint;
			fParent = parent;
			fCombo = this.getComboBoxControl();
			fCombo.select(0);
			fCombo.addSelectionListener(new EventTypeSelectionListener());
		}

		public void initializeComboBox(IPreferenceStore prefStore, CBreakpointPropertyPage page) {
			if (getPage() == null) {
				setPage(page);
			}
			if (getPreferenceStore() == null) {
				setPreferenceStore(prefStore);
			} else
				prefStore = getPreferenceStore();

			String value = getValueForName(fCombo.getText());
			if (prefStore instanceof CBreakpointPreferenceStore) {
				prefStore.setValue(ICEventBreakpoint.EVENT_TYPE_ID, value);
			}
			displayEventArgs(fBreakpoint, fParent);
		}
	}
}
