/*******************************************************************************
 * Copyright (c) 2007, 2012 Intel Corporation and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     Intel Corporation - Initial API and implementation
 *******************************************************************************/
package org.eclipse.cdt.core.settings.model.util;

import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.Set;
import java.util.StringTokenizer;

import org.eclipse.cdt.core.CCorePlugin;
import org.eclipse.cdt.core.cdtvariables.CdtVariableException;
import org.eclipse.cdt.core.cdtvariables.ICdtVariableManager;
import org.eclipse.cdt.core.model.CoreModelUtil;
import org.eclipse.cdt.core.model.ILanguageDescriptor;
import org.eclipse.cdt.core.model.LanguageManager;
import org.eclipse.cdt.core.settings.model.CIncludeFileEntry;
import org.eclipse.cdt.core.settings.model.CIncludePathEntry;
import org.eclipse.cdt.core.settings.model.CLibraryFileEntry;
import org.eclipse.cdt.core.settings.model.CLibraryPathEntry;
import org.eclipse.cdt.core.settings.model.CMacroEntry;
import org.eclipse.cdt.core.settings.model.CMacroFileEntry;
import org.eclipse.cdt.core.settings.model.COutputEntry;
import org.eclipse.cdt.core.settings.model.CSourceEntry;
import org.eclipse.cdt.core.settings.model.ICConfigurationDescription;
import org.eclipse.cdt.core.settings.model.ICExclusionPatternPathEntry;
import org.eclipse.cdt.core.settings.model.ICLanguageSettingEntry;
import org.eclipse.cdt.core.settings.model.ICLibraryFileEntry;
import org.eclipse.cdt.core.settings.model.ICOutputEntry;
import org.eclipse.cdt.core.settings.model.ICSettingBase;
import org.eclipse.cdt.core.settings.model.ICSettingEntry;
import org.eclipse.cdt.core.settings.model.ICSourceEntry;
import org.eclipse.cdt.core.settings.model.ICStorageElement;
import org.eclipse.cdt.core.settings.model.extension.CBuildData;
import org.eclipse.cdt.core.settings.model.extension.CConfigurationData;
import org.eclipse.cdt.core.settings.model.extension.CFolderData;
import org.eclipse.cdt.core.settings.model.extension.CLanguageData;
import org.eclipse.cdt.core.settings.model.extension.CResourceData;
import org.eclipse.cdt.core.settings.model.extension.CTargetPlatformData;
import org.eclipse.cdt.core.settings.model.extension.impl.CDataFactory;
import org.eclipse.cdt.core.settings.model.extension.impl.CDefaultLanguageData;
import org.eclipse.cdt.internal.core.WeakHashSet;
import org.eclipse.cdt.internal.core.WeakHashSetSynchronized;
import org.eclipse.cdt.internal.core.settings.model.ExceptionFactory;
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.Path;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.content.IContentType;
import org.eclipse.core.runtime.content.IContentTypeManager;
import org.eclipse.core.runtime.content.IContentTypeSettings;
import org.eclipse.core.runtime.preferences.IScopeContext;

public class CDataUtil {
	private static final String EMPTY = "";  //$NON-NLS-1$
	private static final String DELIM = " "; //$NON-NLS-1$

	private static Random randomNumber;
	public static final String[] EMPTY_STRING_ARRAY = {};

	/**
	 * Pool of setting entries implemented as WeakHashSet. That allows to gain memory savings
	 * at the expense of CPU time. WeakHashSet handles garbage collection when a list is not
	 * referenced anywhere else. See JavaDoc {@link java.lang.ref.WeakReference} about weak reference objects.
	 */
	private static WeakHashSet<ICSettingEntry> settingEntriesPool = new WeakHashSetSynchronized<>();

	public static int genRandomNumber() {
		if (randomNumber == null) {
			// Set the random number seed
			randomNumber = new Random();
			randomNumber.setSeed(System.currentTimeMillis());
		}
		int i = randomNumber.nextInt();
		if (i < 0) {
			i *= -1;
		}
		return i;
	}

	public static String genId(String baseId) {
		String suffix = new Integer(genRandomNumber()).toString();
		return baseId != null ?
				new StringBuilder(baseId).append(".").append(suffix).toString() : //$NON-NLS-1$
				suffix;
	}

	public static boolean objectsEqual(Object o1, Object o2) {
		if (o1 == null)
			return o2 == null;
		return o1.equals(o2);
	}

	public static String arrayToString(String[] array, String separator) {
		return arrayToString((Object[])array, separator);
	}

	public static String arrayToString(Object[] array, String separator) {
		if (array == null)
			return null;
		if (array.length == 0)
			return ""; //$NON-NLS-1$
		if (array.length == 1)
			return array[0].toString();
		StringBuilder buf = new StringBuilder();
		buf.append(array[0]);
		for (int i = 1; i < array.length; i++) {
			buf.append(separator).append(array[i]);
		}

		return buf.toString();
	}

	public static String[] stringToArray(String string, String separator) {
		if (string == null)
			return null;
		if (string.length() == 0)
			return EMPTY_STRING_ARRAY;
		StringTokenizer t = new StringTokenizer(string, separator);
		List<String> list = new ArrayList<>(t.countTokens());
		while (t.hasMoreElements()) {
			list.add(t.nextToken());
		}
		return list.toArray(new String[list.size()]);
	}

	public static ICSettingEntry[] resolveEntries(ICSettingEntry entries[], ICConfigurationDescription cfgDes) {
		if (entries.length == 0)
			return entries;

		ArrayList<ICSettingEntry> out = new ArrayList<>(entries.length);
		ICdtVariableManager mngr = CCorePlugin.getDefault().getCdtVariableManager();

		for (int i = 0; i < entries.length; i++) {
			ICSettingEntry entry = entries[i];
			out.addAll(Arrays.asList(createResolvedEntry(entry, cfgDes, mngr)));
		}
		return out.toArray(new ICSettingEntry[out.size()]);
	}

