/*******************************************************************************
 * Copyright (c) 2000, 2017 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 Corporation - initial API and implementation
 *******************************************************************************/

package org.eclipse.team.internal.core;

import java.io.DataInputStream;
import java.io.EOFException;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.TreeMap;

import org.eclipse.core.resources.IStorage;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.content.IContentType;
import org.eclipse.core.runtime.content.IContentTypeManager;
import org.eclipse.team.core.IFileContentManager;
import org.eclipse.team.core.IStringMapping;
import org.eclipse.team.core.Team;

/**
 * TODO: implement extension point
 */
public class FileContentManager implements IFileContentManager {

	private static final String PREF_TEAM_EXTENSION_TYPES= "file_types"; //$NON-NLS-1$
	private static final String PREF_TEAM_FILENAME_TYPES= "cvs_mode_for_file_without_extensions"; //$NON-NLS-1$

	private static class StringMapping implements IStringMapping {

		private final String fString;
		private final int fType;

		public StringMapping(String string, int type) {
			fString= string;
			fType= type;
		}

		@Override
		public String getString() {
			return fString;
		}

		@Override
		public int getType() {
			return fType;
		}
	}

	private static class UserExtensionMappings extends UserStringMappings {

		public UserExtensionMappings(String key) {
			super(key);
		}

		@Override
		protected Map<String, Integer> loadMappingsFromPreferences() {
			final Map<String, Integer> result= super.loadMappingsFromPreferences();
			if (loadMappingsFromOldWorkspace(result)) {
				TeamPlugin.getPlugin().savePluginPreferences();
			}
			return result;
		}

		/**
		 * If the workspace is an old 2.0 one, read the old file and delete it.
		 *
		 * @param A map where the new mappings should be added.
		 *
		 * @return true if the workspace was a 2.0 one and the old mappings have
		 * been added to the map, false otherwise.
		 *
		 */
		private boolean loadMappingsFromOldWorkspace(Map<String, Integer> map) {
			// File name of the persisted file type information
			String STATE_FILE = ".fileTypes"; //$NON-NLS-1$
			IPath pluginStateLocation = TeamPlugin.getPlugin().getStateLocation().append(STATE_FILE);
			File f = pluginStateLocation.toFile();

			if (!f.exists())
				return false;

			try {
				DataInputStream input = new DataInputStream(new FileInputStream(f));
				try {
					map.putAll(readOldFormatExtensionMappings(input));
				} finally {
					input.close();
					f.delete();
				}
			} catch (IOException ex) {
				TeamPlugin.log(IStatus.ERROR, ex.getMessage(), ex);
				return false;
			}
			return true;
		}

		/**
		 * Read the saved file type state from the given input stream.
		 *
		 * @param input the input stream to read the saved state from
		 * @throws IOException if an I/O problem occurs
		 */
		private Map<String, Integer> readOldFormatExtensionMappings(DataInputStream input) throws IOException {
			final Map<String, Integer> result= new TreeMap<>();
			int numberOfMappings = 0;
			try {
				numberOfMappings = input.readInt();
			} catch (EOFException e) {
				// Ignore the exception, it will occur if there are no
				// patterns stored in the state file.
				return Collections.emptyMap();
			}
			for (int i = 0; i < numberOfMappings; i++) {
				final String extension = input.readUTF();
				final int type = input.readInt();
				result.put(extension, Integer.valueOf(type));
			}
			return result;
		}
	}

	private final UserStringMappings fUserExtensionMappings, fUserNameMappings;
	private PluginStringMappings fPluginExtensionMappings;//, fPluginNameMappings;
	private IContentType textContentType;

	public FileContentManager() {
		fUserExtensionMappings= new UserExtensionMappings(PREF_TEAM_EXTENSION_TYPES);
		fUserNameMappings= new UserStringMappings(PREF_TEAM_FILENAME_TYPES);
		fPluginExtensionMappings= new PluginStringMappings(TeamPlugin.FILE_TYPES_EXTENSION, "extension"); //$NON-NLS-1$
	}

