/*******************************************************************************
 * Copyright (c) 2006, 2015 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.examples.dsf.timers;

import java.util.concurrent.RejectedExecutionException;

import org.eclipse.cdt.dsf.concurrent.ConfinedToDsfExecutor;
import org.eclipse.cdt.dsf.concurrent.DsfRunnable;
import org.eclipse.cdt.dsf.concurrent.RequestMonitor;
import org.eclipse.cdt.dsf.service.DsfSession;
import org.eclipse.cdt.dsf.ui.viewmodel.VMDelta;
import org.eclipse.cdt.dsf.ui.viewmodel.datamodel.AbstractDMVMNode;
import org.eclipse.cdt.dsf.ui.viewmodel.datamodel.AbstractDMVMProvider;
import org.eclipse.cdt.dsf.ui.viewmodel.properties.IElementPropertiesProvider;
import org.eclipse.cdt.dsf.ui.viewmodel.properties.IPropertiesUpdate;
import org.eclipse.cdt.dsf.ui.viewmodel.properties.LabelAttribute;
import org.eclipse.cdt.dsf.ui.viewmodel.properties.LabelColumnInfo;
import org.eclipse.cdt.dsf.ui.viewmodel.properties.LabelForeground;
import org.eclipse.cdt.dsf.ui.viewmodel.properties.LabelImage;
import org.eclipse.cdt.dsf.ui.viewmodel.properties.LabelText;
import org.eclipse.cdt.dsf.ui.viewmodel.properties.PropertiesBasedLabelProvider;
import org.eclipse.cdt.examples.dsf.DsfExamplesPlugin;
import org.eclipse.cdt.examples.dsf.timers.TimerService.TimerDMContext;
import org.eclipse.debug.internal.ui.viewers.model.provisional.IChildrenUpdate;
import org.eclipse.debug.internal.ui.viewers.model.provisional.IElementLabelProvider;
import org.eclipse.debug.internal.ui.viewers.model.provisional.ILabelUpdate;
import org.eclipse.debug.internal.ui.viewers.model.provisional.IModelDelta;
import org.eclipse.debug.internal.ui.viewers.model.provisional.IViewerUpdate;
import org.eclipse.swt.graphics.RGB;

/**
 * View model node that defines how timer DMContexts are displayed in the view. Timers
 * change with every tick of the timer, so the label has to be repained
 * upon timer tick events.
 * @see TimerDMContext
 */
@SuppressWarnings("restriction")
class TimersVMNode extends AbstractDMVMNode implements IElementLabelProvider, IElementPropertiesProvider {
	private static final String PROP_TIMER_NUMBER = "alarmNumber";
	private static final String PROP_TIMER_VALUE = "alarmTriggerValue";

	// Create and configure the label provider.
	private static final PropertiesBasedLabelProvider fgLabelProvider;
	static {
		fgLabelProvider = new PropertiesBasedLabelProvider();

		LabelColumnInfo idCol = new LabelColumnInfo(
				new LabelAttribute[] { new LabelText("Timer #{0}", new String[] { PROP_TIMER_NUMBER }),
						new LabelForeground(new RGB(0, 0, 255)), new LabelImage(DsfExamplesPlugin.getDefault()
								.getImageRegistry().getDescriptor(DsfExamplesPlugin.IMG_TIMER)) });
		fgLabelProvider.setColumnInfo(TimersViewColumnPresentation.COL_ID, idCol);

		LabelColumnInfo valueCol = new LabelColumnInfo(
				new LabelAttribute[] { new LabelText("{0}", new String[] { PROP_TIMER_VALUE }) });
		fgLabelProvider.setColumnInfo(TimersViewColumnPresentation.COL_VALUE, valueCol);

	}

	public TimersVMNode(AbstractDMVMProvider provider, DsfSession session) {
		super(provider, session, TimerDMContext.class);
	}

	@Override
	public String toString() {
		return "TimersVMNode(" + getSession().getId() + ")"; //$NON-NLS-1$ //$NON-NLS-2$
	}

	@Override
	public void update(ILabelUpdate[] updates) {
		fgLabelProvider.update(updates);
	}

	@Override
	protected void updateElementsInSessionThread(final IChildrenUpdate update) {
		TimerService timerService = getServicesTracker().getService(TimerService.class, null);
		if (timerService == null) {
			handleFailedUpdate(update);
			return;
		}

		// Retrieve the timer DMContexts, create the corresponding VMCs array, and
		// set them as result.
		TimerDMContext[] timers = timerService.getTimers();
		fillUpdateWithVMCs(update, timers);
		update.done();
	}

	@Override
	public void update(final IPropertiesUpdate[] updates) {
		// Switch to the session thread before processing the updates.
		try {
			getSession().getExecutor().execute(new DsfRunnable() {
				@Override
				public void run() {
					for (IPropertiesUpdate update : updates) {
						updatePropertiesInSessionThread(update);
					}
				}
			});
		} catch (RejectedExecutionException e) {
			for (IViewerUpdate update : updates) {
				handleFailedUpdate(update);
			}
		}
	}

	@ConfinedToDsfExecutor("getSession#getExecutor")
	private void updatePropertiesInSessionThread(final IPropertiesUpdate update) {
		// Find the timer context in the element being updated
		TimerDMContext dmc = findDmcInPath(update.getViewerInput(), update.getElementPath(), TimerDMContext.class);
		TimerService timerService = getServicesTracker().getService(TimerService.class, null);

		// If either update or service are not valid, fail the update and exit.
		if (dmc == null || timerService == null) {
			handleFailedUpdate(update);
			return;
		}

		int value = timerService.getTimerValue(dmc);

		if (value == -1) {
			handleFailedUpdate(update);
			return;
		}

		update.setProperty(PROP_TIMER_NUMBER, dmc.getTimerNumber());
		update.setProperty(PROP_TIMER_VALUE, value);
		update.done();
	}

	@Override
	public int getDeltaFlags(Object e) {
		// This node generates delta if the timers have changed, or if the
		// label has changed.
		if (e instanceof TimerService.TimerTickDMEvent) {
			return IModelDelta.STATE;
		} else if (e instanceof TimerService.TimersChangedEvent) {
			return IModelDelta.CONTENT;
		}
		return IModelDelta.NO_CHANGE;
	}

	@Override
	public void buildDelta(Object e, VMDelta parentDelta, int nodeOffset, RequestMonitor requestMonitor) {
		if (e instanceof TimerService.TimerTickDMEvent) {
			// Add delta indicating that the given timer has changed.
			parentDelta.addNode(createVMContext(((TimerService.TimerTickDMEvent) e).getDMContext()), IModelDelta.STATE);
		} else if (e instanceof TimerService.TimersChangedEvent) {
			// The list of timers has changed, which means that the parent
			// node needs to refresh its contents, which in turn will re-fetch the
			// elements from this node.
			parentDelta.setFlags(parentDelta.getFlags() | IModelDelta.CONTENT);
		}
		requestMonitor.done();
	}
}