	public static ICLanguageSettingEntry[] resolveEntries(ICLanguageSettingEntry entries[], ICConfigurationDescription cfgDes) {
		if (entries.length == 0)
			return entries;

		ICSettingEntry[] resolved = resolveEntries((ICSettingEntry[])entries, cfgDes);
		ICLanguageSettingEntry[] resolvedLangEntries = new ICLanguageSettingEntry[resolved.length];
		System.arraycopy(resolved, 0, resolvedLangEntries, 0, resolved.length);
		return resolvedLangEntries;
	}

	public static ICSourceEntry[] resolveEntries(ICSourceEntry entries[], ICConfigurationDescription cfgDes) {
		if (entries.length == 0)
			return entries;

		ICSettingEntry[] resolved = resolveEntries((ICSettingEntry[])entries, cfgDes);
		ICSourceEntry[] resolvedLangEntries = new ICSourceEntry[resolved.length];
		System.arraycopy(resolved, 0, resolvedLangEntries, 0, resolved.length);
		return resolvedLangEntries;
	}

	public static ICOutputEntry[] resolveEntries(ICOutputEntry entries[], ICConfigurationDescription cfgDes) {
		if (entries.length == 0)
			return entries;

		ICSettingEntry[] resolved = resolveEntries((ICSettingEntry[])entries, cfgDes);
		ICOutputEntry[] resolvedLangEntries = new ICOutputEntry[resolved.length];
		System.arraycopy(resolved, 0, resolvedLangEntries, 0, resolved.length);
		return resolvedLangEntries;
	}

	private static ICSettingEntry[] createResolvedEntry(ICSettingEntry entry, ICConfigurationDescription cfg, ICdtVariableManager mngr) {
		if (entry.isResolved())
			return new ICSettingEntry[] { entry };

		String name = entry.getName();

		String[] names = new String[] { name }; // default value
		try {
			if ((entry.getKind() != ICSettingEntry.MACRO) &&
					mngr.isStringListValue(name, cfg)) {
				names = mngr.resolveStringListValue(name, EMPTY, DELIM, cfg);
			} else {
				names[0] = mngr.resolveValue(name, EMPTY, DELIM, cfg);
			}
		} catch (CdtVariableException e) {
			CCorePlugin.log(e);
		}

		ICSettingEntry[] result = new ICSettingEntry[names.length];

		for (int k=0; k<names.length; k++) {
			String value = null;
			IPath[] exclusionFilters = null;
			IPath srcPath = null, srcRootPath = null, srcPrefixMapping = null;

			switch (entry.getKind()) {
			case ICSettingEntry.MACRO:
				value = entry.getValue();
				try {
					value = mngr.resolveValue(value, EMPTY, DELIM, cfg);
				} catch (CdtVariableException e) {
					CCorePlugin.log(e);
				}
				break;
			case ICSettingEntry.LIBRARY_FILE:
				ICLibraryFileEntry libFile = (ICLibraryFileEntry)entry;
				srcPath = libFile.getSourceAttachmentPath();
				srcRootPath = libFile.getSourceAttachmentRootPath();
				srcPrefixMapping = libFile.getSourceAttachmentPrefixMapping();
				if (srcPath != null)
					srcPath = resolvePath(mngr, cfg, srcPath);
				if (srcRootPath != null)
					srcRootPath = resolvePath(mngr, cfg, srcRootPath);
				if (srcPrefixMapping != null)
					srcPrefixMapping = resolvePath(mngr, cfg, srcPrefixMapping);
				break;
			case ICSettingEntry.SOURCE_PATH:
			case ICSettingEntry.OUTPUT_PATH:
				exclusionFilters = ((ICExclusionPatternPathEntry)entry).getExclusionPatterns();
				for (int i = 0; i < exclusionFilters.length; i++) {
					String exclString = exclusionFilters[i].toString();
					try {
						exclString = mngr.resolveValue(exclString, EMPTY, DELIM, cfg);
					} catch (CdtVariableException e) {
						CCorePlugin.log(e);
					}
					exclusionFilters[i] = new Path(exclString);
				}
				break;
			default:
				break;
			}
			result[k] = createEntry(entry.getKind(), names[k], value, exclusionFilters, entry.getFlags() | ICSettingEntry.RESOLVED, srcPath, srcRootPath, srcPrefixMapping);
		}
		return result;
	}

	private static IPath resolvePath(ICdtVariableManager mngr, ICConfigurationDescription cfg, IPath path) {
		if (path == null)
			return null;

		try {
			String unresolved = path.toString();
			String resolved = mngr.resolveValue(unresolved, EMPTY, DELIM, cfg);
			if (resolved != null && !resolved.equals(unresolved))
				path = new Path(resolved);
		} catch (CdtVariableException e) {
			CCorePlugin.log(e);
		}

		return path;
	}

	/**
	 * Return entry cached in setting entries pool to optimize for memory usage.
	 * Note that the pool will handle garbage collection for unreferenced entries.
	 *
	 * @since 5.4
	 */
	@SuppressWarnings("unchecked")
	public static <T extends ICSettingEntry> T getPooledEntry(T entry) {
		return (T) settingEntriesPool.add(entry);
	}

	/**
	 * Convenience method to clone {@link ICLanguageSettingEntry} with modified flags.
	 * Note that this method keeps the entries in the pool to avoid proliferation of duplicates.
	 *
	 * @param entry - source entry.
	 * @param flagsToAdd - binary combination of bits to add to the flags.
	 * @param flafsToClear -  binary combination of bits to clear in the flags.
	 * @return new entry with the modified flags.
	 */
	public static ICLanguageSettingEntry createEntry(ICLanguageSettingEntry entry, int flagsToAdd, int flafsToClear) {
		return createEntry(entry, (entry.getFlags() | flagsToAdd) & (~flafsToClear));
	}

