/*******************************************************************************
 * Copyright (c) 2007, 2010 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.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.eclipse.cdt.core.settings.model.ICLanguageSettingEntry;
import org.eclipse.cdt.core.settings.model.ICMacroEntry;
import org.eclipse.cdt.core.settings.model.ICSettingEntry;

public class SettingsSet {
	public static final int READ_ONLY = 1;
	public static final int WRITABLE = 1 << 1;

	private SettingLevel[] fLevels;

	public class SettingLevel {
		private int fFlagsToSet;
		private int fFlagsToClear;
		private boolean fIsReadOnly;
		private boolean fIsOverrideSupported;
		private LinkedHashMap<EntryNameKey, EntryInfo> fEntries;
		HashSet<String> fOverrideSet;
		private Object fContext;
		
		private SettingLevel() {
			fEntries = new LinkedHashMap<EntryNameKey, EntryInfo>();
		}
		
		public boolean isReadOnly() {
			return fIsReadOnly;
		}
		
		public void setReadOnly(boolean readOnly) {
			fIsReadOnly = readOnly;
		}

		public boolean isOverrideSupported() {
			return fIsOverrideSupported;
		}
		
		public void setOverrideSupported(boolean supported) {
			fIsOverrideSupported = supported;
		}

		public void setFlagsToSet(int flags) {
			fFlagsToSet = flags;
		}
		
		public boolean containsOverrideInfo() {
			return fOverrideSet != null;
		}

		public void setFlagsToClear(int flags) {
			fFlagsToClear = flags;
		}
		
		public int getFlagsToSet() {
			return fFlagsToSet;
		}
		
		public int getFlagsToClear() {
			return fFlagsToClear;
		}
		
		public int getFlags(int baseFlags) {
			return (baseFlags | fFlagsToSet) & (~fFlagsToClear);
		}
		
		@SuppressWarnings("unchecked")
		public Set<String> getOverrideSet() {
			if (fOverrideSet != null)
				return (HashSet<String>)fOverrideSet.clone();
			return new HashSet<String>();
		}

		public void addEntries(ICLanguageSettingEntry entries[]) {
			if (entries != null) {
				for (int i = 0; i < entries.length; i++) {
					addEntry(entries[i]);
				}
			}
		}

		public void addEntries(List<ICLanguageSettingEntry> list) {
			for (ICLanguageSettingEntry se : list)
				addEntry(se);
		}
		
		public void addEntry(ICLanguageSettingEntry entry) {
			addEntry(entry, null);
		}

		public void addEntry(ICLanguageSettingEntry entry, Object customInfo) {
			entry = CDataUtil.createEntry(entry, fFlagsToSet, fFlagsToClear);
			EntryInfo info = new EntryInfo(entry, customInfo);
			fEntries.put(info.getContentsKey(), info);
		}
		
		public void addOverrideName(String name) {
			if (fOverrideSet == null)
				fOverrideSet = new HashSet<String>();
			
			fOverrideSet.add(name);
		}
		
		public void addOverrideNameSet(Set<String> set) {
			if (set == null)
				return;
			if (fOverrideSet != null) {
				fOverrideSet.addAll(set);
			} else if (set.size() != 0) {
				fOverrideSet = new HashSet<String>(set);
			}
		}

		public void removeOverrideName(String name) {
			if (fOverrideSet == null)
				return;
			
			fOverrideSet.remove(name);
			
			if (fOverrideSet.size() == 0)
				fOverrideSet = null;
		}

		public void clear() {
			fEntries.clear();
			fOverrideSet = null;
		}
		
		public Map<EntryNameKey, EntryInfo> clearAndGetMap() {
			Map<EntryNameKey, EntryInfo> map = fEntries;
			fEntries = new LinkedHashMap<EntryNameKey, EntryInfo>();
			fOverrideSet = null;
			return map;
		}
		
		public EntryInfo[] getInfos() {
			return fEntries.values().toArray(new EntryInfo[fEntries.size()]);
		}
		
		public ICLanguageSettingEntry[] getEntries() {
			List<ICLanguageSettingEntry> list = getEntriesList(false);
			return list.toArray(new ICLanguageSettingEntry[list.size()]);
		}

		public ICLanguageSettingEntry[] getEntries(boolean includeOverridden) {
			List<ICLanguageSettingEntry> list = getEntriesList(includeOverridden);
			return list.toArray(new ICLanguageSettingEntry[list.size()]);
		}

		public List<ICLanguageSettingEntry> getEntriesList(boolean includeOverridden) {
			List<ICLanguageSettingEntry> list = new ArrayList<ICLanguageSettingEntry>();
			EntryInfo infos[] = getInfos();
			for (EntryInfo info : infos) {
				if (includeOverridden || !info.isOverridden())
					list.add(info.getEntry());
			}
			
			return list;
		}
		
		public Object getContext() {
			return fContext;
		}
		
		public void setContext(Object context) {
			fContext = context;
		}
	}
	
	public static class EntryInfo {
		private ICLanguageSettingEntry fEntry;
		private EntryNameKey fNameKey;
		private boolean fIsOverRidden;
		private Object fCustomInfo;

		private EntryInfo(ICLanguageSettingEntry entry, Object customInfo) {
			fEntry = entry;
			fCustomInfo = customInfo;
		}

		public EntryNameKey getContentsKey() {
			if (fNameKey == null) {
				fNameKey = new EntryNameKey(fEntry);
			}
			return fNameKey;
		}
		
		private void makeOverridden(boolean overridden) {
			fIsOverRidden = overridden;
		}

		public ICLanguageSettingEntry getEntry() {
			return fEntry;
		}

		public boolean isOverridden() {
			return fIsOverRidden;
		}

		public Object getCustomInfo() {
			return fCustomInfo;
		}
	}
	
	public SettingsSet(int num) {
		fLevels = new SettingLevel[num];
		for (int i = 0; i < num; i++) {
			fLevels[i] = new SettingLevel();
		}
	}
	
	public SettingLevel[] getLevels() {
		return fLevels.clone();
	}
	
	public void adjustOverrideState() {
		Set<String> set = new HashSet<String>();
		SettingLevel level;
		for (int i = 0; i < fLevels.length; i++) {
			level = fLevels[i];
			if (level.isOverrideSupported() && level.fOverrideSet != null)
				set.addAll(level.fOverrideSet);
			adjustOverrideState(fLevels[i], set);
		}
	}
	
	private void adjustOverrideState(SettingLevel level, Set<String> overridenSet) {
		for (EntryInfo info : level.getInfos()) {
			if (overridenSet.add(info.getEntry().getName())) {
				info.makeOverridden(false);
			} else {
				info.makeOverridden(true);
			}
		}
	}

	public ICLanguageSettingEntry[] getEntries() {
		return getEntries(READ_ONLY | WRITABLE);
	}

	public ICLanguageSettingEntry[] getEntries(int types) {
		adjustOverrideState();
		List<ICLanguageSettingEntry> entries = new ArrayList<ICLanguageSettingEntry>();
		for (SettingLevel sl : fLevels) {
			if (isCompatible(sl, types))
				getEntries(sl, entries);
		}
		return entries.toArray(new ICLanguageSettingEntry[entries.size()]);
	}
	
	private void getEntries(SettingLevel level, List<ICLanguageSettingEntry> list) {
		for (EntryInfo info : level.getInfos())
			if (!info.isOverridden())
				list.add(info.getEntry());
	}
	
	private boolean isCompatible(SettingLevel level, int types) {
		if ((types & READ_ONLY) == 0 && level.isReadOnly())
			return false;
		if ((types & WRITABLE) == 0 && !level.isReadOnly())
			return false;
		
		return true;
	}
	
	private int getWritableLevelNum() {
		for (int i = 0; i <fLevels.length; i++) {
			if (!fLevels[i].isReadOnly())
				return i;
		}
		
		return -1;
	}

	private int getOverrideLevelNum() {
		for (int i = 0; i <fLevels.length; i++) {
			if (fLevels[i].isOverrideSupported())
				return i;
		}
		
		return -1;
	}

	@SuppressWarnings("unchecked")
	public void applyEntries(ICLanguageSettingEntry[] entries) {
		HashMap<EntryNameKey, Object[]> map = getEntryLevelMap(WRITABLE | READ_ONLY);
		Map<EntryNameKey, Object[]> mapCopy = (HashMap<EntryNameKey, Object[]>)map.clone();
		Map<EntryNameKey, EntryInfo>[] clearedInfos = new Map [fLevels.length];
		
		for (int i = 0; i < fLevels.length; i++) {
			if (!fLevels[i].isReadOnly())
				clearedInfos[i] = fLevels[i].clearAndGetMap();
		}

		Integer levelInteger;
		int levelNum;
		ICLanguageSettingEntry entry;
		int writableLevel = getWritableLevelNum();
		SettingLevel level;
		
		for (int i = 0; i < entries.length; i++) {
			entry = entries[i];
			EntryNameKey key = new EntryNameKey(entry);
			Object[] o = map.get(key);

			
			if (o != null && valueMatches(entry, o[1])) {
				mapCopy.remove(key);
				levelInteger = (Integer)o[0];
				if (! entry.isBuiltIn()) // allow overwrite existing entry,
					levelInteger = null; // even with the same value
			} else {
				levelInteger = null;
			}

			levelNum = levelInteger != null ? levelInteger.intValue() : writableLevel;
			if (levelNum >= 0) {
				level = fLevels[levelNum];
				if (!level.isReadOnly()) {
					Map<EntryNameKey, EntryInfo> clearedInfo = clearedInfos[levelNum];
					Object customInfo = null;
					if (clearedInfo != null) {
						EntryInfo info = clearedInfo.get(key);
						if (info != null && entry.equalsByContents(info.getEntry()))
							customInfo = info.getCustomInfo();
					}
					level.addEntry(entry, customInfo);
				}
			}
		}
		
		int overrideLevel = getOverrideLevelNum();
		if (overrideLevel >= 0) {
			level = fLevels[overrideLevel];
			if (level.isOverrideSupported() && !mapCopy.isEmpty()) {
				for (EntryNameKey enk : mapCopy.keySet()) {
					ICSettingEntry e = enk.getEntry();
					if ((e.getFlags() & ICSettingEntry.BUILTIN) == 0)
						continue;
					String str = e.getName();
					if (str != null) 
						level.addOverrideName(str);
				}
			}
		}
		adjustOverrideState();
	}
	
	public HashMap<EntryNameKey, Object[]> getEntryLevelMap(int types) {
		HashMap<EntryNameKey, Object[]> map = new HashMap<EntryNameKey, Object[]>();
		for (int i = 0; i < fLevels.length; i++) {
			if (isCompatible(fLevels[i], types))
				addLevelInfoToMap(fLevels[i], i, map);
		}
		return map;
	}
	
	private void addLevelInfoToMap(SettingLevel level, int l, Map<EntryNameKey, Object[]> map) {
		for (EntryInfo info : level.getInfos()) {
			EntryNameKey key = info.getContentsKey(); 
			if (!map.containsKey(key))
				map.put(key, new Object[]{new Integer(l), info.getEntry()});
		}
	}
	
	private static boolean valueMatches(ICLanguageSettingEntry e, Object o) {
		if (!(e instanceof ICMacroEntry))
			return true; // ignore values for other entries
		if (!(o instanceof ICMacroEntry))
			return false; // cannot compare different entries
		String s1 = e.getValue();
		String s2 = ((ICMacroEntry) o).getValue();
		return s1 == null ? s2 == null : s1.equals(s2);
	}
}
