/*******************************************************************************
 * Copyright (c) 2000, 2017 IBM Corporation 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:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
package org.eclipse.jdt.internal.debug.ui.propertypages;

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

import org.eclipse.core.resources.IWorkspaceRunnable;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IAdaptable;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.debug.internal.ui.SWTFactory;
import org.eclipse.jdt.core.IMember;
import org.eclipse.jdt.debug.core.IJavaBreakpoint;
import org.eclipse.jdt.debug.core.IJavaLineBreakpoint;
import org.eclipse.jdt.debug.core.IJavaMethodBreakpoint;
import org.eclipse.jdt.debug.core.IJavaWatchpoint;
import org.eclipse.jdt.debug.ui.breakpoints.JavaBreakpointConditionEditor;
import org.eclipse.jdt.internal.debug.core.breakpoints.JavaClassPrepareBreakpoint;
import org.eclipse.jdt.internal.debug.core.breakpoints.JavaExceptionBreakpoint;
import org.eclipse.jdt.internal.debug.core.breakpoints.JavaLineBreakpoint;
import org.eclipse.jdt.internal.debug.core.breakpoints.JavaMethodBreakpoint;
import org.eclipse.jdt.internal.debug.core.breakpoints.JavaWatchpoint;
import org.eclipse.jdt.internal.debug.ui.BreakpointUtils;
import org.eclipse.jdt.internal.debug.ui.IJavaDebugHelpContextIds;
import org.eclipse.jdt.internal.debug.ui.JDIDebugUIPlugin;
import org.eclipse.jdt.internal.debug.ui.breakpoints.AbstractJavaBreakpointEditor;
import org.eclipse.jdt.internal.debug.ui.breakpoints.CompositeBreakpointEditor;
import org.eclipse.jdt.internal.debug.ui.breakpoints.ExceptionBreakpointEditor;
import org.eclipse.jdt.internal.debug.ui.breakpoints.MethodBreakpointEditor;
import org.eclipse.jdt.internal.debug.ui.breakpoints.StandardJavaBreakpointEditor;
import org.eclipse.jdt.internal.debug.ui.breakpoints.WatchpointEditor;
import org.eclipse.jdt.ui.JavaElementLabelProvider;
import org.eclipse.osgi.util.NLS;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.ShellEvent;
import org.eclipse.swt.events.ShellListener;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.widgets.Button;
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.ui.IPropertyListener;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.dialogs.PropertyPage;
import org.eclipse.ui.model.IWorkbenchAdapter;

/**
 * Property page for configuring IJavaBreakpoints.
 */
public class JavaBreakpointPage extends PropertyPage {

	protected JavaElementLabelProvider fJavaLabelProvider= new JavaElementLabelProvider(JavaElementLabelProvider.SHOW_DEFAULT);
	protected Button fEnabledButton;
	/*
	 * protected Button fTriggerPointButton; protected Button fTriggerPointButtonActive;
	 */
	protected List<String> fErrorMessages= new ArrayList<>();
	protected String fPrevMessage = null;
	private AbstractJavaBreakpointEditor fEditor;

	/**
	 * Attribute used to indicate that a breakpoint should be deleted
	 * when cancel is pressed.
	 */
	public static final String ATTR_DELETE_ON_CANCEL = JDIDebugUIPlugin.getUniqueIdentifier() + ".ATTR_DELETE_ON_CANCEL";  //$NON-NLS-1$

	/**
	 * Constant for the empty string
	 */
	protected static final String EMPTY_STRING = ""; //$NON-NLS-1$

	/**
	 * Store the breakpoint properties.
	 * @see org.eclipse.jface.preference.IPreferencePage#performOk()
	 */
	@Override
	public boolean performOk() {
		IWorkspaceRunnable wr = new IWorkspaceRunnable() {
			@Override
			public void run(IProgressMonitor monitor) throws CoreException {
				IJavaBreakpoint breakpoint = getBreakpoint();
				boolean delOnCancel = breakpoint.getMarker().getAttribute(ATTR_DELETE_ON_CANCEL) != null;
				if (delOnCancel) {
				    // if this breakpoint is being created, remove the "delete on cancel" attribute
				    // and register with the breakpoint manager
					breakpoint.getMarker().setAttribute(ATTR_DELETE_ON_CANCEL, (String)null);
					breakpoint.setRegistered(true);
				}
				doStore();
			}
		};
		try {
			ResourcesPlugin.getWorkspace().run(wr, null, 0, null);
		}
		catch (CoreException e) {
			JDIDebugUIPlugin.statusDialog(e.getStatus());
			JDIDebugUIPlugin.log(e);
		}
		return super.performOk();
	}