	/**
	 * Convenience method to clone {@link ICLanguageSettingEntry} with different flags.
	 * Note that this method keeps the entries in the pool to avoid proliferation of duplicates.
	 *
	 * @param entry - source entry.
	 * @param flags - new flags.
	 * @return new entry with the specified flags.
	 */
	public static ICLanguageSettingEntry createEntry(ICLanguageSettingEntry entry, int flags) {
		switch (entry.getKind()) {
		case ICSettingEntry.INCLUDE_PATH:
			entry = new CIncludePathEntry(entry.getName(), flags);
			break;
		case ICSettingEntry.MACRO:
			entry = new CMacroEntry(entry.getName(), entry.getValue(), flags);
			break;
		case ICSettingEntry.INCLUDE_FILE:
			entry = new CIncludeFileEntry(entry.getName(), flags);
			break;
		case ICSettingEntry.MACRO_FILE:
			entry = new CMacroFileEntry(entry.getName(), flags);
			break;
		case ICSettingEntry.LIBRARY_PATH:
			entry = new CLibraryPathEntry(entry.getName(), flags);
			break;
		case ICSettingEntry.LIBRARY_FILE:
			ICLibraryFileEntry libFile = (ICLibraryFileEntry)entry;
			entry = new CLibraryFileEntry(entry.getName(),
					flags,
					libFile.getSourceAttachmentPath(),
					libFile.getSourceAttachmentRootPath(),
					libFile.getSourceAttachmentPrefixMapping()
					);
			break;
		}
		return getPooledEntry(entry);
	}

	/**
	 * Convenience method to create {@link ICSettingEntry} depending on kind.
	 * Note that this method keeps the entries in the pool to avoid proliferation of duplicates.
	 */
	public static ICSettingEntry createEntry(int kind, String name, String value, IPath[] exclusionPatterns, int flags) {
		return createEntry(kind, name, value, exclusionPatterns, flags, null, null, null);
	}

	/**
	 * Convenience method to create {@link ICSettingEntry} depending on kind.
	 * Note that this method keeps the entries in the pool to avoid proliferation of duplicates.
	 */
	public static ICSettingEntry createEntry(int kind, String name, String value, IPath[] exclusionPatterns, int flags, IPath sourceAttachmentPath, IPath sourceAttachmentRootPath, IPath sourceAttachmentPrefixMapping) {
		ICSettingEntry entry = null;
		switch (kind) {
		case ICLanguageSettingEntry.INCLUDE_PATH:
			entry = new CIncludePathEntry(name, flags);
			break;
		case ICLanguageSettingEntry.MACRO:
			entry = new CMacroEntry(name, value, flags);
			break;
		case ICLanguageSettingEntry.INCLUDE_FILE:
			entry = new CIncludeFileEntry(name, flags);
			break;
		case ICLanguageSettingEntry.MACRO_FILE:
			entry = new CMacroFileEntry(name, flags);
			break;
		case ICLanguageSettingEntry.LIBRARY_PATH:
			entry = new CLibraryPathEntry(name, flags);
			break;
		case ICLanguageSettingEntry.LIBRARY_FILE:
			entry = new CLibraryFileEntry(name, flags, sourceAttachmentPath, sourceAttachmentRootPath, sourceAttachmentPrefixMapping);
			break;
		case ICLanguageSettingEntry.OUTPUT_PATH:
			entry = new COutputEntry(name, exclusionPatterns, flags);
			break;
		case ICLanguageSettingEntry.SOURCE_PATH:
			entry = new CSourceEntry(name, exclusionPatterns, flags);
			break;
		default:
			throw new IllegalArgumentException();
		}
		return getPooledEntry(entry);
	}

	/**
	 * Utility method to create {@link CIncludePathEntry}.
	 * Note that this method keeps the entries in the pool to avoid proliferation of duplicates.
	 *
	 * @since 5.4
	 */
	public static CIncludePathEntry createCIncludePathEntry(String name, int flags) {
		return getPooledEntry(new CIncludePathEntry(name, flags));
	}

	/**
	 * Utility method to create {@link CIncludeFileEntry}.
	 * Note that this method keeps the entries in the pool to avoid proliferation of duplicates.
	 *
	 * @since 5.4
	 */
	public static CIncludeFileEntry createCIncludeFileEntry(String name, int flags) {
		return getPooledEntry(new CIncludeFileEntry(name, flags));
	}

	/**
	 * Utility method to create {@link CMacroEntry}.
	 * Note that this method keeps the entries in the pool to avoid proliferation of duplicates.
	 *
	 * @since 5.4
	 */
	public static CMacroEntry createCMacroEntry(String name, String value, int flags) {
		return getPooledEntry(new CMacroEntry(name, value, flags));
	}

	/**
	 * Utility method to create {@link CMacroFileEntry}.
	 * Note that this method keeps the entries in the pool to avoid proliferation of duplicates.
	 *
	 * @since 5.4
	 */
	public static CMacroFileEntry createCMacroFileEntry(String name, int flags) {
		return getPooledEntry(new CMacroFileEntry(name, flags));
	}

	/**
	 * Utility method to create {@link CLibraryPathEntry}.
	 * Note that this method keeps the entries in the pool to avoid proliferation of duplicates.
	 *
	 * @since 5.4
	 */
	public static CLibraryPathEntry createCLibraryPathEntry(String name, int flags) {
		return getPooledEntry(new CLibraryPathEntry(name, flags));
	}

	/**
	 * Utility method to create {@link CLibraryFileEntry}.
	 * Note that this method keeps the entries in the pool to avoid proliferation of duplicates.
	 *
	 * @since 5.4
	 */
	public static CLibraryFileEntry createCLibraryFileEntry(String name, int flags) {
		return getPooledEntry(new CLibraryFileEntry(name, flags));
	}

