/*******************************************************************************
 * Copyright (c) 2007, 2014 Wind River 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:
 *     Wind River Systems - initial API and implementation
 *******************************************************************************/
package org.eclipse.cdt.dsf.debug.ui.viewmodel.variable;

import java.util.concurrent.RejectedExecutionException;

import org.eclipse.cdt.dsf.concurrent.DsfRunnable;
import org.eclipse.cdt.dsf.debug.internal.ui.viewmodel.DsfCastToTypeSupport;
import org.eclipse.cdt.dsf.debug.service.ICachingService;
import org.eclipse.cdt.dsf.debug.service.IExpressions;
import org.eclipse.cdt.dsf.debug.service.IExpressions2;
import org.eclipse.cdt.dsf.debug.service.IRunControl.ISuspendedDMEvent;
import org.eclipse.cdt.dsf.debug.ui.DsfDebugUITools;
import org.eclipse.cdt.dsf.debug.ui.IDsfDebugUIConstants;
import org.eclipse.cdt.dsf.debug.ui.viewmodel.numberformat.AbstractElementVMProvider;
import org.eclipse.cdt.dsf.debug.ui.viewmodel.update.BreakpointHitUpdatePolicy;
import org.eclipse.cdt.dsf.debug.ui.viewmodel.update.DebugManualUpdatePolicy;
import org.eclipse.cdt.dsf.internal.ui.DsfUIPlugin;
import org.eclipse.cdt.dsf.service.DsfServicesTracker;
import org.eclipse.cdt.dsf.service.DsfSession;
import org.eclipse.cdt.dsf.ui.viewmodel.AbstractVMAdapter;
import org.eclipse.cdt.dsf.ui.viewmodel.IRootVMNode;
import org.eclipse.cdt.dsf.ui.viewmodel.IVMNode;
import org.eclipse.cdt.dsf.ui.viewmodel.datamodel.RootDMVMNode;
import org.eclipse.cdt.dsf.ui.viewmodel.update.AutomaticUpdatePolicy;
import org.eclipse.cdt.dsf.ui.viewmodel.update.IVMUpdatePolicy;
import org.eclipse.debug.internal.ui.viewers.model.provisional.IColumnPresentation;
import org.eclipse.debug.internal.ui.viewers.model.provisional.IPresentationContext;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.jface.util.IPropertyChangeListener;
import org.eclipse.jface.util.PropertyChangeEvent;

public class VariableVMProvider extends AbstractElementVMProvider {
	private IPropertyChangeListener fPreferencesListener = new IPropertyChangeListener() {
		@Override
		public void propertyChange(PropertyChangeEvent event) {
			String property = event.getProperty();
			if (property.equals(IDsfDebugUIConstants.PREF_WAIT_FOR_VIEW_UPDATE_AFTER_STEP_ENABLE)) {
				IPreferenceStore store = DsfDebugUITools.getPreferenceStore();
				setDelayEventHandleForViewUpdate(store.getBoolean(property));
			}
		}
	};

	private IPropertyChangeListener fPresentationContextListener = new IPropertyChangeListener() {
		@Override
		public void propertyChange(final PropertyChangeEvent event) {
			getExecutor().execute(new DsfRunnable() {
				@Override
				public void run() {
					handleEvent(event);
				};
			});
		}
	};

	public VariableVMProvider(AbstractVMAdapter adapter, IPresentationContext context, DsfSession session) {
		super(adapter, context, session);

		context.addPropertyChangeListener(fPresentationContextListener);

		IPreferenceStore store = DsfDebugUITools.getPreferenceStore();
		store.addPropertyChangeListener(fPreferencesListener);
		setDelayEventHandleForViewUpdate(
				store.getBoolean(IDsfDebugUIConstants.PREF_WAIT_FOR_VIEW_UPDATE_AFTER_STEP_ENABLE));

		configureLayout();
	}

	@Override
	public void dispose() {
		DsfDebugUITools.getPreferenceStore().removePropertyChangeListener(fPreferencesListener);
		getPresentationContext().removePropertyChangeListener(fPresentationContextListener);
		super.dispose();
	}

	/**
	 * Configures the nodes of this provider.  This method may be over-ridden by
	 * sub classes to create an alternate configuration in this provider.
	 *
	 * @since 2.1
	 */
	protected void configureLayout() {

		// Create the variable data access routines.
		SyncVariableDataAccess varAccess = new SyncVariableDataAccess(getSession());

		// Create the top level node to deal with the root selection.
		IRootVMNode rootNode = new RootDMVMNode(this);
		setRootNode(rootNode);

		// Create the next level which represents members of structs/unions/enums and elements of arrays.
		VariableVMNode subExpressioNode = new VariableVMNode(this, getSession(), varAccess);
		addChildNodes(rootNode, new IVMNode[] { subExpressioNode });

		// Wire up the casting support if the IExpressions2 service is available.
		hookUpCastingSupport(varAccess, subExpressioNode);

		// Configure the sub-expression node to be a child of itself.  This way the content
		// provider will recursively drill-down the variable hierarchy.
		addChildNodes(subExpressioNode, new IVMNode[] { subExpressioNode });
	}

	private void hookUpCastingSupport(final SyncVariableDataAccess syncvarDataAccess,
			final VariableVMNode variableNode) {
		try {
			getSession().getExecutor().execute(new DsfRunnable() {
				@Override
				public void run() {
					DsfServicesTracker tracker = new DsfServicesTracker(DsfUIPlugin.getBundleContext(),
							getSession().getId());
					IExpressions2 expressions2 = tracker.getService(IExpressions2.class);
					if (expressions2 != null) {
						variableNode.setCastToTypeSupport(
								new DsfCastToTypeSupport(getSession(), VariableVMProvider.this, syncvarDataAccess));
					}
					tracker.dispose();
				}
			});
		} catch (RejectedExecutionException e) {
			// Session disposed, ignore.
		}
	}

	@Override
	public IColumnPresentation createColumnPresentation(IPresentationContext context, Object element) {
		return new VariableColumnPresentation();
	}

	@Override
	public String getColumnPresentationId(IPresentationContext context, Object element) {
		return VariableColumnPresentation.ID;
	}

	@Override
	protected IVMUpdatePolicy[] createUpdateModes() {
		return new IVMUpdatePolicy[] { new AutomaticUpdatePolicy(), new DebugManualUpdatePolicy(),
				new BreakpointHitUpdatePolicy() };
	}

	@Override
	protected boolean canSkipHandlingEvent(Object newEvent, Object eventToSkip) {
		// To optimize the performance of the view when stepping rapidly, skip all
		// other events when a suspended event is received, including older suspended
		// events.
		return newEvent instanceof ISuspendedDMEvent;
	}

	@Override
	public void refresh() {
		super.refresh();
		try {
			getSession().getExecutor().execute(new DsfRunnable() {
				@Override
				public void run() {
					DsfServicesTracker tracker = new DsfServicesTracker(DsfUIPlugin.getBundleContext(),
							getSession().getId());
					IExpressions expressionsService = tracker.getService(IExpressions.class);
					if (expressionsService instanceof ICachingService) {
						((ICachingService) expressionsService).flushCache(null);
					}
					tracker.dispose();
				}
			});
		} catch (RejectedExecutionException e) {
			// Session disposed, ignore.
		}
	}
}
