/*******************************************************************************
 *  Copyright (c) 2009, 2016 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.ui.viewmodel.properties;

import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

import org.eclipse.cdt.dsf.concurrent.DataRequestMonitor;
import org.eclipse.cdt.dsf.debug.internal.ui.viewmodel.VMViewerUpdateTracing;
import org.eclipse.cdt.dsf.internal.DsfPlugin;
import org.eclipse.cdt.dsf.internal.LoggingUtils;
import org.eclipse.cdt.dsf.internal.ui.DsfUIPlugin;
import org.eclipse.cdt.dsf.ui.viewmodel.VMViewerUpdate;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.debug.internal.ui.viewers.model.provisional.IPresentationContext;
import org.eclipse.debug.internal.ui.viewers.model.provisional.IViewerUpdate;
import org.eclipse.jface.viewers.TreePath;

/**
 * Properties update used as to collect property data from the provider.
 * <p>
 * The status returned by the VMPropertiesUpdate is always going to be of type
 * PropertiesUpdateStatus, which allows for setting status for individual
 * properties.
 * </p>
 *
 * @see PropertiesUpdateStatus
 *
 * @since 2.0
 */
public class VMPropertiesUpdate extends VMViewerUpdate implements IPropertiesUpdate {

	/**
	 * Properties that the client has requested to retrieve.
	 */
	private final Set<String> fProperties;

	/**
	 * Flag indicating that the update has created a new map, as opposed to
	 * using directly a map that was created using setAllProperties() call.
	 */
	private boolean fCreatedOwnMap = false;

	/**
	 * Map of property values, created on demand.
	 */
	private Map<String, Object> fValues = Collections.emptyMap();

	public VMPropertiesUpdate(Set<String> properties, IViewerUpdate parentUpdate,
			DataRequestMonitor<Map<String, Object>> rm) {
		super(parentUpdate, rm);
		super.setStatus(new PropertiesUpdateStatus());
		fProperties = properties;
	}

	public VMPropertiesUpdate(Set<String> properties, TreePath elementPath, Object viewerInput,
			IPresentationContext presentationContext, DataRequestMonitor<Map<String, Object>> rm) {
		super(elementPath, viewerInput, presentationContext, rm);
		super.setStatus(new PropertiesUpdateStatus());
		fProperties = properties;
	}

	@Override
	public Set<String> getProperties() {
		return fProperties;
	}

	/**
	 * @since 2.2
	 */
	public Map<String, Object> getValues() {
		return fValues;
	}

	@Override
	public synchronized void setProperty(String property, Object value) {
		if (!fCreatedOwnMap) {
			fCreatedOwnMap = true;
			Map<String, Object> curValues = fValues;
			fValues = new HashMap<>(fProperties.size() * 4 / 3, 0.75f);
			if (curValues != null) {
				fValues.putAll(curValues);
			}
		}
		fValues.put(property, value);
	}

	@Override
	public synchronized void setAllProperties(Map<String, Object> properties) {
		if (fCreatedOwnMap) {
			fValues.putAll(properties);
		} else {
			fValues = properties;
		}
	}

	/**
	 * Overrides the base class to implement special handling of
	 * {@link PropertiesUpdateStatus}.  If the given status is an instance of
	 * properties status, this new status will be set to the update.  Otherwise, the
	 * given status will be merged into the updates existing properties status.
	 * This way {@link #getStatus()} should always return an instance of
	 * <code>PropertiesUpdateStatus</code>.
	 */
	@Override
	public void setStatus(IStatus status) {
		if (status instanceof PropertiesUpdateStatus) {
			super.setStatus(status);
		} else if ((getStatus() instanceof PropertiesUpdateStatus)) {
			((PropertiesUpdateStatus) getStatus()).add(status);
		} else {
			assert getStatus()
					.getSeverity() == IStatus.CANCEL : "VMPropertiesUpdate status should always be a PropertiesUpdateStatus unless update is canceled."; //$NON-NLS-1$
		}
	}

	/**
	 * Overrides the standard done in order to store the retrieved values
	 * in the client's request monitor.
	 */
	@Override
	public void done() {
		@SuppressWarnings("unchecked")
		DataRequestMonitor<Map<String, Object>> rm = (DataRequestMonitor<Map<String, Object>>) getRequestMonitor();
		rm.setData(fValues);

		// trace our result
		if (VMViewerUpdateTracing.DEBUG_VMUPDATES && !isCanceled()
				&& VMViewerUpdateTracing.matchesFilterRegex(this.getClass())) {
			StringBuilder str = new StringBuilder();
			str.append(DsfPlugin.getDebugTime()).append(' ').append(LoggingUtils.toString(this))
					.append(" marked done; element = ").append(LoggingUtils.toString(getElement())); //$NON-NLS-1$
			if (fValues != null) {
				Iterator<String> keyIter = fValues.keySet().iterator();
				while (keyIter.hasNext()) {
					String prop = keyIter.next();
					Object val = fValues.get(prop);
					if (val instanceof String[]) {
						val = LoggingUtils.toString((String[]) val);
					}
					str.append("   ").append(prop).append("=").append(val).append('\n'); //$NON-NLS-1$ //$NON-NLS-2$
				}
				str.deleteCharAt(str.length() - 1); // remove trailing linefeed
			}
			DsfUIPlugin.debug(str.toString());
		}

		super.done();
	}

	@Override
	public String toString() {
		return "VMPropertiesUpdate:" + getElement() + " " + fProperties; //$NON-NLS-1$ //$NON-NLS-2$/
	}
}