	public static String[] getSourceExtensions(IProject project, CLanguageData data) {
		String[] exts = null;
		String[] typeIds = data.getSourceContentTypeIds();
		if (typeIds != null && typeIds.length != 0) {
			exts = getExtensionsFromContentTypes(project, typeIds);
		} else {
			exts = data.getSourceExtensions();
			if (exts != null && exts.length != 0)
				exts = exts.clone();
			else
				exts = CDefaultLanguageData.EMPTY_STRING_ARRAY;
		}

		if (exts == null)
			exts = CDefaultLanguageData.EMPTY_STRING_ARRAY;
		return exts;
	}

	public static String[] getExtensionsFromContentTypes(IProject project, String[] typeIds) {
		String[] exts = null;
		if (typeIds != null && typeIds.length != 0) {
			IContentTypeManager manager = Platform.getContentTypeManager();
			IContentType type;
			if (typeIds.length == 1) {
				type = manager.getContentType(typeIds[0]);
				if (type != null)
					exts = getContentTypeFileSpecs(project, type);
			} else {
				List<String> list = new ArrayList<>();
				for (int i = 0; i < typeIds.length; i++) {
					type = manager.getContentType(typeIds[i]);
					if (type != null) {
						list.addAll(Arrays.asList(getContentTypeFileSpecs(project, type)));
					}
				}
				exts = list.toArray(new String[list.size()]);
			}
		}

		if (exts == null)
			exts = CDefaultLanguageData.EMPTY_STRING_ARRAY;
		return exts;
	}

	public static String[] getContentTypeFileSpecs (IProject project, IContentType type) {
		String[] globalSpecs = type.getFileSpecs(IContentType.FILE_EXTENSION_SPEC);
		IContentTypeSettings settings = null;
		if (project != null) {
			IScopeContext projectScope = new ProjectScope(project);
			try {
				settings = type.getSettings(projectScope);
			} catch (Exception e) {}
			if (settings != null) {
				String[] specs = settings.getFileSpecs(IContentType.FILE_EXTENSION_SPEC);
				if (specs.length > 0) {
					int total = globalSpecs.length + specs.length;
					String[] projSpecs = new String[total];
					int i=0;
					for (int j=0; j<specs.length; j++) {
						projSpecs[i] = specs[j];
						i++;
					}
					for (int j=0; j<globalSpecs.length; j++) {
						projSpecs[i] = globalSpecs[j];
						i++;
					}
					return projSpecs;
				}
			}
		}
		return globalSpecs;
	}

	public static CLanguageData findLanguagDataForFile(String fileName, IProject project, CFolderData fData) {
		return findLanguagDataForFile(fileName, project, fData.getLanguageDatas());
	}

	public static CLanguageData findLanguagDataForFile(String fileName, IProject project, CLanguageData datas[]) {
		//	if (cType != null) {
		//		setting = findLanguageSettingForContentTypeId(cType.getId(), settings, true);
		//		if (setting == null)
		//			setting = findLanguageSettingForContentTypeId(cType.getId(), settings, false);
		//	}
			CLanguageData data = null;
			int index = fileName.lastIndexOf('.');
			if (index > 0) {
				String ext = fileName.substring(index + 1).trim();
				if (ext.length() > 0) {
					data = findLanguageDataForExtension(ext, datas);
				}
			}
			return data;
		}

	public static CLanguageData findLanguageDataForExtension(String ext, CLanguageData datas[]/*, boolean src*/) {
		CLanguageData data;
		for (int i = 0; i < datas.length; i++) {
			data = datas[i];
			String exts[] = data.getSourceExtensions();
/*			if (src) {
				if (setting.getSourceContentType() == null) {
					exts = setting.getSourceExtensions();
				}
			} else {
				if (setting.getHeaderContentType() == null) {
					exts = setting.getHeaderExtensions();
				}
			}
*/
			if (exts != null && exts.length != 0) {
				for (int j = 0; j < exts.length; j++) {
					if (ext.equals(exts[j]))
						return data;
				}
			}
		}
		return null;
	}

	public static Map<IPath, CResourceData> createPathRcDataMap(CConfigurationData data) {
		Map<IPath, CResourceData> map = new HashMap<>();
		CResourceData[] rcDatas = data.getResourceDatas();
		CResourceData rcData;
		for (int i = 0; i < rcDatas.length; i++) {
			rcData = rcDatas[i];
			map.put(rcData.getPath(), rcData);
		}
		return map;
	}

	public static PathSettingsContainer createRcDataHolder(CConfigurationData data) {
		PathSettingsContainer h = PathSettingsContainer.createRootContainer();

		h.setValue(data.getRootFolderData());
		CResourceData[] rcDatas = data.getResourceDatas();
		CResourceData rcData;
		PathSettingsContainer child;
		for (int i = 0; i < rcDatas.length; i++) {
			rcData = rcDatas[i];
			child = h.getChildContainer(rcData.getPath(), true, true);
			child.setValue(rcData);
		}
		return h;
	}

	public static CConfigurationData createEmptyData(String id, String name, CDataFactory factory, boolean performLangAdjustment) {
		if (id == null)
			id = genId(null);

		CConfigurationData data = factory.createConfigurationdata(id, name, null, false);
		if (data.getRootFolderData() == null) {
			CFolderData foData = factory.createFolderData(data, null, genId(data.getId()), false, Path.EMPTY);
			factory.link(data, foData);
		}

		if (data.getBuildData() == null) {
			CBuildData bData = factory.createBuildData(data, null, genId(data.getId()), null, false);
			factory.link(data, bData);
		}

		if (data.getTargetPlatformData() == null) {
			CTargetPlatformData tpData = factory.createTargetPlatformData(data, null, genId(data.getId()), null, false);
			factory.link(data, tpData);
		}

		if (performLangAdjustment)
			adjustConfig(data, factory);

		return data;
	}

