/*******************************************************************************
 * Copyright (c) 2008, 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
 *     Winnie Lai (Texas Instruments) - Individual Element Number Format (Bug 202556)
 *******************************************************************************/
package org.eclipse.cdt.tests.dsf.vm;

import java.util.Iterator;
import java.util.Set;

import org.eclipse.cdt.dsf.concurrent.CountingRequestMonitor;
import org.eclipse.cdt.dsf.concurrent.DataRequestMonitor;
import org.eclipse.cdt.dsf.concurrent.RequestMonitor;
import org.eclipse.cdt.dsf.debug.ui.viewmodel.IDebugVMConstants;
import org.eclipse.cdt.dsf.debug.ui.viewmodel.numberformat.FormattedValueLabelText;
import org.eclipse.cdt.dsf.debug.ui.viewmodel.numberformat.FormattedValueRetriever;
import org.eclipse.cdt.dsf.debug.ui.viewmodel.update.ElementFormatEvent;
import org.eclipse.cdt.dsf.service.DsfSession;
import org.eclipse.cdt.dsf.ui.viewmodel.IRootVMNode;
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.LabelText;
import org.eclipse.cdt.dsf.ui.viewmodel.properties.PropertiesBasedLabelProvider;
import org.eclipse.cdt.tests.dsf.ViewerUpdatesListener;
import org.eclipse.cdt.tests.dsf.vm.TestModel.TestElement;
import org.eclipse.cdt.tests.dsf.vm.TestModel.TestEvent;
import org.eclipse.debug.internal.ui.viewers.model.provisional.IChildrenCountUpdate;
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.IHasChildrenUpdate;
import org.eclipse.debug.internal.ui.viewers.model.provisional.ILabelUpdate;
import org.eclipse.debug.internal.ui.viewers.model.provisional.IModelDelta;
import org.eclipse.jface.util.PropertyChangeEvent;

/**
 * @since 2.2
 */
