/*******************************************************************************
 * Copyright (c) 2006, 2015 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.debug.core.model.IDebugElement;
import org.eclipse.debug.core.model.IDebugTarget;
import org.eclipse.debug.core.model.IProcess;
import org.eclipse.debug.internal.ui.SWTFactory;
import org.eclipse.jdi.internal.VirtualMachineImpl;
import org.eclipse.jdt.debug.core.IJavaDebugTarget;
import org.eclipse.jdt.internal.debug.core.model.JDIDebugTarget;
import org.eclipse.jdt.internal.debug.ui.IJavaDebugHelpContextIds;
import org.eclipse.jdt.internal.debug.ui.JDIDebugUIPlugin;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.jface.resource.JFaceResources;
import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Text;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.dialogs.PropertyPage;
import org.eclipse.ui.forms.events.ExpansionAdapter;
import org.eclipse.ui.forms.events.ExpansionEvent;
import org.eclipse.ui.forms.widgets.ExpandableComposite;
import org.eclipse.ui.forms.widgets.SharedScrolledComposite;

/**
 * This class provides a properties page displaying all of the capabilities
 * of the VM associated with the selected <code>IDebugTarget</code> or <code>IProcess</code>
 *
 * @since 3.3
 */
public class VMCapabilitiesPropertyPage extends PropertyPage {

	/**
	 * Provides a scrollable area for the expansion composites
	 */
	class ScrollPain extends SharedScrolledComposite {
		public ScrollPain(Composite parent) {
			super(parent, SWT.V_SCROLL | SWT.H_SCROLL);
			setExpandHorizontal(true);
			setExpandVertical(true);
			GridLayout layout = new GridLayout(1, false);
			layout.marginHeight = 0;
			layout.marginWidth = 0;
			setLayout(layout);
		}
	}

	private List<ExpandableComposite> fExpandedComps;
	private static final String EXPANDED_STATE = "vmc_expanded_state"; //$NON-NLS-1$
	private static Font fHeadingFont = JFaceResources.getFontRegistry().getBold(JFaceResources.DIALOG_FONT);

	/**
	 * Constructor
	 */
	public VMCapabilitiesPropertyPage() {
		fExpandedComps = new ArrayList<>();
	}