	public static CConfigurationData adjustConfig(CConfigurationData cfg, CDataFactory factory) {
		LanguageManager mngr = LanguageManager.getInstance();
		ILanguageDescriptor dess[] = mngr.getLanguageDescriptors();
		Map<String, ILanguageDescriptor[]> map = mngr.getContentTypeIdToLanguageDescriptionsMap();

		CResourceData[] rcDatas = cfg.getResourceDatas();
		for (int i = 0; i < rcDatas.length; i++) {
			if (rcDatas[i].getType() == ICSettingBase.SETTING_FOLDER) {
				adjustFolderData(cfg, (CFolderData)rcDatas[i], factory, dess, new HashMap<>(map));
			}
		}

		return cfg;
	}


	private static void adjustFolderData(CConfigurationData cfgData, CFolderData data, CDataFactory factory, ILanguageDescriptor dess[], HashMap<String, ILanguageDescriptor[]> map) {
		Map<String, ILanguageDescriptor> langMap = new HashMap<>();
		for (int i = 0; i < dess.length; i++) {
			langMap.put(dess[i].getId(), dess[i]);
		}
		CLanguageData lDatas[] = data.getLanguageDatas();
		for (int i = 0; i < lDatas.length; i++) {
			CLanguageData lData = lDatas[i];
			String langId = lData.getLanguageId();
			if (langId != null) {
				ILanguageDescriptor des = langMap.remove(langId);
				adjustLanguageData(data, lData, des);
						continue;
			} else {
				String[] cTypeIds = lData.getSourceContentTypeIds();
				for (int c = 0; c < cTypeIds.length; c++) {
					String cTypeId = cTypeIds[c];
					ILanguageDescriptor[] langs = map.remove(cTypeId);
					if (langs != null && langs.length != 0) {
						for (int q = 0; q < langs.length; q++) {
							langMap.remove(langs[q].getId());
						}

						adjustLanguageData(data, lData, langs[0]);
					}
				}
			}
		}

		if (!langMap.isEmpty()) {
			addLangs(cfgData, data, factory, langMap, map);
		}

	}

	private static CLanguageData adjustLanguageData(CFolderData data, CLanguageData lData, ILanguageDescriptor des) {
		String [] cTypeIds = des.getContentTypeIds();
		String srcIds[] = lData.getSourceContentTypeIds();

		Set<String> landTypes = new HashSet<>(Arrays.asList(cTypeIds));
		landTypes.removeAll(Arrays.asList(srcIds));

		if (landTypes.size() != 0) {
			List<String> srcList = new ArrayList<>();
			srcList.addAll(landTypes);
			lData.setSourceContentTypeIds(srcList.toArray(new String[srcList.size()]));
		}

		if (!des.getId().equals(lData.getLanguageId())) {
			lData.setLanguageId(des.getId());
		}
		return lData;
	}

	private static void addLangs(CConfigurationData cfgData, CFolderData data, CDataFactory factory, Map<String, ILanguageDescriptor> langMap, Map<String, ILanguageDescriptor[]> cTypeToLangMap) {
		List<ILanguageDescriptor> list = new ArrayList<>(langMap.values());
		ILanguageDescriptor des;
		while(list.size() != 0) {
			des = list.remove(list.size() - 1);
			String[] ctypeIds = des.getContentTypeIds();
			boolean addLang = false;
			for (int i = 0; i < ctypeIds.length; i++) {
				ILanguageDescriptor[] langs = cTypeToLangMap.remove(ctypeIds[i]);
				if (langs != null && langs.length != 0) {
					addLang = true;
					for (int q = 0; q < langs.length; q++) {
						list.remove(langs[q]);
					}
				}
			}

			if (addLang) {
				CLanguageData lData = factory.createLanguageData(cfgData, data, genId(data.getId()), des.getName(), des.getId(),
						ICSettingEntry.INCLUDE_FILE
						| ICSettingEntry.INCLUDE_PATH
						| ICSettingEntry.MACRO
						| ICSettingEntry.MACRO_FILE,
						ctypeIds, true);
				factory.link(data, lData);
			}
		}
	}

	public static boolean isExcluded(IPath path, ICSourceEntry[] entries) {
		for (int i = 0; i < entries.length; i++) {
			if (!isExcluded(path, entries[i]))
				return false;
		}
		return true;
	}

	public static boolean isExcluded(IPath path, ICSourceEntry entry) {
		IPath entryPath = new Path(entry.getName());

		if (path.isPrefixOf(entryPath))
			return false;

		if (!entryPath.isPrefixOf(path))
			return true;

		if (path.segmentCount() == 0)
			return false;
		char[][] exclusions = entry.fullExclusionPatternChars();
		return CoreModelUtil.isExcluded(path, exclusions);
	}

	public static boolean isOnSourceEntry(IPath path, ICSourceEntry entry) {
		IPath entryPath = new Path(entry.getName());

		if (path.equals(entryPath))
			return true;

		if (!entryPath.isPrefixOf(path))
			return false;

		if (path.segmentCount() == 0)
			return true;
		char[][] exclusions = entry.fullExclusionPatternChars();
		return !CoreModelUtil.isExcluded(path, exclusions);
	}

	public static boolean canExclude(IPath path, boolean isFolder, boolean excluded, ICSourceEntry[] entries) {
		try {
			ICSourceEntry[] out = setExcluded(path, isFolder, excluded, entries, false);
			return !isEqual(entries, out);
		} catch (CoreException e) {	}
		return false;
	}

