/* This code is part of Freenet. It is distributed under the GNU General
 * Public License, version 2 (or at your option any later version). See
 * http://www.gnu.org/ for further details of the GPL. */
package freenet.client;

import java.util.LinkedList;

import freenet.keys.FreenetURI;
import freenet.support.LogThresholdCallback;
import freenet.support.Logger;
import freenet.support.Logger.LogLevel;

/**
 * Tracks all files currently in the cache from a given key.
 * Keeps the last known hash of the key (if this changes in a fetch, we flush the cache, unpack,
 * then throw an ArchiveRestartedException).
 * Provides fetch methods for Fetcher, which try the cache and then fetch if necessary, 
 * subject to the above.
 * 
 * Always take the lock on ArchiveStoreContext before the lock on ArchiveManager, NOT the other way around.
 */
class ArchiveStoreContext {

	private FreenetURI key;
	private final ArchiveManager.ARCHIVE_TYPE archiveType;
	/** Archive size */
	private long lastSize = -1;
	/** Archive hash */
	private byte[] lastHash;
	/** Index of still-cached ArchiveStoreItems with this key.
	 * Note that we never ever hold this and then take another lock! In particular
	 * we must not take the ArchiveManager lock while holding this lock. It must be
	 * the inner lock to avoid deadlocks. */
	private final LinkedList<ArchiveStoreItem> myItems;

        private static volatile boolean logMINOR;
	static {
		Logger.registerLogThresholdCallback(new LogThresholdCallback(){
			@Override
			public void shouldUpdate(){
				logMINOR = Logger.shouldLog(LogLevel.MINOR, this);
			}
		});
	}
	
	ArchiveStoreContext(FreenetURI key, ArchiveManager.ARCHIVE_TYPE archiveType) {
		this.key = key;
		this.archiveType = archiveType;
		myItems = new LinkedList<ArchiveStoreItem>();
	}

	/** Returns the size of the archive last time we fetched it, or -1 */
	long getLastSize() {
		return lastSize;
	}
	
	/** Sets the size of the archive - @see getLastSize() */
	void setLastSize(long size) {
		lastSize = size;
	}

	
	/** Returns the hash of the archive last time we fetched it, or null */
	byte[] getLastHash() {
		return lastHash;
	}

	/** Sets the hash of the archive - @see getLastHash() */
	void setLastHash(byte[] realHash) {
		lastHash = realHash;
	}

	/**
	 * Remove all ArchiveStoreItems with this key from the cache.
	 */
	void removeAllCachedItems(ArchiveManager manager) {
		ArchiveStoreItem item = null;
		while(true) {
			synchronized (myItems) {
				// removeCachedItem() will call removeItem(), so don't remove it here.
				item = myItems.peek();
			}
			if(item == null) break;
			manager.removeCachedItem(item);
		}
	}

	/** Notify that a new archive store item with this key has been added to the cache. */
	void addItem(ArchiveStoreItem item) {
		synchronized(myItems) {
			myItems.push(item);
		}
	}

	/** Notify that an archive store item with this key has been expelled from the 
	 * cache. Remove it from our local cache and ask it to free the bucket if 
	 * necessary. */
	void removeItem(ArchiveStoreItem item) {
		synchronized(myItems) {
			if (!myItems.remove(item)) {
				if(logMINOR)
					Logger.minor(this, "Not removing: "+item+" for "+this+" - already removed");
				return; // only removed once
			}
		}
		item.innerClose();
	}

	public short getArchiveType() {
		return archiveType.metadataID;
	}
	
	public FreenetURI getKey() {
		return key;
	}

}