	/**
	 * Adds the given error message to the errors currently displayed on this page.
	 * The page displays the most recently added error message.
	 * Clients should retain messages that are passed into this method as the
	 * message should later be passed into removeErrorMessage(String) to clear the error.
	 * This method should be used instead of setErrorMessage(String).
	 * @param message the error message to display on this page.
	 */
	protected void addErrorMessage(String message) {
		fErrorMessages.remove(message);
		fErrorMessages.add(message);
		setErrorMessage(message);
		setValid(message == null);
	}

	/**
	 * Removes the given error message from the errors currently displayed on this page.
	 * When an error message is removed, the page displays the error that was added
	 * before the given message. This is akin to popping the message from a stack.
	 * Clients should call this method instead of setErrorMessage(null).
	 * @param message the error message to clear
	 */
	protected void removeErrorMessage(String message) {
		fErrorMessages.remove(message);
		if (fErrorMessages.isEmpty()) {
			addErrorMessage(null);
		} else {
			addErrorMessage(fErrorMessages.get(fErrorMessages.size() - 1));
		}
	}

	/**
	 * Stores the values configured in this page. This method
	 * should be called from within a workspace runnable to
	 * reduce the number of resource deltas.
	 * @throws CoreException if an exception occurs
	 */
	protected void doStore() throws CoreException {
		IJavaBreakpoint breakpoint = getBreakpoint();
		// storeTriggerPoint(breakpoint);
		// storeTriggerPointActive(breakpoint);
		storeEnabled(breakpoint);
		if (fEditor.isDirty()) {
			fEditor.doSave();
		}
	}

	/**
	 * Stores the value of the enabled state in the breakpoint.
	 * @param breakpoint the breakpoint to update
	 * @throws CoreException if an exception occurs while setting
	 *  the enabled state
	 */
	private void storeEnabled(IJavaBreakpoint breakpoint) throws CoreException {
		breakpoint.setEnabled(fEnabledButton.getSelection());
	}

	/**
	 * Creates the labels and editors displayed for the breakpoint.
	 * @see org.eclipse.jface.preference.PreferencePage#createContents(org.eclipse.swt.widgets.Composite)
	 */
	@Override
	protected Control createContents(Composite parent) {
		noDefaultAndApplyButton();
		Composite mainComposite = SWTFactory.createComposite(parent, parent.getFont(), 1, 1, GridData.FILL_HORIZONTAL, 0, 0);
		createLabels(mainComposite);
		createLabel(mainComposite, ""); //$NON-NLS-1$ // spacer

		Composite composite = SWTFactory.createComposite(mainComposite, parent.getFont(), 4, 1, 0, 0, 0);
		createEnabledButton(composite);

		createTypeSpecificEditors(mainComposite);
		setValid(true);
		// if this breakpoint is being created, change the shell title to indicate 'creation'
		try {
            if (getBreakpoint().getMarker().getAttribute(ATTR_DELETE_ON_CANCEL) != null) {
            	getShell().addShellListener(new ShellListener() {
                    @Override
					public void shellActivated(ShellEvent e) {
                        Shell shell = (Shell)e.getSource();
                        shell.setText(NLS.bind(PropertyPageMessages.JavaBreakpointPage_10, new String[]{getName(getBreakpoint())}));
                        shell.removeShellListener(this);
                    }
                    @Override
					public void shellClosed(ShellEvent e) {
                    }
                    @Override
					public void shellDeactivated(ShellEvent e) {
                    }
                    @Override
					public void shellDeiconified(ShellEvent e) {
                    }
                    @Override
					public void shellIconified(ShellEvent e) {
                    }
                });
            }
        } catch (CoreException e) {
        }
		return mainComposite;
	}