	/**
	 *
	 * @param ein - initial source entries
	 * @param aus - resulting source entries
	 * @return - true if they are equal
	 */
	public static boolean isEqual(ICSourceEntry[] ein, ICSourceEntry[] aus) {
		if (ein == null || aus == null)
			return (ein == null && aus == null);
		if (ein.length != aus.length)
			return false;
		for (int i= 0; i < ein.length; i++) {
			boolean found = false;
			for (int j=0; j<aus.length; j++) {
				if (!ein[i].equalsByName(aus[j]))
					continue;
				if (ein[i].equalsByContents(aus[j])) {
					found = true;
					break;
				}
				return false; // contents is changed !
			}
			if (!found)
				return false; // name is not found !
		}
		return true; // all entries are equal by name and contents
	}

	public static ICSourceEntry[] setExcluded(IPath path, boolean isFolder, boolean excluded, ICSourceEntry[] entries) throws CoreException {
		return setExcluded(path, isFolder, excluded, entries, true);
	}

	public static ICSourceEntry[] setExcludedIfPossible(IPath path, boolean isFolder, boolean excluded, ICSourceEntry[] entries) {
		try {
			ICSourceEntry[] newEntries = setExcluded(path, isFolder, excluded, entries, false);
			if (newEntries == null)
				newEntries = entries;
			return newEntries;
		} catch (CoreException e) {
		}
		return entries;
	}

	public static ICSourceEntry[] setExcluded(IPath path, boolean isFolder, boolean excluded, ICSourceEntry[] entries, boolean throwExceptionOnErr) throws CoreException {
		if (isExcluded(path, entries) == excluded)
			return entries;

		ICSourceEntry[] newEntries;
		if (excluded) {
			List<ICSourceEntry> includeList = new ArrayList<>(entries.length);
			List<ICSourceEntry> excludeList = new ArrayList<>(entries.length);

			sortEntries(path, false, entries, includeList, excludeList);

			for (int i = 0; i < includeList.size(); i++) {
				ICSourceEntry oldEntry = includeList.get(i);
				List<IPath> tmp = new ArrayList<IPath>(1);
				tmp.add(path);
				ICSourceEntry newEntry = addExcludePaths(oldEntry, tmp, true);
				if (newEntry != null)
					excludeList.add(newEntry);
			}

			newEntries = excludeList.toArray(new ICSourceEntry[excludeList.size()]);
		} else {
			List<ICSourceEntry> includeList = new ArrayList<>(entries.length + 1);
			List<ICSourceEntry> excludeList = new ArrayList<>(entries.length);

			sortIncludingExcludingEntries(path, entries, includeList, excludeList);
			boolean included = false;
			if (includeList.size() != 0) {
				if (includeExclusion(path, includeList) >= 0)
					included = true;
			}

			if (!included) {
				if (isFolder) {
					includeList.add(new CSourceEntry(path, null, ICSettingEntry.VALUE_WORKSPACE_PATH | ICSettingEntry.RESOLVED));
				} else {
					if (throwExceptionOnErr)
						throw ExceptionFactory.createCoreException("can not create a source entry for individual file"); //$NON-NLS-1$
					return null;
				}
			}

			includeList.addAll(excludeList);
			newEntries = includeList.toArray(new ICSourceEntry[includeList.size()]);
		}

		return newEntries;
	}

	private static int includeExclusion(IPath path, List<ICSourceEntry> entries) {
		for (int i = 0; i < entries.size(); i++) {
			ICSourceEntry entry = entries.get(i);
			entry = include(path, entry);
			if (entry != null) {
				entries.set(i, entry);
				return i;
			}
		}
		return -1;
	}

	private static ICSourceEntry include(IPath path, ICSourceEntry entry) {
		IPath[] exclusions = entry.getExclusionPatterns();
		IPath entryPath = new Path(entry.getName());
		IPath relPath = path.removeFirstSegments(entryPath.segmentCount()).makeRelative();
		for (int k = 0; k < exclusions.length; k++) {
			if (exclusions[k].equals(relPath)) {
				IPath updatedExclusions[] = new IPath[exclusions.length - 1];
				System.arraycopy(exclusions, 0, updatedExclusions, 0, k);
				System.arraycopy(exclusions, k + 1, updatedExclusions, k, updatedExclusions.length - k);
				ICSourceEntry updatedEntry = new CSourceEntry(entry.getName(), updatedExclusions, entry.getFlags());
				if (isOnSourceEntry(path, updatedEntry))
					return updatedEntry;
				exclusions = updatedExclusions;
				entry = updatedEntry;
			}
		}
		return null;
	}

	private static void sortIncludingExcludingEntries(IPath path, ICSourceEntry[] entries, List<ICSourceEntry> including, List<ICSourceEntry> excluding) {
		for (int i = 0; i < entries.length; i++) {
			IPath entryPath = new Path(entries[i].getName());
			if (entryPath.isPrefixOf(path)) {
				including.add(entries[i]);
			} else {
				excluding.add(entries[i]);
			}
		}
	}

	public static ICSourceEntry[] adjustEntries(ICSourceEntry entries[]) {
		return adjustEntries(entries, false, null);
	}

	private static ICSourceEntry[] getDefaultSourceEntries(boolean absolute, IProject project) {
		ICSourceEntry entry;
		if (absolute) {
			if (project != null) {
				entry = new CSourceEntry(project.getFullPath(), null, ICSettingEntry.VALUE_WORKSPACE_PATH | ICSettingEntry.RESOLVED);
			} else {
				entry = new CSourceEntry(Path.EMPTY, null, ICSettingEntry.VALUE_WORKSPACE_PATH | ICSettingEntry.RESOLVED);
			}
		} else {
			entry = new CSourceEntry(Path.EMPTY, null, ICSettingEntry.VALUE_WORKSPACE_PATH | ICSettingEntry.RESOLVED);
		}
		return new ICSourceEntry[] { entry };
	}