	/* (non-Javadoc)
	 * @see org.eclipse.jface.preference.PreferencePage#createContents(org.eclipse.swt.widgets.Composite)
	 */
	@Override
	protected Control createContents(Composite parent) {
		noDefaultAndApplyButton();
		final ScrollPain scomp = new ScrollPain(parent);
		GridData gd = new GridData(GridData.FILL_BOTH);
		scomp.setLayout(new GridLayout());
		scomp.setLayoutData(gd);
		final Composite comp = new Composite(scomp, SWT.NONE);
		comp.setLayout(new GridLayout(2, true));
		gd = new GridData(GridData.FILL_BOTH);
		comp.setLayoutData(gd);
		scomp.setContent(comp);
		VirtualMachineImpl vm = getVM();
		if(vm == null) {
			setErrorMessage(PropertyPageMessages.VMCapabilitiesPropertyPage_0);
		}
		else {
			createExplanation(comp);
			SWTFactory.createVerticalSpacer(comp, 1);
			createHeadingLabel(comp, vm);
			SWTFactory.createVerticalSpacer(comp, 1);

			// breakpoints
			ExpandableComposite breakpoints = createExpandibleComposite(comp, ExpandableComposite.TWISTIE | ExpandableComposite.CLIENT_INDENT, PropertyPageMessages.VMCapabilitiesPropertyPage_27, 2, GridData.FILL_HORIZONTAL);
			fExpandedComps.add(breakpoints);
			Composite bp_inner = SWTFactory.createComposite(breakpoints, comp.getFont(), 2, 2, GridData.FILL_HORIZONTAL);
			breakpoints.setClient(bp_inner);
			createCapabilityEntry(bp_inner, PropertyPageMessages.VMCapabilitiesPropertyPage_4, vm.canUseInstanceFilters());
			createCapabilityEntry(bp_inner, PropertyPageMessages.VMCapabilitiesPropertyPage_9, vm.canWatchFieldModification());
			createCapabilityEntry(bp_inner, PropertyPageMessages.VMCapabilitiesPropertyPage_10, vm.canWatchFieldAccess());
			createCapabilityEntry(bp_inner, PropertyPageMessages.VMCapabilitiesPropertyPage_24, vm.canGetMethodReturnValues());
			createCapabilityEntry(bp_inner, PropertyPageMessages.VMCapabilitiesPropertyPage_25, vm.canRequestMonitorEvents());

			// hot code replace
			ExpandableComposite hcr = createExpandibleComposite(comp, ExpandableComposite.TWISTIE | ExpandableComposite.CLIENT_INDENT, PropertyPageMessages.VMCapabilitiesPropertyPage_28, 2, GridData.FILL_HORIZONTAL);
			fExpandedComps.add(hcr);
			Composite hcr_inner = SWTFactory.createComposite(hcr, comp.getFont(), 2, 2, GridData.FILL_HORIZONTAL);
			hcr.setClient(hcr_inner);
			createCapabilityEntry(hcr_inner, PropertyPageMessages.VMCapabilitiesPropertyPage_15, vm.canRedefineClasses());
			createCapabilityEntry(hcr_inner, PropertyPageMessages.VMCapabilitiesPropertyPage_12, vm.canAddMethod());
			createCapabilityEntry(hcr_inner, PropertyPageMessages.VMCapabilitiesPropertyPage_16, vm.canUnrestrictedlyRedefineClasses());

			// stepping
			ExpandableComposite stepping = createExpandibleComposite(comp, ExpandableComposite.TWISTIE | ExpandableComposite.CLIENT_INDENT, PropertyPageMessages.VMCapabilitiesPropertyPage_29, 2, GridData.FILL_HORIZONTAL);
			fExpandedComps.add(stepping);
			Composite stepping_inner = SWTFactory.createComposite(stepping, comp.getFont(), 2, 2, GridData.FILL_HORIZONTAL);
			stepping.setClient(stepping_inner);
			createCapabilityEntry(stepping_inner, PropertyPageMessages.VMCapabilitiesPropertyPage_14, vm.canPopFrames());
			createCapabilityEntry(stepping_inner, PropertyPageMessages.VMCapabilitiesPropertyPage_3, vm.canGetSyntheticAttribute());
			createCapabilityEntry(stepping_inner, PropertyPageMessages.VMCapabilitiesPropertyPage_21, vm.canForceEarlyReturn());

			// others
			ExpandableComposite general = createExpandibleComposite(comp, ExpandableComposite.TWISTIE | ExpandableComposite.CLIENT_INDENT, PropertyPageMessages.VMCapabilitiesPropertyPage_30, 2, GridData.FILL_HORIZONTAL);
			fExpandedComps.add(general);
			Composite general_inner = SWTFactory.createComposite(general, comp.getFont(), 2, 2, GridData.FILL_HORIZONTAL);
			general.setClient(general_inner);
			createCapabilityEntry(general_inner, PropertyPageMessages.VMCapabilitiesPropertyPage_6, vm.canGetCurrentContendedMonitor() && vm.canGetOwnedMonitorInfo());
			createCapabilityEntry(general_inner, PropertyPageMessages.VMCapabilitiesPropertyPage_18, vm.canSetDefaultStratum());
			createCapabilityEntry(general_inner, PropertyPageMessages.VMCapabilitiesPropertyPage_26, vm.canGetInstanceInfo());

			restoreExpansionState();
		}
        applyDialogFont(comp);
		return comp;
	}

	private void createExplanation(Composite parent) {
		Composite comp = SWTFactory.createComposite(parent, parent.getFont(), 1, 2, GridData.FILL_HORIZONTAL);
		Label label = new Label(comp, SWT.WRAP);
        label.setFont(parent.getFont());
        label.setText(PropertyPageMessages.VMCapabilitiesPropertyPage_31);
		label.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
	}

	private void createHeadingLabel(Composite parent, VirtualMachineImpl vm) {
		Composite comp = SWTFactory.createComposite(parent, parent.getFont(), 2, 2, GridData.HORIZONTAL_ALIGN_BEGINNING);
		SWTFactory.createLabel(comp, PropertyPageMessages.VMCapabilitiesPropertyPage_1, fHeadingFont, 1);
		StringBuilder buff = new StringBuilder(vm.name().trim());
		buff = buff.append(" ").append(vm.version().trim()); //$NON-NLS-1$
		Text text = SWTFactory.createText(comp, SWT.READ_ONLY, 1, buff.toString());
		text.setBackground(parent.getBackground());
	}

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

