/*******************************************************************************
 * Copyright (c) 2000, 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 - Initial API and implementation
 *******************************************************************************/
package org.eclipse.core.tools.resources;

import java.util.Map;
import org.eclipse.core.internal.resources.*;
import org.eclipse.core.resources.*;
import org.eclipse.core.runtime.*;
import org.eclipse.core.tools.*;
import org.eclipse.jface.util.SafeRunnable;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.osgi.util.NLS;

/**
 * Constructs a tree made of <code>TreeContentProviderNode</code>, representing
 * several details about resources.
 * Details include:
 * <ul>
 *   <li>resource's full path;</li>
 *   <li>content ID;</li>
 *   <li>node ID;</li>
 *   <li>resource type;</li>
 *   <li>local sync info;</li>
 *   <li>resource info flags;</li>
 *   <li>markers' type and attributes;</li>
 *   <li>sync info;</li>
 *   <li>session properties;</li>
 *   <li>persistent properties.</li>
 * </ul>
 *
 * @see org.eclipse.core.tools.TreeContentProviderNode
 */
@SuppressWarnings("restriction")
public class ResourceContentProvider extends AbstractTreeContentProvider {

	/**
	 * The maximum number of bytes to be shown for sync info
	 */
	private final static int MAX_SYNC_INFO_BYTES = 8;

	/**
	 * Collects resource info. Calls all other <code>extract...</code> methods.
	 *
	 * @param selectedResource the resource object from where to extract information
	 */
	protected void extractInfo(IResource selectedResource) {

		if (!(selectedResource instanceof Resource)) {
			String message = NLS.bind(Messages.resource_error_unknown_resource_impl, selectedResource.getClass().getName());
			getRootNode().addChild(new TreeContentProviderNode(message));
			return;
		}

		Resource resource = (Resource) selectedResource;

		ResourceInfo info = resource.getResourceInfo(true, false);
		// Resource#getResourceInfo may return null when the resource
		// does not exist anymore. In this case, we just ignore it.
		if (info == null) {
			return;
		}
		extractBasicInfo(resource, info);
		extractFlags(info);
		extractContentDescription(resource);
		extractMarkersInfo(resource, info);
		extractSyncInfo(info);
		extractPersistentProperties(resource);
		extractSessionProperties(resource);

	}

	/**
	 * Collects persistent properties information.
	 *
	 * @param resource the resource object from where to extract information
	 */
	protected void extractPersistentProperties(IResource resource) {
		try {
			Map<QualifiedName, String> properties = ((Workspace) ResourcesPlugin.getWorkspace()).getPropertyManager().getProperties(resource);
			if (properties.isEmpty()) {
				return;
			}
			// creates a node for persistent properties and populates it
			TreeContentProviderNode propertiesRootNode = createNode(Messages.resource_persistent_properties);
			getRootNode().addChild(propertiesRootNode);
			for (Map.Entry<QualifiedName, String> entry : properties.entrySet()) {
				propertiesRootNode.addChild(createNode(entry.getKey().toString(), entry.getValue()));
			}
		} catch (CoreException ce) {
			getRootNode().addChild(createNode(NLS.bind(Messages.resource_error_stored_properties, ce.toString())));
		}
	}

	/**
	 * Collects session properties information.
	 *
	 * @param resource the resource object from where to extract information
	 */
	protected void extractSessionProperties(IResource resource) {

		// retrieves all session properties for the selected resource
		Map<QualifiedName, Object> properties = org.eclipse.core.internal.resources.SpySupport.getSessionProperties(resource);
		if (properties == null || properties.size() == 0) {
			return;
		}
		// creates a node for session properties and populates it
		TreeContentProviderNode propertiesRootNode = createNode(Messages.resource_session_properties);
		getRootNode().addChild(propertiesRootNode);

		for (Map.Entry<QualifiedName, Object> entry : properties.entrySet()) {
			propertiesRootNode.addChild(createNode(entry.getKey().toString(), entry.getValue()));
		}

	}