	private static ICOutputEntry[] getDefaultOutputEntries(boolean absolute, IProject project) {
		ICOutputEntry entry;
		if (absolute) {
			if (project != null) {
				entry = new COutputEntry(project.getFullPath(), null, ICSettingEntry.VALUE_WORKSPACE_PATH | ICSettingEntry.RESOLVED);
			} else {
				entry = new COutputEntry(Path.EMPTY, null, ICSettingEntry.VALUE_WORKSPACE_PATH | ICSettingEntry.RESOLVED);
			}
		} else {
			entry = new COutputEntry(Path.EMPTY, null, ICSettingEntry.VALUE_WORKSPACE_PATH | ICSettingEntry.RESOLVED);
		}
		return new ICOutputEntry[] { entry };
	}

	public static ICOutputEntry[] adjustEntries(ICOutputEntry entries[], boolean makeAbsolute, IProject project) {
		if (entries == null || entries.length == 0)
			return getDefaultOutputEntries(makeAbsolute, project);

		return makeAbsolute ? makeAbsolute(project, entries) : makeRelative(project, entries);
	}

	public static ICSourceEntry[] adjustEntries(ICSourceEntry entries[], boolean makeAbsolute, IProject project) {
		if (entries == null || entries.length == 0)
			return getDefaultSourceEntries(makeAbsolute, project);

		ICSourceEntry ei, ej;
		LinkedHashMap<ICSourceEntry, List<IPath>> map = new LinkedHashMap<>();
		for (int i = 0; i < entries.length; i++) {
			ei = entries[i];
			List<IPath> list = null;
			for (int j = 0; j < entries.length; j++) {
				ej = entries[j];
				if (ei == ej)
					continue;

				IPath ejPath = new Path(ej.getName());
				if (!isExcluded(ejPath, ei)) {
					if (list == null)
						list = new ArrayList<>();
					list.add(ejPath);
				}
			}

			map.put(ei, list);
		}
		List<ICSourceEntry> resultList = new ArrayList<>(entries.length);
		for (Iterator<Map.Entry<ICSourceEntry, List<IPath>>> iter = map.entrySet().iterator(); iter.hasNext();) {
			Map.Entry<ICSourceEntry, List<IPath>> entry = iter.next();
			List<IPath> list = entry.getValue();
			if (list == null) {
				resultList.add(entry.getKey());
			} else {
				ICSourceEntry se = entry.getKey();
				se = addExcludePaths(se, list, true);
				if (se != null)
					resultList.add(se);
			}
		}

		if (makeAbsolute) {
			if (project != null)
				resultList = makeAbsolute(project, resultList);
		} else {
			resultList = makeRelative(project, resultList);
		}

		ICSourceEntry[] resultArray = resultList.toArray(new ICSourceEntry[resultList.size()]);
		Arrays.sort(resultArray, new Comparator<ICSourceEntry>() {
			@Override
			public int compare(ICSourceEntry o1, ICSourceEntry o2) {
				return o1.getFullPath().toString().compareTo(o2.getFullPath().toString());
			}
		});

		return resultArray;
	}

	private static List<ICSourceEntry> makeRelative(IProject project, List<ICSourceEntry> list) {
		int size = list.size();

		for (int i = 0; i < size; i++) {
			list.set(i, makeRelative(project, list.get(i)));
		}
		return list;
	}

	private static List<ICSourceEntry> makeAbsolute(IProject project, List<ICSourceEntry> list) {
		int size = list.size();

		for (int i = 0; i < size; i++) {
			list.set(i, makeAbsolute(project, list.get(i)));
		}
		return list;
	}

	public static ICSourceEntry makeAbsolute(IProject project, ICSourceEntry entry) {
		return (ICSourceEntry)makeAbsolute(project, entry, true);
	}

	public static ICSourceEntry makeRelative(IProject project, ICSourceEntry entry) {
		return (ICSourceEntry)makeRelative(project, entry, true);
	}

	public static ICSourceEntry[] makeRelative(IProject project, ICSourceEntry[] entries) {
		return (ICSourceEntry[])makeRelative(project, entries, true);
	}

	public static ICSourceEntry[] makeAbsolute(IProject project, ICSourceEntry[] entries) {
		return (ICSourceEntry[])makeAbsolute(project, entries, true);
	}

	public static ICOutputEntry makeAbsolute(IProject project, ICOutputEntry entry) {
		return (ICOutputEntry)makeAbsolute(project, entry, true);
	}

	public static ICOutputEntry makeRelative(IProject project, ICOutputEntry entry) {
		return (ICOutputEntry)makeRelative(project, entry, true);
	}

	public static ICOutputEntry[] makeAbsolute(IProject project, ICOutputEntry[] entries) {
		return (ICOutputEntry[])makeAbsolute(project, entries, true);
	}

	public static ICOutputEntry[] makeRelative(IProject project, ICOutputEntry[] entries) {
		return (ICOutputEntry[])makeRelative(project, entries, true);
	}

	private static Collection<IPath> removePrefix(IPath prefix, Collection<IPath> paths, Collection<IPath> result) {
		if (result == null)
			result = new ArrayList<>(paths.size());
		for (Iterator<IPath> iter = paths.iterator(); iter.hasNext(); ) {
			IPath path = iter.next();
			if (prefix.isPrefixOf(path))
				result.add(path.removeFirstSegments(prefix.segmentCount()));
//			else
//				result.add(path);
		}
		return result;
	}

	public static ICSourceEntry addExcludePaths(ICSourceEntry entry, Collection<IPath> paths, boolean removePrefix) {
		IPath entryPath = new Path(entry.getName());
		IPath[] oldExclusions = entry.getExclusionPatterns();
//		List newExList = new ArrayList(oldExclusions.length + paths.size());
		LinkedHashSet<IPath> newSet = new LinkedHashSet<>();
		if (removePrefix) {
			removePrefix(entryPath, paths, newSet);
		} else {
			newSet.addAll(paths);
		}

		for (Iterator<IPath> iter = newSet.iterator(); iter.hasNext();) {
			IPath path = iter.next();
			if (path.segmentCount() == 0)
				return null;
		}

		newSet.addAll(Arrays.asList(oldExclusions));

		IPath[] newExclusions = newSet.toArray(new IPath[newSet.size()]);
		return new CSourceEntry(entry.getName(), newExclusions, entry.getFlags());
	}

