/*
 * LazyHashMapMap.java - This file is part of the Jakstab project.
 * Copyright 2007-2015 Johannes Kinder <jk@jakstab.org>
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, see <http://www.gnu.org/licenses/>.
 */
package org.jakstab.util;

import java.util.*;

import org.jakstab.util.Logger;

/**
 * HashMapMap with lazy copying. I.e., only copies submaps if they are modified.
 * 
 * @author Johannes Kinder
 */
public final class LazyHashMapMap<K, L, V> implements MapMap<K,L,V> {

	@SuppressWarnings("unused")
	private static final Logger logger = Logger.getLogger(LazyHashMapMap.class);

	private static final class ReferenceCountingHashMapMap<M, N, W> extends HashMapMap<M, N, W> {

		private int referenceCount;
		private int hashCode = 0;
		
		public ReferenceCountingHashMapMap() {
			super();
			referenceCount = 1;
		}

		public ReferenceCountingHashMapMap(
				ReferenceCountingHashMapMap<M, N, W> innerMap) {
			super(innerMap);
			referenceCount = 1;
		}
		
		protected Map<N, W> createSubMap() {
			return new LazyTreeMap<N, W>();
		}
		
		protected Map<N, W> createSubMap(Map<N, W> proto) {
			return new LazyTreeMap<N, W>((LazyTreeMap<N, W>)proto);
		}
		
		public void addRef() {
			referenceCount++;
		}
		
		public void release() {
			referenceCount--;
		}
		
		public boolean isShared() {
			return referenceCount > 1;
		}

	}
	
	public LazyHashMapMap() {
		this.innerMap = new ReferenceCountingHashMapMap<K, L, V>();
	}
	
	public LazyHashMapMap(LazyHashMapMap<K, L, V> proto) {
		this.innerMap = proto.innerMap;
		this.innerMap.addRef();
	}

	private ReferenceCountingHashMapMap<K, L, V> innerMap;

	public boolean makeExclusive() {
		if (innerMap.isShared()) {
			innerMap.release();
			innerMap = new ReferenceCountingHashMapMap<K, L, V>(innerMap);
			assert !innerMap.isShared();
			return true;
		}
		return false;
	}

	@Override
	public void clear() {
		makeExclusive();
		innerMap.clear();
	}

	@Override
	public boolean containsKey(K keyLeft, L keyRight) {
		return innerMap.containsKey(keyLeft, keyRight);
	}

	@Override
	public boolean containsLeftKey(K keyLeft) {
		return innerMap.containsLeftKey(keyLeft);
	}

	@Override
	public V get(K keyLeft, L keyRight) {
		return innerMap.get(keyLeft, keyRight);
	}

	@Override
	public Map<L, V> getSubMap(K keyLeft) {
		Map<L, V> subMap = innerMap.getSubMap(keyLeft);
		if (subMap == null) return null;
		//return subMap;
		return Collections.unmodifiableMap(subMap);
	}
	
	public Iterator<Map.Entry<L, V>> subMapIterator(K keyLeft) {
		Map<L, V> subMap;
		if (innerMap.containsLeftKey(keyLeft)) {
			// Required, the caller might call remove on the iterator and
			// we would not notice the modification
			makeExclusive();
			// subMap is now already from the copy
			subMap = innerMap.getSubMap(keyLeft);
		} else {
			subMap = Collections.emptyMap();
		}
		return subMap.entrySet().iterator();
	}

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

	@Override
	public Set<K> leftKeySet() {
		return innerMap.leftKeySet();
	}

	@Override
	public V put(K keyLeft, L keyRight, V value) {
		makeExclusive();
		return innerMap.put(keyLeft, keyRight, value);
	}

	@Override
	public void putAll(MapMap<K, L, V> other) {
		makeExclusive();
		innerMap.putAll(other);
	}

	@Override
	public V remove(K keyLeft, L keyRight) {
		makeExclusive();
		return innerMap.remove(keyLeft, keyRight);
	}

	@Override
	public void remove(K keyLeft) {
		makeExclusive();
		innerMap.remove(keyLeft);
	}

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

	@Override
	public boolean equals(Object obj) {
		if (obj == null) 
			return false;
		return innerMap.equals(((LazyHashMapMap<?,?,?>)obj).innerMap);
	}

	@Override
	public int hashCode() {
		if (innerMap.hashCode == 0)
			innerMap.hashCode = innerMap.hashCode();
		return innerMap.hashCode;
	}

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

	@Override
	protected void finalize() throws Throwable {
		innerMap.release();
		super.finalize();
	}

	@Override
	public EntryIterator<K, L, V> entryIterator() {
		return innerMap.entryIterator();
	}
}
