/* This file is part of the db4o object database http://www.db4o.com

Copyright (C) 2004 - 2011  Versant Corporation http://www.versant.com

db4o is free software; you can redistribute it and/or modify it under
the terms of version 3 of the GNU General Public License as published
by the Free Software Foundation.

db4o 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
for more details.

You should have received a copy of the GNU General Public License along
with this program.  If not, see http://www.gnu.org/licenses/. */
package com.db4o.internal.caching;

import java.util.*;

import com.db4o.foundation.*;

/**
 * @exclude
 * Simplified version of the algorithm taken from here:
 * http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.34.2641
 */
class LRU2QLongCache<V> implements Cache4<Long,V>{
	
	private final CircularLongBuffer4 _am;
	
	private final CircularLongBuffer4 _a1;
	
	private final Map<Long,V> _slots;
	
	private final int _maxSize;

	private final int _a1_threshold;
	
	LRU2QLongCache(int maxSize) {
		_maxSize = maxSize;
		_a1_threshold = _maxSize / 4;
		_am = new CircularLongBuffer4(_maxSize);
		_a1 = new CircularLongBuffer4(_maxSize);
		_slots = new HashMap<Long, V>(maxSize);
	}
	
	public V produce(Long key, Function4<Long, V> producer, Procedure4<V> finalizer) {
		
		if(_am.remove(key)){
			_am.addFirst(key);
			return _slots.get(key);
		}
		
		if(_a1.remove(key)){
			_am.addFirst(key);
			return _slots.get(key);
		}
		
		if(_slots.size() >= _maxSize){
			discardPage(finalizer);
		}
		
		final V value = producer.apply(key);
		_slots.put(key, value);
		_a1.addFirst(key);
		return value;
	}

	private void discardPage(Procedure4<V> finalizer) {
	    if(_a1.size() >= _a1_threshold) {
	    	discardPageFrom(_a1, finalizer);
	    } else {
	    	discardPageFrom(_am, finalizer);
	    }
    }

	private void discardPageFrom(final CircularLongBuffer4 list, Procedure4<V> finalizer) {
	    discard(list.removeLast(), finalizer);
    }

	private void discard(Long key, Procedure4<V> finalizer) {
		if (null != finalizer) {
			finalizer.apply(_slots.get(key));
		}
	    _slots.remove(key);
    }

	public String toString() {
		return "LRU2QCache(am=" + toString(_am)  + ", a1=" + toString(_a1) + ")";
	}

	private String toString(Iterable4<Long> buffer) {
		return Iterators.toString(buffer);
	}

	public Iterator<V> iterator() {
		return _slots.values().iterator();
    }
}
