/*
 * Copyright (c) OSGi Alliance (2017). All Rights Reserved.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.osgi.util.converter;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Dictionary;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * @author $Id: cd32783312f5decc14b345def7a895d41a727c46 $
 */
class MapDelegate<K, V> implements Map<K,V> {
	// not synchronized. Worst that can happen is that cloning is done more than
	// once, which is harmless.
	private volatile boolean		cloned	= false;
	private final ConvertingImpl	convertingImpl;
	Map<K,V>						delegate;

	private MapDelegate(ConvertingImpl converting, Map<K,V> del) {
		convertingImpl = converting;
		delegate = del;
	}

	static MapDelegate<String,Object> forBean(Object b, Class< ? > beanClass,
			ConvertingImpl converting) {
		return new MapDelegate<>(converting,
				new DynamicBeanFacade(b, beanClass, converting));
	}

	static <K, V> Map<K,V> forMap(Map<K,V> m, ConvertingImpl converting) {
		return new MapDelegate<>(converting,
				new DynamicMapFacade<>(m, converting));
	}

	static <K, V> MapDelegate<K,V> forDictionary(Dictionary<K,V> d,
			ConvertingImpl converting) {
		return new MapDelegate<>(converting,
				new DynamicDictionaryFacade<>(d, converting));
	}

	static MapDelegate<String,Object> forDTO(Object obj, Class< ? > dtoClass,
			ConvertingImpl converting) {
		return new MapDelegate<>(converting,
				new DynamicDTOFacade(obj, dtoClass, converting));
	}

	static MapDelegate<String,Object> forInterface(Object obj, Class< ? > intf,
			ConvertingImpl converting) {
		return new MapDelegate<>(converting,
				new DynamicInterfaceFacade(obj, intf, converting));
	}

	@Override
	public int size() {
		// Need to convert the entire map to get the size
		Set<Object> keys = new HashSet<>();

		Set<K> ks = delegate.keySet();
		for (K key : ks) {
			keys.add(getConvertedKey(key));
		}

		return keys.size();
	}

	@Override
	public boolean isEmpty() {
		return delegate.isEmpty();
	}

	@Override
	public boolean containsKey(Object key) {
		return keySet().contains(key);
	}

	@Override
	public boolean containsValue(Object value) {
		return values().contains(value);
	}

	@Override
	@SuppressWarnings("unchecked")
	public V get(Object key) {
		V val = null;
		if (internalKeySet().contains(key)) {
			val = delegate.get(key);
		}

		if (val == null) {
			key = findConvertedKey(internalKeySet(), key);
			val = delegate.get(key);
		}

		if (val == null)
			return null;
		else
			return (V) getConvertedValue(val);
	}

	private Object getConvertedKey(Object key) {
		return convertingImpl.convertMapKey(key);
	}

	private Object getConvertedValue(Object val) {
		return convertingImpl.convertMapValue(val);
	}

	private Object findConvertedKey(Set< ? > keySet, Object key) {
		for (Object k : keySet) {
			if (key.equals(k))
				return k;
		}

		for (Object k : keySet) {
			Object c = convertingImpl.converter.convert(k).to(key.getClass());
			if (c != null && c.equals(key))
				return k;
		}
		return key;
	}

	@Override
	public V put(K key, V value) {
		cloneDelegate();

		return delegate.put(key, value);
	}

	@Override
	public V remove(Object key) {
		cloneDelegate();

		return delegate.remove(key);
	}

	@Override
	public void putAll(Map< ? extends K, ? extends V> m) {
		cloneDelegate();

		delegate.putAll(m);
	}

	@Override
	public void clear() {
		cloned = true;
		delegate = new HashMap<>();
	}

	private Set<K> internalKeySet() {
		return delegate.keySet();
	}

	@SuppressWarnings("unchecked")
	@Override
	public Set<K> keySet() {
		Set<K> keys = new HashSet<>();
		for (Object key : internalKeySet()) {
			keys.add((K) getConvertedKey(key));
		}
		return keys;
	}

	@Override
	public Collection<V> values() {
		List<V> values = new ArrayList<>();
		for (Map.Entry<K,V> entry : entrySet()) {
			values.add(entry.getValue());
		}
		return values;
	}

	@Override
	@SuppressWarnings("unchecked")
	public Set<java.util.Map.Entry<K,V>> entrySet() {
		Set<Map.Entry<K,V>> result = new HashSet<>();
		for (Map.Entry< ? , ? > entry : delegate.entrySet()) {
			K key = (K) findConvertedKey(internalKeySet(), entry.getKey());
			V val = (V) getConvertedValue(entry.getValue());
			result.add(new MapEntry<K,V>(key, val));
		}
		return result;
	}

	@Override
	public boolean equals(Object o) {
		return delegate.equals(o);
	}

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

	private void cloneDelegate() {
		if (cloned) {
			return;
		} else {
			cloned = true;
			delegate = new HashMap<>(delegate);
		}
	}

	@Override
	public String toString() {
		return delegate.toString();
	}

	static class MapEntry<K, V> implements Map.Entry<K,V> {
		private final K	key;
		private final V	value;

		MapEntry(K k, V v) {
			key = k;
			value = v;
		}

		@Override
		public K getKey() {
			return key;
		}

		@Override
		public V getValue() {
			return value;
		}

		@Override
		public V setValue(V value) {
			throw new UnsupportedOperationException();
		}
	}
}
