/*******************************************************************************
 * Copyright (c) 2006, 2014 Wind River Systems, Inc. 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:
 *     Markus Schorn - initial API and implementation
 *******************************************************************************/
package org.eclipse.cdt.internal.core;

import org.eclipse.cdt.core.CCorePlugin;
import org.eclipse.cdt.core.model.CoreModel;
import org.eclipse.cdt.internal.core.pdom.indexer.IndexerPreferences;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.ProjectScope;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.content.IContentType;
import org.eclipse.core.runtime.content.IContentTypeMatcher;
import org.eclipse.core.runtime.content.IContentTypeSettings;
import org.eclipse.core.runtime.preferences.IScopeContext;
import org.osgi.service.prefs.BackingStoreException;
import org.osgi.service.prefs.Preferences;

/**
 * Handles the access to the content types of the platform.
 * @author markus.schorn@windriver.com
 */
public class CContentTypes {
	private static final String PREF_LOCAL_CONTENT_TYPE_SETTINGS = "enabled"; //$NON-NLS-1$
	private static final Preferences PROJECT_SCOPE = Platform.getPreferencesService().getRootNode()
			.node(ProjectScope.SCOPE);
	private static final String CONTENT_TYPE_PREF_NODE = "content-types"; //$NON-NLS-1$
	private static final String FULLPATH_CONTENT_TYPE_PREF_NODE = Platform.PI_RUNTIME + IPath.SEPARATOR
			+ CONTENT_TYPE_PREF_NODE;

	/**
	 * Implementation for {@link CCorePlugin#getContentType(IProject, String)}.
	 */
	public static IContentType getContentType(IProject project, String filename) {
		if (filename == null) {
			return null;
		}
		IContentTypeMatcher matcher = null;
		IScopeContext scopeCtx = null;
		boolean preferCpp = true;
		if (project != null) {
			// try with the project settings
			try {
				matcher = project.getContentTypeMatcher();
				if (usesProjectSpecificContentTypes(project)) {
					scopeCtx = new ProjectScope(project);
				}
				if (CoreModel.hasCNature(project)) {
					preferCpp = CoreModel.hasCCNature(project) == IndexerPreferences.preferDefaultLanguage(project);
				}
			} catch (CoreException e) {
				// fallback to workspace wide definitions.
				matcher = Platform.getContentTypeManager();
			}
		} else {
			matcher = Platform.getContentTypeManager();
		}

		IContentType[] cts = matcher.findContentTypesFor(filename);
		switch (cts.length) {
		case 0:
			return null;
		case 1:
			return cts[0];
		}

		int maxPossiblePriority = scopeCtx == null ? 11 : 101;
		int bestPriority = -1;
		IContentType bestResult = null;

		for (int i = 0; i < cts.length; i++) {
			IContentType candidate = cts[i];
			int priority = 0;
			try {
				if (scopeCtx != null) {
					IContentTypeSettings settings = candidate.getSettings(scopeCtx);
					if (isStrictlyAssociatedWith(settings, filename)) {
						priority = 100;
					}
				}
				if (priority == 0 && bestPriority < 100) {
					if (isStrictlyAssociatedWith(candidate, filename)) {
						priority = 10;
					}
				}
				if (isPreferredContentType(candidate, preferCpp)) {
					priority += 1;
				}
			} catch (CoreException e) {
				// skip it
			}
			if (priority > bestPriority) {
				if (priority == maxPossiblePriority) {
					return candidate;
				}
				bestPriority = priority;
				bestResult = candidate;
			}
		}
		return bestResult;
	}

	private static boolean isPreferredContentType(IContentType candidate, boolean preferCpp) {
		while (candidate != null) {
			String id = candidate.getId();
			if (CCorePlugin.CONTENT_TYPE_CXXHEADER.equals(id) || CCorePlugin.CONTENT_TYPE_CXXSOURCE.equals(id)) {
				return preferCpp;
			}

			if (CCorePlugin.CONTENT_TYPE_CHEADER.equals(id) || CCorePlugin.CONTENT_TYPE_CSOURCE.equals(id)) {
				return !preferCpp;
			}
			candidate = candidate.getBaseType();
		}
		return false;
	}

	private static boolean isStrictlyAssociatedWith(IContentTypeSettings settings, String filename) {
		String[] namespecs = settings.getFileSpecs(IContentType.FILE_NAME_SPEC);
		for (int i = 0; i < namespecs.length; i++) {
			String name = namespecs[i];
			if (name.equals(filename)) {
				return true;
			}
		}
		// check the file extensions only
		int dotPosition = filename.lastIndexOf('.');
		if (dotPosition >= 0 && dotPosition < filename.length() - 1) {
			String fileExtension = filename.substring(dotPosition + 1);
			String[] extensions = settings.getFileSpecs(IContentType.FILE_EXTENSION_SPEC);
			for (int i = 0; i < extensions.length; i++) {
				String ext = extensions[i];
				if (ext.equals(fileExtension)) {
					return true;
				}
			}
		}
		return false;
	}

	/**
	 * This method is copied from the resources plugin and figures out whether
	 * project specific settings are enabled or not.
	 * Implementation for {@link CCorePlugin#usesProjectSpecificContentTypes(IProject)}.
	 */
	public static boolean usesProjectSpecificContentTypes(IProject project) {
		String projectName = project.getName();
		try {
			// be careful looking up for our node so not to create any nodes as side effect
			Preferences node = PROJECT_SCOPE;
			//TODO once bug 90500 is fixed, should be simpler
			// for now, take the long way
			if (!node.nodeExists(projectName))
				return false;
			node = node.node(projectName);
			if (!node.nodeExists(Platform.PI_RUNTIME))
				return false;
			node = node.node(Platform.PI_RUNTIME);
			if (!node.nodeExists(CONTENT_TYPE_PREF_NODE))
				return false;
			node = node.node(CONTENT_TYPE_PREF_NODE);
			return node.getBoolean(PREF_LOCAL_CONTENT_TYPE_SETTINGS, false);
		} catch (BackingStoreException e) {
			// exception treated when retrieving the project preferences
		}
		return false;
	}

	/**
	 * Implementation for {@link CCorePlugin#setUseProjectSpecificContentTypes(IProject, boolean)}.
	 */
	public static void setUseProjectSpecificContentTypes(IProject project, boolean val) {
		ProjectScope projectScope = new ProjectScope(project);
		Preferences contentTypePrefs = projectScope.getNode(FULLPATH_CONTENT_TYPE_PREF_NODE);
		if (usesProjectSpecificContentTypes(project) != val) {
			// enable project-specific settings for this project
			contentTypePrefs.putBoolean(PREF_LOCAL_CONTENT_TYPE_SETTINGS, val);
			try {
				contentTypePrefs.flush();
			} catch (BackingStoreException e) {
				// ignore ??
			}
		}
	}
}