    /**
     * Returns the name of the given element.
     *
     * @param element the element
     * @return the name of the element
     */
    private String getName(IAdaptable element) {
		IWorkbenchAdapter adapter = element.getAdapter(IWorkbenchAdapter.class);
        if (adapter != null) {
            return adapter.getLabel(element);
        }
        return EMPTY_STRING;
    }

	/**
	 * Creates the labels displayed for the breakpoint.
	 * @param parent the parent composite
	 */
	protected void createLabels(Composite parent) {
		Composite labelComposite = SWTFactory.createComposite(parent, parent.getFont(), 2, 1, GridData.FILL_HORIZONTAL, 0, 0);
		try {
			String typeName = ((IJavaBreakpoint) getElement()).getTypeName();
			if (typeName != null) {
				createLabel(labelComposite, PropertyPageMessages.JavaBreakpointPage_3);
				Text text = SWTFactory.createText(labelComposite, SWT.READ_ONLY, 1, typeName);
				text.setBackground(parent.getBackground());
			}
			createTypeSpecificLabels(labelComposite);
		} catch (CoreException ce) {
			JDIDebugUIPlugin.log(ce);
		}
	}

	/**
	 * Creates the button to toggle enablement of the breakpoint
	 * @param parent the parent composite
	 */
	protected void createEnabledButton(Composite parent) {
		fEnabledButton = createCheckButton(parent, PropertyPageMessages.JavaBreakpointPage_5);
		try {
			fEnabledButton.setSelection(getBreakpoint().isEnabled());
		}
		catch(CoreException ce) {
			JDIDebugUIPlugin.log(ce);
		}
	}

	/**
	 * Returns the breakpoint that this preference page configures
	 * @return the breakpoint this page configures
	 */
	protected IJavaBreakpoint getBreakpoint() {
		return (IJavaBreakpoint) getElement();
	}

	/**
	 * Allows subclasses to add type specific labels to the common Java
	 * breakpoint page.
	 * @param parent the parent composite
	 */
	protected void createTypeSpecificLabels(Composite parent) {
		// Line number
		IJavaBreakpoint jb = getBreakpoint();
		if (jb instanceof IJavaLineBreakpoint) {
			IJavaLineBreakpoint breakpoint = (IJavaLineBreakpoint) jb;
			StringBuilder lineNumber = new StringBuilder(4);
			try {
				int lNumber = breakpoint.getLineNumber();
				if (lNumber > 0) {
					lineNumber.append(lNumber);
				}
			} catch (CoreException ce) {
				JDIDebugUIPlugin.log(ce);
			}
			if (lineNumber.length() > 0) {
				createLabel(parent, PropertyPageMessages.JavaLineBreakpointPage_2);
				Text text = SWTFactory.createText(parent, SWT.READ_ONLY, 1, lineNumber.toString());
				text.setBackground(parent.getBackground());
			}
			// Member
			try {
				IMember member = BreakpointUtils.getMember(breakpoint);
				if (member == null) {
					return;
				}
				String label = PropertyPageMessages.JavaLineBreakpointPage_3;
				if (breakpoint instanceof IJavaMethodBreakpoint) {
					label = PropertyPageMessages.JavaLineBreakpointPage_4;
				} else if (breakpoint instanceof IJavaWatchpoint) {
					label = PropertyPageMessages.JavaLineBreakpointPage_5;
				}
				createLabel(parent, label);
				Text text = SWTFactory.createText(parent, SWT.READ_ONLY, 1, fJavaLabelProvider.getText(member));
				text.setBackground(parent.getBackground());
			}
			catch (CoreException exception) {JDIDebugUIPlugin.log(exception);}
		}
	}