	/**
	 * Extracts basic resource info.
	 *
	 * @param resource the resource object from where to extract information
	 * @param info the resource info object from where to extract information
	 */
	protected void extractBasicInfo(IResource resource, ResourceInfo info) {

		// extracts information from IResource
		IPath resourcePath = resource.getFullPath();
		long id = info.getContentId();

		String type = null;
		switch (resource.getType()) {
			case IResource.FILE :
				type = Messages.resource_file;
				break;
			case IResource.FOLDER :
				type = Messages.resource_folder;
				break;
			case IResource.PROJECT :
				type = Messages.resource_project;
				break;
			case IResource.ROOT :
				type = Messages.resource_root;
				break;
		}

		// extracts information from ResourceInfo
		long localSyncInfo = info.getLocalSyncInfo();
		long nodeId = info.getNodeId();

		// creates a root node for each basic information
		getRootNode().addChild(createNode(Messages.resource_full_path, resourcePath));
		getRootNode().addChild(createNode(Messages.resource_content_id, Long.toString(id)));
		getRootNode().addChild(createNode(Messages.resource_type, type));
		getRootNode().addChild(createNode(Messages.resource_node_id, Long.toString(nodeId)));
		getRootNode().addChild(createNode(Messages.resource_local_sync_info, Long.toString(localSyncInfo)));
	}

	/**
	 * Extracts flags from the resource info object, building a 'Flags'
	 * subtree in the resource details' tree.
	 *
	 * @param info the resource info object from where to extract information
	 */

	protected void extractFlags(ResourceInfo info) {
		// extract flags'values for the resource info object
		boolean isOpen = info.isSet(ICoreConstants.M_OPEN);
		boolean localExists = info.isSet(ICoreConstants.M_LOCAL_EXISTS);
		boolean isPhantom = info.isSet(ICoreConstants.M_PHANTOM);
		boolean isUsed = info.isSet(ICoreConstants.M_USED);
		boolean isDerived = info.isSet(ICoreConstants.M_DERIVED);
		boolean isTeamPrivateMember = info.isSet(ICoreConstants.M_TEAM_PRIVATE_MEMBER);
		boolean isMarkersSnapDirty = info.isSet(ICoreConstants.M_MARKERS_SNAP_DIRTY);
		boolean isSyncInfoSnapDirty = info.isSet(ICoreConstants.M_SYNCINFO_SNAP_DIRTY);
		boolean noContentDescription = info.isSet(ICoreConstants.M_NO_CONTENT_DESCRIPTION);
		boolean defaultContentDescription = info.isSet(ICoreConstants.M_DEFAULT_CONTENT_DESCRIPTION);
		boolean isHidden = info.isSet(ICoreConstants.M_HIDDEN);

		// creates a node for flags
		TreeContentProviderNode flagsParentNode = createNode(Messages.resource_flags);
		getRootNode().addChild(flagsParentNode);

		// creates a child node in "Flags" node for each basic information
		flagsParentNode.addChild(createNode(Messages.resource_open, Boolean.valueOf(isOpen)));
		flagsParentNode.addChild(createNode(Messages.resource_local_exists, Boolean.valueOf(localExists)));
		flagsParentNode.addChild(createNode(Messages.resource_phantom, Boolean.valueOf(isPhantom)));
		flagsParentNode.addChild(createNode(Messages.resource_used, Boolean.valueOf(isUsed)));
		flagsParentNode.addChild(createNode(Messages.resource_derived, Boolean.valueOf(isDerived)));
		flagsParentNode.addChild(createNode(Messages.resource_team_private, Boolean.valueOf(isTeamPrivateMember)));
		flagsParentNode.addChild(createNode(Messages.resource_markers_snap_dirty, Boolean.valueOf(isMarkersSnapDirty)));
		flagsParentNode.addChild(createNode(Messages.resource_sync_info_snap_dirty, Boolean.valueOf(isSyncInfoSnapDirty)));
		flagsParentNode.addChild(createNode(Messages.resource_no_content_description, Boolean.valueOf(noContentDescription)));
		flagsParentNode.addChild(createNode(Messages.resource_default_content_description, Boolean.valueOf(defaultContentDescription)));
		flagsParentNode.addChild(createNode(Messages.resource_hidden, Boolean.valueOf(isHidden)));

	}

	protected void extractContentDescription(Resource resource) {
		if (resource.getType() != IResource.FILE) {
			return;
		}
		File file = (File) resource;
		// creates a node for flags
		try {
			boolean cached = org.eclipse.core.internal.resources.SpySupport.isContentDescriptionCached(file);
			String description = file.getContentDescription() + " (" + NLS.bind(Messages.resource_content_description_from_cache, Boolean.toString(cached)) + ")"; //$NON-NLS-1$//$NON-NLS-2$
			TreeContentProviderNode contentDescriptionNode = createNode(Messages.resource_content_description, description);
			getRootNode().addChild(contentDescriptionNode);
		} catch (CoreException ce) {
			// in the case of a CoreException, just present it as node
			getRootNode().addChild(createNode(NLS.bind(Messages.resource_error_content_description, ce.toString())));
		}

	}