	private static void sortEntries(IPath path, boolean byExclude, ICSourceEntry[] entries, List<ICSourceEntry> included, List<ICSourceEntry> excluded) {
		for (int i = 0; i < entries.length; i++) {
			if (byExclude ? isExcluded(path, entries[i]) : !isOnSourceEntry(path, entries[i])) {
				if (excluded != null)
					excluded.add(entries[i]);
			} else {
				if (included != null)
					included.add(entries[i]);
			}
		}
	}

	public static Map<EntryNameKey, ICSettingEntry> fillEntriesMapByNameKey(Map<EntryNameKey, ICSettingEntry> map, ICSettingEntry[] entries) {
		if (map == null)
			map = new LinkedHashMap<>();

		for (int i = 0; i < entries.length; i++) {
			ICSettingEntry entry = entries[i];
			map.put(new EntryNameKey(entry), entry);
		}
		return map;
	}

	public static Map<EntryContentsKey, ICSettingEntry> fillEntriesMapByContentsKey(Map<EntryContentsKey, ICSettingEntry> map, ICSettingEntry[] entries) {
		if (map == null)
			map = new LinkedHashMap<>();

		for (int i = 0; i < entries.length; i++) {
			ICSettingEntry entry = entries[i];
			map.put(new EntryContentsKey(entry), entry);
		}
		return map;
	}

	public static boolean getBoolean(ICStorageElement el, String attr, boolean defaultValue) {
		if (el != null) {
			String tmp = el.getAttribute(attr);
			if (tmp != null) {
				return Boolean.valueOf(tmp).booleanValue();
			}
		}
		return defaultValue;
	}

	public static void setBoolean(ICStorageElement el, String attr, boolean value) {
		el.setAttribute(attr, Boolean.valueOf(value).toString());
	}

	public static int getInteger(ICStorageElement el, String attr, int defaultValue) {
		if (el != null) {
			String tmp = el.getAttribute(attr);
			if (tmp != null) {
				try {
					return Integer.parseInt(tmp);
				} catch (NumberFormatException e) {
				}
			}
		}
		return defaultValue;
	}

	public static void setInteger(ICStorageElement el, String attr, int value) {
		el.setAttribute(attr, new Integer(value).toString());
	}

	public static ICExclusionPatternPathEntry addRemoveExclusionsToEntry(ICExclusionPatternPathEntry entry, IPath[] paths, boolean add) throws IllegalArgumentException{
		if (paths == null || paths.length == 0)
			return entry;

		Set<IPath> set = mergeRemovingDups(entry.getExclusionPatterns(), paths, add);
		IPath exclusions[] = set.toArray(new IPath[set.size()]);

		return (ICExclusionPatternPathEntry)createEntry(entry.getKind(), entry.getName(), null, exclusions, entry.getFlags());
	}

	private static Set<IPath> mergeRemovingDups(IPath[] o1, IPath[] o2, boolean add) {
		LinkedHashSet<IPath> set = new LinkedHashSet<>();
		set.addAll(Arrays.asList(o1));
		if (add) {
			set.addAll(Arrays.asList(o2));
		} else {
			set.removeAll(Arrays.asList(o2));
		}
		return set;
	}

	public static ICExclusionPatternPathEntry makeAbsolute(IProject project, ICExclusionPatternPathEntry entry, boolean force) {
		if (!entry.isValueWorkspacePath() && !force)
			return entry;

		IPath path = new Path(entry.getName());
		IPath projPath = project.getFullPath();
		if (!path.isAbsolute() || (force && !projPath.isPrefixOf(path))) {
			path = projPath.append(path).makeAbsolute();
			return (ICExclusionPatternPathEntry) createEntry(entry.getKind(), path.toString(), null, entry.getExclusionPatterns(), entry.getFlags());
		}
		return entry;
	}

	public static ICExclusionPatternPathEntry makeRelative(IProject project, ICExclusionPatternPathEntry entry, boolean force) {
		if (!entry.isValueWorkspacePath() && !force)
			return entry;

		IPath path = new Path(entry.getName());
		IPath projPath = project.getFullPath();

		if (path.isAbsolute()) {
			if (projPath.isPrefixOf(path)) {
				path = path.removeFirstSegments(projPath.segmentCount()).makeRelative();
			} else if (force) {
				path = path.makeRelative();
			}
			return (ICExclusionPatternPathEntry) createEntry(entry.getKind(), path.toString(), null, entry.getExclusionPatterns(), entry.getFlags());
		}
		return entry;
	}

	public static ICExclusionPatternPathEntry[] makeRelative(IProject project, ICExclusionPatternPathEntry[] entries, boolean force) {
		if (entries == null)
			return null;

		ICExclusionPatternPathEntry[] relEntries = (ICExclusionPatternPathEntry[])Array.newInstance(entries.getClass().getComponentType(), entries.length);
		for (int i = 0; i < entries.length; i++) {
			relEntries[i] = makeRelative(project, entries[i], force);
		}
		return relEntries;
	}

	public static ICExclusionPatternPathEntry[] makeAbsolute(IProject project, ICExclusionPatternPathEntry[] entries, boolean force) {
		if (entries == null)
			return null;

		ICExclusionPatternPathEntry[] relEntries = (ICExclusionPatternPathEntry[])Array.newInstance(entries.getClass().getComponentType(), entries.length);
		for (int i = 0; i < entries.length; i++) {
			relEntries[i] = makeAbsolute(project, entries[i], force);
		}
		return relEntries;
	}
}
