/*******************************************************************************
 * Copyright (c) 2001, 2023 IBM Corporation and others.
 * All rights reserved. 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 Corporation - initial API and implementation
 *     Jens Lukowski/Innoopract - initial renaming/restructuring
 *     
 *******************************************************************************/
package org.eclipse.wst.sse.core.internal.modelhandler;

import java.io.IOException;
import java.io.InputStream;

import org.eclipse.core.resources.IFile;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IConfigurationElement;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.content.IContentDescription;
import org.eclipse.core.runtime.content.IContentType;
import org.eclipse.wst.sse.core.internal.Logger;
import org.eclipse.wst.sse.core.internal.encoding.CodedIO;
import org.eclipse.wst.sse.core.internal.ltk.modelhandler.IModelHandler;
import org.eclipse.wst.sse.core.internal.util.Utilities;


public class ModelHandlerRegistry {
	private static ModelHandlerRegistry instance = null;
	static final String INTERNAL_DEFAULT_EXTENSION = "org.eclipse.wst.xml.core.internal.modelhandler"; //$NON-NLS-1$

	public synchronized static ModelHandlerRegistry getInstance() {
		if (instance == null) {
			instance = new ModelHandlerRegistry();
		}
		return instance;
	}

	private IModelHandler defaultHandler = null;
	private ModelHandlerRegistryReader reader = new ModelHandlerRegistryReader();

	private ModelHandlerRegistry() {
		super();
		reader = new ModelHandlerRegistryReader().readRegistry();
	}

	/**
	 * Finds the default model handler. Note: we still go through the registry
	 * to be sure to get the existing instance, but then we do remember it, so
	 * subsequent requests will be faster. The first time through, we do check
	 * the whole list, to be sure there is only one.
	 * 
	 */
	final public IModelHandler getDefault() {
		if (defaultHandler == null) {
			IConfigurationElement[] elements = reader.elements;
			for (int i = 0; i < elements.length; i++) {
				boolean ofInterest = reader.isElementDefault(elements[i]);
				if (ofInterest) {
					/*
					 * If, here within the search loop we've already found one
					 * defaultHandler, then something is wrong!
					 */
					if (defaultHandler == null) {
						defaultHandler = reader.getInstance(elements[i]);
					}
					else {
						String errorString = "Program or configuration error. More than one default content handler found"; //$NON-NLS-1$
						Logger.log(Logger.ERROR, errorString);
						throw new IllegalStateException(errorString);
					}
				}
			}
		}
		if (defaultHandler == null) {
			String errorString = "Program or configuration error. No default content type handler found."; //$NON-NLS-1$
			Logger.log(Logger.ERROR, errorString);
			throw new IllegalStateException(errorString);
		}
		return defaultHandler;
	}

	/**
	 * Finds a ModelHandler based on literal extension id. It's basically a
	 * "first found first returned". No specific order is guaranteed and the
	 * uniqueness of IDs is not considered.
	 * 
	 * @param extensionId
	 * @return the given extension, or null
	 */
	private IModelHandler getHandlerExtension(String extensionId) {
		IModelHandler found = null;
		IConfigurationElement[] elements = reader.elements;
		if (elements != null) {
			for (int i = 0; i < elements.length; i++) {
				String currentId = reader.getId(elements[i]);
				if (extensionId.equals(currentId)) {
					IModelHandler item = reader.getInstance(elements[i]);
					found = item;
				}
			}
		}
		else if (Logger.DEBUG){
			Logger.log(Logger.WARNING, "There were no Model Handler found in registry"); //$NON-NLS-1$
		}
		return found;
	}

	/**
	 * Finds the registered IModelHandler for a given named file's content
	 * type.
	 * 
	 * @param file
	 * @param provideDefault should the default extension be used in the absence of other methods
	 * @return The IModelHandler registered for the content type of the given
	 *         file. If an exact match is not found, the most-specific match
	 *         according to IContentType.isKindOf() will be returned. If none
	 *         are found, either a default or null will be returned.
	 * @throws CoreException
	 */
	public IModelHandler getHandlerFor(IFile file, boolean provideDefault) throws CoreException {
		IModelHandler modelHandler = null;
		IContentDescription contentDescription = null;
		IContentType contentType = null;
		boolean accessible = file.isAccessible();
		if (accessible) {
			/* Try the optimized method first as the description may be cached */
			contentDescription = file.getContentDescription();
			if (contentDescription != null) {
				// use the provided description
				contentType = contentDescription.getContentType();
			}
			else {
				/* use the more thorough discovery method to get a description */
				InputStream contents = null;
				try {
					contents = file.getContents(false);
					contentDescription = Platform.getContentTypeManager().getDescriptionFor(contents, file.getName(), IContentDescription.ALL);
					if (contentDescription != null) {
						contentType = contentDescription.getContentType();
					}
				}
				catch (IOException e) {
					// nothing further can be done, but will log for debugging
					Logger.logException(e);
				}
				finally {
					if (contents != null) {
						try {
							contents.close();
						}
						catch (IOException e1) {
							// nothing can be done
						}
					}
				}
			}
		}

		/*
		 * If we couldn't get the content type from a description, try basing
		 * it on just the filename
		 */
		if (contentType == null) {
			contentType = Platform.getContentTypeManager().findContentTypeFor(file.getName());
		}

		if (contentType != null) {
			modelHandler = getHandlerForContentType(contentType);
		}
		else if (contentType == null && provideDefault) {
			// hard coding for null content type
			modelHandler = getHandlerExtension(INTERNAL_DEFAULT_EXTENSION); //$NON-NLS-1$
		}

		return modelHandler;
	}