public class TestModelDMVMNode extends AbstractDMVMNode
		implements IRootVMNode, IElementLabelProvider, IElementPropertiesProvider {

	final public static String PROP_PREFIX_DUMMY = "dummy.";

	final private static String PROP_TEST_ELEMENT_LABEL = "PROP_TEST_ELEMENT_LABEL";

	private ViewerUpdatesListener fViewerUpdateListener = NULL_VIEWER_UPDATE_LISTENER;
	private FormattedValuesListener fFormattedValuesListener;

	private static final ViewerUpdatesListener NULL_VIEWER_UPDATE_LISTENER = new ViewerUpdatesListener();

	final private static PropertiesBasedLabelProvider fLabelProvider = new PropertiesBasedLabelProvider();
	{
		LabelColumnInfo idLabelInfo = new LabelColumnInfo(
				new LabelAttribute[] { new LabelText("{0}", new String[] { PROP_TEST_ELEMENT_LABEL }) });

		fLabelProvider.setColumnInfo(PropertiesBasedLabelProvider.ID_COLUMN_NO_COLUMNS, idLabelInfo);
		fLabelProvider.setColumnInfo(TestModelCachingVMProvider.COLUMN_ID, idLabelInfo);
		fLabelProvider.setColumnInfo(TestModelCachingVMProvider.COLUMN_FORMATTED_VALUE,
				new LabelColumnInfo(new LabelAttribute[] { new FormattedValueLabelText() }));
		fLabelProvider.setColumnInfo(TestModelCachingVMProvider.COLUMN_DUMMY_VALUE,
				new LabelColumnInfo(new LabelAttribute[] { new FormattedValueLabelText(PROP_PREFIX_DUMMY) }));
	}

	private final FormattedValueRetriever fFormattedValueRetriever;
	private final FormattedValueRetriever fDummyFormattedValueRetriever;

	public TestModelDMVMNode(AbstractDMVMProvider provider, DsfSession session) {
		super(provider, session, TestElement.class);
		fFormattedValueRetriever = new FormattedValueRetriever(this, getSession(), TestModel.class, TestElement.class);
		fDummyFormattedValueRetriever = new FormattedValueRetriever(this, getSession(),
				DummyFormattedValueService.class, TestElement.class, PROP_PREFIX_DUMMY);
	}

	@Override
	public void dispose() {
		super.dispose();
		fFormattedValueRetriever.dispose();
		fDummyFormattedValueRetriever.dispose();
	}

	public void setVMUpdateListener(ViewerUpdatesListener viewerUpdateListener) {
		if (viewerUpdateListener != null) {
			fViewerUpdateListener = viewerUpdateListener;
		} else {
			fViewerUpdateListener = NULL_VIEWER_UPDATE_LISTENER;
		}
	}

	public void setFormattedValuesListener(FormattedValuesListener formattedValuesListener) {
		fFormattedValuesListener = formattedValuesListener;
	}

	public PropertiesBasedLabelProvider getLabelProvider() {
		return fLabelProvider;
	}

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

	@Override
	public void update(IHasChildrenUpdate[] updates) {
		fViewerUpdateListener.viewerUpdatesBegin();
		for (IHasChildrenUpdate update : updates) {
			fViewerUpdateListener.updateStarted(update);
			if (update.getElement() instanceof TestElementVMContext) {
				TestElement element = ((TestElementVMContext) update.getElement()).getElement();
				update.setHasChilren(element.getChildren().length != 0);
			}
			update.done();
			fViewerUpdateListener.updateComplete(update);
		}
		fViewerUpdateListener.viewerUpdatesComplete();
	}

	@Override
	public void update(IChildrenCountUpdate[] updates) {
		fViewerUpdateListener.viewerUpdatesBegin();
		for (IChildrenCountUpdate update : updates) {
			fViewerUpdateListener.updateStarted(update);
			if (update.getElement() instanceof TestElementVMContext) {
				TestElement element = ((TestElementVMContext) update.getElement()).getElement();
				update.setChildCount(element.getChildren().length);
			}
			update.done();
			fViewerUpdateListener.updateComplete(update);
		}
		fViewerUpdateListener.viewerUpdatesComplete();
	}

	@Override
	public void update(IChildrenUpdate[] updates) {
		fViewerUpdateListener.viewerUpdatesBegin();
		for (IChildrenUpdate update : updates) {
			fViewerUpdateListener.updateStarted(update);
			if (update.getElement() instanceof TestElementVMContext) {
				TestElement element = ((TestElementVMContext) update.getElement()).getElement();
				fillUpdateWithTestElements(update, element.getChildren());
			}
			update.done();
			fViewerUpdateListener.updateComplete(update);
		}
		fViewerUpdateListener.viewerUpdatesComplete();
	}

	@Override
	protected void updateElementsInSessionThread(IChildrenUpdate update) {
		// TODO Auto-generated method stub

	}

	@Override
	public void update(final IPropertiesUpdate[] updates) {
		fViewerUpdateListener.propertiesUpdatesStarted(updates);
		if (fFormattedValuesListener != null)
			fFormattedValuesListener.propertiesUpdatesStarted(updates);

		CountingRequestMonitor crm = new CountingRequestMonitor(getExecutor(), null) {
			@Override
			protected void handleSuccess() {
				for (IPropertiesUpdate update : updates) {
					if (update.getElement() instanceof TestElementVMContext) {
						TestElement element = ((TestElementVMContext) update.getElement()).getElement();
						update.setProperty(PROP_TEST_ELEMENT_LABEL, element.getLabel());
					}
					update.done();
					fViewerUpdateListener.propertiesUpdateCompleted(update);
					if (fFormattedValuesListener != null)
						fFormattedValuesListener.propertiesUpdateCompleted(update);
				}
			}
		};
		int count = 0;

		fFormattedValueRetriever.update(updates, crm);
		count++;
		fDummyFormattedValueRetriever.update(updates, crm);
		count++;
		crm.setDoneCount(count);
	}

	private void fillUpdateWithTestElements(IChildrenUpdate update, TestElement[] modelElements) {
		int updateIdx = update.getOffset() != -1 ? update.getOffset() : 0;
		int endIdx = updateIdx + (update.getLength() != -1 ? update.getLength() : modelElements.length);
		while (updateIdx < endIdx && updateIdx < modelElements.length) {
			update.setChild(createVMContext(modelElements[updateIdx]), updateIdx);
			updateIdx++;
		}
	}

	public TestElementVMContext createVMContext(TestElement element) {
		return new TestElementVMContext(this, element);
	}

	@Override
	public int getDeltaFlags(Object e) {
		if (e instanceof PropertyChangeEvent && ((PropertyChangeEvent) e)
				.getProperty() == IDebugVMConstants.PROP_FORMATTED_VALUE_FORMAT_PREFERENCE) {
			return IModelDelta.CONTENT;
		}
		if (e instanceof ElementFormatEvent) {
			return IModelDelta.STATE;
		}
		if (e instanceof TestEvent) {
			return ((TestEvent) e).getType();
		}

		return IModelDelta.NO_CHANGE;
	}

	@Override
	public void buildDelta(Object e, VMDelta parent, int nodeOffset, RequestMonitor rm) {
		if (e instanceof PropertyChangeEvent && ((PropertyChangeEvent) e)
				.getProperty() == IDebugVMConstants.PROP_FORMATTED_VALUE_FORMAT_PREFERENCE) {
			parent.setFlags(parent.getFlags() | IModelDelta.CONTENT);
		}
		if (e instanceof ElementFormatEvent) {
			Set<Object> elements = ((ElementFormatEvent) e).getElements();
			Iterator<Object> it = elements.iterator();
			while (it.hasNext()) {
				parent.addNode(it.next(), IModelDelta.STATE);
			}
		}
		rm.done();
	}

	@Override
	public boolean isDeltaEvent(Object rootObject, Object event) {
		return getDeltaFlags(event) != IModelDelta.NO_CHANGE;
	}

	@Override
	public void createRootDelta(Object rootObject, Object event, DataRequestMonitor<VMDelta> rm) {
		int flags = IModelDelta.NO_CHANGE;
		if (event instanceof PropertyChangeEvent && ((PropertyChangeEvent) event)
				.getProperty() == IDebugVMConstants.PROP_FORMATTED_VALUE_FORMAT_PREFERENCE) {
			flags |= IModelDelta.CONTENT;
		}
		if (event instanceof ElementFormatEvent) {
			flags |= IModelDelta.CONTENT;
		}

		// TODO: make more sophisticated to update specific elements.
		if (event instanceof TestEvent) {
			flags |= ((TestEvent) event).getType();
		}

		rm.setData(new VMDelta(rootObject, 0, flags));
		rm.done();
	}

}
