/*******************************************************************************
 * Copyright (c) 2007, 2008 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 overrridden){
			fIsOverRidden = overrridden;
		}

		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 (SettingLevel[])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 = (Object[])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 = (EntryInfo)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();
		if (s1 == null && s2 == null)
			return true;
		if (s1 != null)
			return s1.equals(s2);
		else	
			return s2.equals(s1);
	}

	

}
