package charactermanaj.graphics;

import java.awt.image.BufferedImage;
import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.SoftReference;
import java.util.HashMap;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * 画像のキャッシュ.<br>
 * キャッシュは自動的にガベージコレクタにより回収されます.<br>
 * ただし、{@link #unlockImages()}が呼び出されるまで、{@link #set(Object, BufferedImage)}されたイメージは
 * ガベージコレクトの対象にはなりません。 
 * @author seraphy
 *
 * @param <K>
 */
public class ImageCache<K> {
	
	private static final Logger logger = Logger.getLogger(ImageCache.class.getName());

	private HashMap<K, BufferedImageWithKeyReference<K>> lockedImages
		= new HashMap<K, BufferedImageWithKeyReference<K>>();
	
	private ReferenceQueue<? super BufferedImage> queue
		= new ReferenceQueue<BufferedImage>();
	
	private HashMap<K, BufferedImageWithKeyReference<K>> caches
		= new HashMap<K, BufferedImageWithKeyReference<K>>();
	
	public BufferedImage get(K key) {
		if (key == null) {
			return null;
		}
		synchronized (caches) {
			BufferedImageWithKeyReference<K> ref = caches.get(key);
			BufferedImage img = null;
			if (ref != null) {
				img = ref.get();
			}
			sweep();
			return img;
		}
	}

	public void set(K key, BufferedImage img) {
		if (key == null) {
			return;
		}
		synchronized (caches) {
			if (img == null) {
				if (logger.isLoggable(Level.FINE)) {
					logger.log(Level.FINE, "remove cache: " + key);
				}
				caches.remove(key);
			} else {
				BufferedImageWithKeyReference<K> cacheData = new BufferedImageWithKeyReference<K>(key, img, queue);
				lockedImages.put(key, cacheData);
				caches.put(key, cacheData);
			}
			sweep();
		}
	}
	
	public void unlockImages() {
		synchronized (caches) {
			lockedImages.clear();
			sweep();
		}
	}
	
	public void sweep() {
		synchronized (caches) {
			// ガベージコレクト済みアイテムを除去する
			Reference<?> ref = null;
			boolean removed = false;
			while ((ref = queue.poll()) != null) {
				@SuppressWarnings("unchecked")
				K key = ((BufferedImageWithKeyReference<K>) ref).getKey();
				if (key != null) {
					if (caches.get(key).get() == null) {
						if (logger.isLoggable(Level.FINE)) {
							logger.log(Level.FINE, "removed cache: " + key);
						}
						removed = true;
						caches.remove(key);
					}
				}
			}
			if (removed) {
				if (logger.isLoggable(Level.FINE)) {
					logger.log(Level.FINE,
							"cache[" + Integer.toHexString(this.hashCode())
									+ "] size:" + caches.size());
				}
			}
		}
	}
}

/**
 * キー情報つきSoftReference
 * @author seraphy
 *
 * @param <K> キー
 */
class BufferedImageWithKeyReference<K> extends SoftReference<BufferedImage> {
	
	private final K key;
	
	public BufferedImageWithKeyReference(K key, BufferedImage img, ReferenceQueue<? super BufferedImage> queue) {
		super(img, queue);
		this.key = key;
	}
	
	public K getKey() {
		return key;
	}
}