	/**
	 * Extracts from resource info object all information regarding markers,
	 * building a 'Markers' subtree in the resource details tree.
	 *
	 * @param resource the resource object from where to extract information
	 * @param info the resource info object from where to extract information
	 */
	protected void extractMarkersInfo(IResource resource, ResourceInfo info) {
		MarkerSet markerSet = info.getMarkers();
		if (markerSet == null) {
			return;
		}
		IMarkerSetElement[] markerSetElements = markerSet.elements();
		if (markerSetElements.length == 0) {
			return;
		}
		// creates a root node for all markers
		TreeContentProviderNode markersParentNode = createNode(Messages.resource_markers);
		getRootNode().addChild(markersParentNode);

		int markerSetSize = markerSetElements.length;
		for (int i = 0; i < markerSetSize; i++) {
			try {
				long markerID = markerSetElements[i].getId();
				IMarker marker = resource.getMarker(markerID);

				// creates a marker node
				String id = " [id = " + marker.getId() + "]"; //$NON-NLS-1$//$NON-NLS-2$
				TreeContentProviderNode markerNode = createNode(marker.getType() + id);
				markersParentNode.addChild(markerNode);
				extractMarkerAttributes(markerNode, marker);

			} catch (CoreException ce) {
				// in the case of a CoreException, just present it as node
				markersParentNode.addChild(createNode(NLS.bind(Messages.resource_error_marker, ce.toString())));
			}
		}
	}

	/**
	 * Retrieves all attributes from a marker, adding them as sub-nodes in the
	 * marker's node.
	 *
	 * @param markerNode the marker's node
	 * @param marker the marker object
	 * @throws CoreException if this method fails. Reasons include:
	 * <ul>
	 * <li>This marker does not exist.</li>
	 * </ul>
	 */
	protected void extractMarkerAttributes(TreeContentProviderNode markerNode, IMarker marker) throws CoreException {

		Map<String, Object> attributes = marker.getAttributes();
		if (attributes == null || attributes.size() == 0) {
			return;
		}
		// create a node (under markerNode) for each attribute found
		for (Map.Entry<String, Object> mapEntry : attributes.entrySet()) {
			String attributeName = mapEntry.getKey();
			Object attributeValue = mapEntry.getValue();
			// adds each attribute as a subnode under
			// this marker's node
			markerNode.addChild(createNode(attributeName, attributeValue));
		}
		// attributes will be ordered by attribute name
		markerNode.sort();
	}

	/**
	 * Extracts from the resource info object all information related to VCM,
	 * building a 'Sync Info' subtree in the resource details tree.
	 *
	 * @param info the resource info object from where to extract information
	 */
	protected void extractSyncInfo(ResourceInfo info) {
		Map<QualifiedName, Object> syncInfo = info.getSyncInfo(true);
		if (syncInfo == null || syncInfo.isEmpty()) {
			return;
		}
		// creates a root node for all sync info
		TreeContentProviderNode syncInfoParentNode = createNode(Messages.resource_sync_info);
		getRootNode().addChild(syncInfoParentNode);

		for (Map.Entry<QualifiedName, Object> entry : syncInfo.entrySet()) {
			// creates a sync info node
			String name = entry.getKey().toString();
			String value = ByteUtil.byteArrayToString((byte[]) entry.getValue(), MAX_SYNC_INFO_BYTES);
			syncInfoParentNode.addChild(createNode(name, value));
		}

	}

	/**
	 * Reconstructs this content provider data model upon the provided input object.
	 *
	 * @param input the new input object - must not be null
	 */
	@Override
	protected void rebuild(Viewer viewer, final Object input) {
		SafeRunner.run(new SafeRunnable() {
			@Override
			public void run() throws Exception {
				extractInfo((IResource) input);
			}
		});
	}

	/**
	 * Returns true if the input is a resource.
	 *
	 * @param input an input object
	 * @see org.eclipse.core.tools.AbstractTreeContentProvider#acceptInput(java.lang.Object)
	 */
	@Override
	protected boolean acceptInput(Object input) {
		return input instanceof IResource;
	}

}
