/*******************************************************************************
 * Copyright (c) 2007, 2011 Intel 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:
 * Intel Corporation - Initial API and implementation
 *******************************************************************************/
package org.eclipse.cdt.core.settings.model.util;

import java.util.AbstractCollection;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.NoSuchElementException;

import org.eclipse.cdt.core.model.CoreModelUtil;

public class PatternNameMap {
	private static final char[] SPEC_CHARS = new char[] { '*', '?' };
	static final String DOUBLE_STAR_PATTERN = "**"; //$NON-NLS-1$

	private Map<String, PathSettingsContainer> fChildrenMap;
	private Map<StringCharArray, PathSettingsContainer> fPatternMap;
	private Collection<PathSettingsContainer> fValues;
	private boolean fContainsDoubleStar;

	private static class StringCharArray {
		private String fString;
		private char[] fCharArray;

		StringCharArray(String string) {
			fString = string;
		}

		char[] getCharArray() {
			if (fCharArray == null) {
				fCharArray = fString.toCharArray();
			}
			return fCharArray;
		}

		@Override
		public boolean equals(Object obj) {
			if (obj == this)
				return true;

			if (!(obj instanceof StringCharArray))
				return false;

			return fString.equals(((StringCharArray) obj).fString);
		}

		@Override
		public int hashCode() {
			return fString.hashCode();
		}

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

	private class EmptyIterator implements Iterator<PathSettingsContainer> {

		@Override
		public boolean hasNext() {
			return false;
		}

		@Override
		public PathSettingsContainer next() {
			throw new NoSuchElementException();
		}

		@Override
		public void remove() {
			throw new IllegalStateException();
		}

	}

	private class ValuesCollection extends AbstractCollection<PathSettingsContainer> {

		private class Iter implements Iterator<PathSettingsContainer> {
			private Iterator<Entry<String, PathSettingsContainer>> fEntrySetIter;
			private Entry<String, PathSettingsContainer> fCur;

			Iter(Iterator<Entry<String, PathSettingsContainer>> entryIter) {
				this.fEntrySetIter = entryIter;
			}

			@Override
			public boolean hasNext() {
				return fEntrySetIter.hasNext();
			}

			@Override
			public PathSettingsContainer next() {
				fCur = fEntrySetIter.next();
				return fCur.getValue();
			}

			@Override
			public void remove() {
				fEntrySetIter.remove();
				String name = fCur.getKey();
				if (DOUBLE_STAR_PATTERN.equals(name)) {
					fContainsDoubleStar = false;
				} else {
					removePattern(name);
				}
			}
		}

		@Override
		public Iterator<PathSettingsContainer> iterator() {
			return fChildrenMap != null ? new Iter(fChildrenMap.entrySet().iterator()) : new EmptyIterator();
		}

		@Override
		public int size() {
			return PatternNameMap.this.size();
		}

		@Override
		public void clear() {
			PatternNameMap.this.clear();
		}

		@Override
		public boolean contains(Object o) {
			return fChildrenMap != null ? fChildrenMap.containsValue(o) : false;
		}
	}

	public /* PathSettingsContainer */ Object get(String name) {
		return fChildrenMap != null ? fChildrenMap.get(name) : null;
	}

	public int size() {
		return fChildrenMap != null ? fChildrenMap.size() : 0;
	}

	public boolean isEmpty() {
		return fChildrenMap == null || fChildrenMap.isEmpty();
	}

	public boolean hasPatterns() {
		return fContainsDoubleStar || hasPatternsMap();
	}

	public boolean hasPatternsMap() {
		return (fPatternMap != null && fPatternMap.size() != 0);
	}

	public List<PathSettingsContainer> getValues(String name) {
		if (fChildrenMap == null)
			return null;

		PathSettingsContainer val = fChildrenMap.get(name);
		if (hasPatternsMap()) {
			List<PathSettingsContainer> list;
			if (val != null) {
				list = new ArrayList<>(3);
				list.add(val);
			} else {
				list = null;
			}

			Map.Entry<PatternNameMap.StringCharArray, PathSettingsContainer> entry;
			StringCharArray strCA;
			char[] nameCharArray = name.toCharArray();
			for (Iterator<Map.Entry<PatternNameMap.StringCharArray, PathSettingsContainer>> iter = fPatternMap
					.entrySet().iterator(); iter.hasNext();) {
				entry = iter.next();
				strCA = entry.getKey();
				if (CoreModelUtil.match(strCA.getCharArray(), nameCharArray, true)) {
					if (list == null)
						list = new ArrayList<>(2);
					list.add(entry.getValue());
				}
			}
			return list;
		} else if (val != null) {
			List<PathSettingsContainer> list = new ArrayList<>(1);
			list.add(val);
			return list;
		}
		return null;
	}

	public boolean containsDoubleStar() {
		return fContainsDoubleStar;
	}

	public /* PathSettingsContainer */ Object put(String name, /* PathSettingsContainer */Object value) {
		return put(name, (PathSettingsContainer) value);
	}

	private PathSettingsContainer put(String name, PathSettingsContainer value) {
		if (value == null)
			return (PathSettingsContainer) remove(name);

		PathSettingsContainer oldValue;
		if (fChildrenMap == null) {
			fChildrenMap = new HashMap<>();
			oldValue = null;
		} else {
			oldValue = fChildrenMap.get(name);
		}

		fChildrenMap.put(name, value);

		if (DOUBLE_STAR_PATTERN.equals(name)) {
			fContainsDoubleStar = true;
		} else if (isPatternName(name)) {
			StringCharArray strCA = new StringCharArray(name);
			if (fPatternMap == null)
				fPatternMap = new HashMap<>();

			fPatternMap.put(strCA, value);
		}

		return oldValue;
	}

	public /* PathSettingsContainer */ Object remove(String name) {
		if (fChildrenMap != null) {
			PathSettingsContainer oldVal = fChildrenMap.remove(name);
			if (fChildrenMap.size() == 0) {
				fChildrenMap = null;
				fPatternMap = null;
				fContainsDoubleStar = false;
			} else if (DOUBLE_STAR_PATTERN.equals(name)) {
				fContainsDoubleStar = false;
			} else {
				removePattern(name);
			}
			return oldVal;
		}
		return null;
	}

	private void removePattern(String name) {
		if (fPatternMap != null) {
			fPatternMap.remove(new StringCharArray(name));
			if (fPatternMap.size() == 0)
				fPatternMap = null;
		}
	}

	private static boolean hasSpecChars(String str) {
		for (int i = 0; i < SPEC_CHARS.length; i++) {
			if (str.indexOf(SPEC_CHARS[i]) != -1)
				return true;
		}
		return false;
	}

	public static boolean isPatternName(String str) {
		//TODO: check escape chars
		return hasSpecChars(str);
	}

	public void clear() {
		fChildrenMap = null;
		fPatternMap = null;
		fContainsDoubleStar = false;
	}

	public Collection<PathSettingsContainer> values() {
		if (fValues == null)
			fValues = new ValuesCollection();
		return fValues;
	}
}