	@Override
	public int getTypeForName(String filename) {
		final int userType= fUserNameMappings.getType(filename);
//        final int pluginType= fPluginNameMappings.getType(filename);
//        return userType != Team.UNKNOWN ? userType : pluginType;
		return userType;
	}

	@Override
	public int getTypeForExtension(String extension) {
		final int userType= fUserExtensionMappings.getType(extension);
		final int pluginType= fPluginExtensionMappings.getType(extension);
		return userType != Team.UNKNOWN ? userType : pluginType;
	}

	@Override
	public void addNameMappings(String[] names, int [] types) {
		fUserNameMappings.addStringMappings(names, types);
	}

	@Override
	public void addExtensionMappings(String[] extensions, int [] types) {
		fUserExtensionMappings.addStringMappings(extensions, types);
	}

	@Override
	public void setNameMappings(String[] names, int [] types) {
		fUserNameMappings.setStringMappings(names, types);
	}

	@Override
	public void setExtensionMappings(String[] extensions, int [] types) {
		fUserExtensionMappings.setStringMappings(extensions, types);
	}

	@Override
	public IStringMapping[] getNameMappings() {
		return getMappings(fUserNameMappings, null);//fPluginNameMappings);
	}

	@Override
	public IStringMapping[] getExtensionMappings() {
		return getMappings(fUserExtensionMappings, fPluginExtensionMappings);
	}

	@Override
	public int getType(IStorage storage) {
		int type;

		final String name= storage.getName();
		if (name != null && (type= getTypeForName(name)) != Team.UNKNOWN)
			return type;

		final String extension= getFileExtension(name);
		if (extension != null && (type= getTypeForExtension(extension)) != Team.UNKNOWN)
			return type;

		IContentType contentType = Platform.getContentTypeManager().findContentTypeFor(name);
		if (contentType != null) {
			IContentType textType = getTextContentType();
			if (contentType.isKindOf(textType)) {
				return Team.TEXT;
			}
		}

		return Team.UNKNOWN;
	}

	private IContentType getTextContentType() {
		if (textContentType == null)
			textContentType = Platform.getContentTypeManager().getContentType(IContentTypeManager.CT_TEXT);
		return textContentType;
	}

	@Override
	public IStringMapping[] getDefaultNameMappings() {
		// TODO: There is currently no extension point for this
		return new IStringMapping[0];//getStringMappings(fPluginNameMappings.referenceMap());
	}

	@Override
	public IStringMapping[] getDefaultExtensionMappings() {
		return getStringMappings(fPluginExtensionMappings.referenceMap());
	}

	@Override
	public boolean isKnownExtension(String extension) {
		return fUserExtensionMappings.referenceMap().containsKey(extension)
		|| fPluginExtensionMappings.referenceMap().containsKey(extension);
	}

	@Override
	public boolean isKnownFilename(String filename) {
		return fUserNameMappings.referenceMap().containsKey(filename);
//        || fPluginNameMappings.referenceMap().containsKey(filename);
	}

	private static String getFileExtension(String name) {
		if (name == null)
			return null;
		int index = name.lastIndexOf('.');
		if (index == -1)
			return null;
		if (index == (name.length() - 1))
			return ""; //$NON-NLS-1$
		return name.substring(index + 1);
	}

	private static IStringMapping [] getStringMappings(Map map) {
		final IStringMapping [] result= new IStringMapping [map.size()];
		int index= 0;
		for (final Iterator iter = map.entrySet().iterator(); iter.hasNext();) {
			final Map.Entry entry= (Map.Entry)iter.next();
			result[index++]= new StringMapping((String)entry.getKey(), ((Integer)entry.getValue()).intValue());
		}
		return result;
	}

	private IStringMapping [] getMappings(UserStringMappings userMappings, PluginStringMappings pluginMappings) {
		final Map<String, Integer> mappings= new HashMap<>();
		if (pluginMappings != null)
			mappings.putAll(pluginMappings.referenceMap());
		mappings.putAll(userMappings.referenceMap());
		return getStringMappings(mappings);
	}
}