	/**
	 * Returns the VM from the debug target
	 * @return the VM form the element
	 */
	private VirtualMachineImpl getVM() {
		Object obj = getElement();
		IDebugTarget target = null;
		if(obj instanceof IDebugElement) {
			target = ((IDebugElement) obj).getAdapter(IDebugTarget.class);
		}
		else if(obj instanceof IProcess) {
			target = ((IProcess) obj).getAdapter(IDebugTarget.class);
		}
		if(target != null) {
			if(!target.isTerminated() && !target.isDisconnected()) {
				IJavaDebugTarget dtarget = target.getAdapter(IJavaDebugTarget.class);
				if(dtarget instanceof JDIDebugTarget) {
					return (VirtualMachineImpl) ((JDIDebugTarget)target).getVM();
				}
			}
		}
		return null;
	}

	/**
	 * Returns a new capability entry for a specified group
	 * @param parent the parent group to add this entry to
	 * @param label the text for the label
	 * @param enabled the checked state of the check button
	 */
	private void createCapabilityEntry(Composite parent, String label, boolean enabled) {
		SWTFactory.createCheckButton(parent, null, null, enabled, 1).setEnabled(false);
		SWTFactory.createLabel(parent, label, parent.getFont(), 1);
	}

	/**
	 * Creates an ExpandibleComposite widget
	 * @param parent the parent to add this widget to
	 * @param style the style for ExpandibleComposite expanding handle, and layout
	 * @param label the label for the widget
	 * @param hspan how many columns to span in the parent
	 * @param fill the fill style for the widget
	 * @return a new ExpandibleComposite widget
	 */
	private ExpandableComposite createExpandibleComposite(Composite parent, int style, String label, int hspan, int fill) {
		ExpandableComposite ex = SWTFactory.createExpandibleComposite(parent, style, label, hspan, fill);
		ex.addExpansionListener(new ExpansionAdapter() {
			@Override
			public void expansionStateChanged(ExpansionEvent e) {
				ScrollPain sp = getParentScrollPane((ExpandableComposite) e.getSource());
				if(sp != null) {
					sp.reflow(true);
				}
			}
		});
		return ex;
	}

	/* (non-Javadoc)
	 * @see org.eclipse.jface.preference.PreferencePage#doGetPreferenceStore()
	 */
	@Override
	protected IPreferenceStore doGetPreferenceStore() {
		return JDIDebugUIPlugin.getDefault().getPreferenceStore();
	}

	/**
	 * save the expansion state for next time, this only happens when the user selects the OK button when closing the dialog
	 */
	private void persistExpansionState() {
		IPreferenceStore store = getPreferenceStore();
		if(store != null) {
			for (int i = 0; i < fExpandedComps.size(); i++) {
				store.setValue(EXPANDED_STATE+i, fExpandedComps.get(i).isExpanded());
			}
		}
	}

	/**
	 * restore the expansion state
	 */
	private void restoreExpansionState() {
		IPreferenceStore store = getPreferenceStore();
		if(store == null) {
			fExpandedComps.get(0).setExpanded(true);
		}
		else {
			ExpandableComposite ex;
			for (int i = 0; i < fExpandedComps.size(); i++) {
				ex = fExpandedComps.get(i);
				ex.setExpanded(store.getBoolean(EXPANDED_STATE+i));
			}
		}
	}

	/* (non-Javadoc)
	 * @see org.eclipse.jface.preference.PreferencePage#performOk()
	 */
	@Override
	public boolean performOk() {
		boolean ok = super.performOk();
		persistExpansionState();
		return ok;
	}

	/**
	 * Finds the parent ScrollPain that needs to be notified that it should reFlow to show the new elements
	 * @param comp the initial comp
	 * @return the parent or null, in this case though, we will never return null
	 */
	private ScrollPain getParentScrollPane(Composite comp) {
		Control parent = comp.getParent();
		while(parent != null && !(parent instanceof ScrollPain)) {
			parent = parent.getParent();
		}
		if(parent != null) {
			return (ScrollPain)parent;
		}
		return null;
	}
}