	/**
	* Allows subclasses to add type specific editors to the common Java
	* breakpoint page.
	* @param parent the parent composite
	*/
   protected void createTypeSpecificEditors(Composite parent) {
	   try {
		String type = getBreakpoint().getMarker().getType();
		if (JavaClassPrepareBreakpoint.JAVA_CLASS_PREPARE_BREAKPOINT.equals(type)) {
			setTitle(PropertyPageMessages.JavaBreakpointPage_11);
			fEditor = new StandardJavaBreakpointEditor();
		} else if (JavaLineBreakpoint.JAVA_LINE_BREAKPOINT.equals(type)) {
			setTitle(PropertyPageMessages.JavaLineBreakpointPage_18);
			fEditor = new CompositeBreakpointEditor(new AbstractJavaBreakpointEditor[]
			    {new StandardJavaBreakpointEditor(), new JavaBreakpointConditionEditor(null)});
		} else if (JavaExceptionBreakpoint.JAVA_EXCEPTION_BREAKPOINT.equals(type)) {
			setTitle(PropertyPageMessages.JavaExceptionBreakpointPage_5);
			fEditor = new ExceptionBreakpointEditor();
		} else if (JavaWatchpoint.JAVA_WATCHPOINT.equals(type)) {
			setTitle(PropertyPageMessages.JavaLineBreakpointPage_19);
				fEditor = new CompositeBreakpointEditor(new AbstractJavaBreakpointEditor[] { new WatchpointEditor(),
						new JavaBreakpointConditionEditor(null) });
		} else if (JavaMethodBreakpoint.JAVA_METHOD_BREAKPOINT.equals(type)) {
			setTitle(PropertyPageMessages.JavaLineBreakpointPage_20);
			fEditor = new CompositeBreakpointEditor(new AbstractJavaBreakpointEditor[]
			    {new MethodBreakpointEditor(), new JavaBreakpointConditionEditor(null)});
		} else {
			// use standard editor for any other kind of breakpoint (@see bug 325161)
			fEditor = new StandardJavaBreakpointEditor();
		}
		fEditor.createControl(parent);
		fEditor.addPropertyListener(new IPropertyListener() {
			@Override
			public void propertyChanged(Object source, int propId) {
				IStatus status = fEditor.getStatus();
				if (status.isOK()) {
					if (fPrevMessage != null) {
						removeErrorMessage(fPrevMessage);
						fPrevMessage = null;
					}
				} else {
					fPrevMessage = status.getMessage();
					addErrorMessage(fPrevMessage);
				}
			}
		});
		fEditor.setInput(getBreakpoint());
	} catch (CoreException e) {
		setErrorMessage(e.getMessage());
	}
   }

	/**
	 * Creates a fully configured text editor with the given initial value
	 * @param parent the parent composite
	 * @param initialValue the initial {@link String} value
	 * @return the configured text editor
	 */
	protected Text createText(Composite parent, String initialValue) {
		return SWTFactory.createText(parent, SWT.SINGLE | SWT.BORDER, 1, initialValue);
	}

	/**
	 * Creates a fully configured check button with the given text.
	 * @param parent the parent composite
	 * @param text the label of the returned check button
	 * @return a fully configured check button
	 */
	protected Button createCheckButton(Composite parent, String text) {
		return SWTFactory.createCheckButton(parent, text, null, false, 1);
	}

	/**
	 * Creates a fully configured label with the given text.
	 * @param parent the parent composite
	 * @param text the test of the returned label
	 * @return a fully configured label
	 */
	protected Label createLabel(Composite parent, String text) {
		return SWTFactory.createLabel(parent, text, 1);
	}

	/**
	 * Creates a fully configured radio button with the given text.
	 * @param parent the parent composite
	 * @param text the label of the returned radio button
	 * @return a fully configured radio button
	 */
	protected Button createRadioButton(Composite parent, String text) {
		return SWTFactory.createRadioButton(parent, text, 1);
	}

	/**
	 * Check to see if the breakpoint should be deleted.
	 * @return <code>true</code> if the page was canceled, <code>false</code> othewise
	 */
	@Override
	public boolean performCancel() {
		try {
			if (getBreakpoint().getMarker().getAttribute(ATTR_DELETE_ON_CANCEL) != null) {
			    // if this breakpoint is being created, delete on cancel
				getBreakpoint().delete();
			}
		} catch (CoreException e) {
			JDIDebugUIPlugin.statusDialog(PropertyPageMessages.JavaBreakpointPage_9, e.getStatus());
		}
		return super.performCancel();
	}

	/* (non-Javadoc)
	 * @see org.eclipse.jface.preference.PreferencePage#createControl(org.eclipse.swt.widgets.Composite)
	 */
	@Override
	public void createControl(Composite parent) {
		super.createControl(parent);
		PlatformUI.getWorkbench().getHelpSystem().setHelp(getControl(), IJavaDebugHelpContextIds.JAVA_BREAKPOINT_PROPERTY_PAGE);
	}
}