	/**
	 * Finds the registered IModelHandler for a given named file's content
	 * type. Will check for a default.
	 * 
	 * @param file
	 * @return The IModelHandler registered for the content type of the given
	 *         file. If an exact match is not found, the most-specific match
	 *         according to IContentType.isKindOf() will be returned. If none
	 *         are found, either a default or null will be returned.
	 * @throws CoreException
	 */
	public IModelHandler getHandlerFor(IFile file) throws CoreException {
		return getHandlerFor(file, true);
	}


	/**
	 * Finds the registered IModelHandler for a given named InputStream.
	 * 
	 * @param inputName
	 * @param inputStream
	 * @return The IModelHandler registered for the content type of the given
	 *         input. If an exact match is not found, the most-specific match
	 *         according to IContentType.isKindOf() will be returned. If none
	 *         are found, either a default or null will be returned.
	 * @throws IOException
	 */
	public IModelHandler getHandlerFor(String inputName, InputStream inputStream) throws IOException {
		InputStream iStream = Utilities.getMarkSupportedStream(inputStream);
		IModelHandler modelHandler = null;
		IContentType contentType = null;
		if (inputStream != null) {
			try {
				iStream.mark(CodedIO.MAX_MARK_SIZE);
				contentType = Platform.getContentTypeManager().findContentTypeFor(Utilities.getLimitedStream(iStream), inputName);
			}
			finally {
				if (iStream != null && iStream.markSupported()) {
					iStream.reset();
				}
			}

		}
		if (contentType == null) {
			contentType = Platform.getContentTypeManager().findContentTypeFor(inputName);
		}
		// if all else failed, try to detect solely on contents; done last for
		// performance reasons
		if (contentType == null) {
			contentType = Platform.getContentTypeManager().findContentTypeFor(Utilities.getLimitedStream(iStream), null);
		}
		modelHandler = getHandlerForContentType(contentType);
		return modelHandler;
	}

	/**
	 * Finds the registered IModelHandler for a given IContentType.
	 * 
	 * @param contentType
	 * @return The IModelHandler registered for the given content type. If an
	 *         exact match is not found, the most-specific match according to
	 *         IContentType.isKindOf() will be returned. If none are found,
	 *         either a default or null will be returned.
	 */
	private IModelHandler getHandlerForContentType(IContentType contentType) {
		IModelHandler handler = null;
		if (contentType != null) {
			IConfigurationElement exactContentTypeElement = null;
			IConfigurationElement kindOfContentTypeElement = null;
			int kindOfContentTypeDepth = 0;
			IConfigurationElement[] elements = reader.elements;
			if (elements != null) {
				for (int i = 0; i < elements.length && exactContentTypeElement == null; i++) {
					String currentId = reader.getAssociatedContentTypeId(elements[i]);
					IContentType associatedContentType = Platform.getContentTypeManager().getContentType(currentId);
					if (contentType.equals(associatedContentType)) {
						exactContentTypeElement = elements[i];
					}
					else if (contentType.isKindOf(associatedContentType)) {
						/*
						 * Update the kindOfElement variable only if this
						 * element's content type is "deeper" (depth test
						 * ensures the first content type is remembered)
						 */
						IContentType testContentType = associatedContentType;
						int testDepth = 0;
						while (testContentType != null) {
							testDepth++;
							testContentType = testContentType.getBaseType();
						}
						if (testDepth > kindOfContentTypeDepth) {
							kindOfContentTypeElement = elements[i];
							kindOfContentTypeDepth = testDepth;
						}
					}
				}
			}
			else if (Logger.DEBUG){
				Logger.log(Logger.WARNING, "There were no Model Handler found in registry"); //$NON-NLS-1$
			}
			if (exactContentTypeElement != null) {
				handler = reader.getInstance(exactContentTypeElement);
			}
			else if (kindOfContentTypeElement != null) {
				handler = reader.getInstance(kindOfContentTypeElement);
			}
		}

		if (handler == null) {
			// temp hard coding for null content type arguments
			// handler = getHandlerExtension(INTERNAL_DEFAULT_EXTENSION); //$NON-NLS-1$
		}
		return handler;
	}

	/**
	 * Finds the registered IModelHandler for a given content type ID. No
	 * specific order is guaranteed and the uniqueness of IDs is not
	 * considered.
	 * 
	 * @param contentType
	 * @return The IModelHandler registered for the given content type ID. If
	 *         an exact match is not found, the most-specific match according
	 *         to IContentType.isKindOf() will be returned. If none are found,
	 *         either a default or null will be returned.
	 */
	public IModelHandler getHandlerForContentTypeId(String contentTypeId) {
		IContentType contentType = Platform.getContentTypeManager().getContentType(contentTypeId);
		return getHandlerForContentType(contentType);
	}
}
