package fitnesse.util;

import util.Clock;

import java.lang.ref.SoftReference;
import java.lang.ref.WeakReference;
import java.util.Map;
import java.util.WeakHashMap;
import java.util.concurrent.ConcurrentHashMap;

/**
 * A simple Cache interface.
 */
public interface Cache<K, V> {

  V get(K key) throws Exception;

  void evict(K key);

  public interface Loader<K, V> {
    V fetch(K key) throws Exception;
  }

  public interface ExpirationPolicy<K, V> {
    boolean isExpired(K key, V value, long lastModified);
  }

  /**
   * Builder for the Simple Cache.
   * @param <K> Key object type. Keys should be hashable.
   * @param <V> Value object type.
   */
  public class Builder<K, V> {
    private Loader<K, V> loader;
    private ExpirationPolicy<K, V> expirationPolicy = new NoExpirationPolicy<K, V>();

    public Builder<K, V> withLoader(Loader<K, V> loader) {
      this.loader = loader;
      return this;
    }

    public Builder<K, V> withExpirationPolicy(ExpirationPolicy<K, V> expirationPolicy) {
      this.expirationPolicy = expirationPolicy;
      return this;
    }

    public Cache<K, V> build() {
      return new Cache<K, V>() {
        private Map<K, SoftReference<CachedValue<V>>> cacheMap = new ConcurrentHashMap<K, SoftReference<CachedValue<V>>>();

        @Override
        public V get(K key) throws Exception {
          SoftReference<CachedValue<V>> ref = cacheMap.get(key);
          CachedValue<V> cachedValue = ref != null ? ref.get() : null;
          V value;
          if (cachedValue == null || expirationPolicy.isExpired(key, cachedValue.value, cachedValue.lastModified)) {
            value = loader.fetch(key);
            if (value != null) {
              cachedValue = new CachedValue<V>(value, Clock.currentTimeInMillis());
              cacheMap.put(key, new SoftReference<CachedValue<V>>(cachedValue));
              // TODO: implement cleanup cycle.
            } else {
              cacheMap.remove(key);
            }
          } else {
            value = cachedValue.value;
          }
          return value;
        }

        @Override
        public void evict(K key) {
          cacheMap.remove(key);
        }
      };
    }

    private static class CachedValue<V> {
      private V value;
      private long lastModified;

      CachedValue(V value, long lastModified) {
        this.value = value;
        this.lastModified = lastModified;
      }
    }

    private static class NoExpirationPolicy<K, V> implements ExpirationPolicy<K, V> {

      @Override
      public boolean isExpired(K key, V value, long lastModified) {
        return false;
      }
    }

  }
}


@@ -312,16 +312,45 @@
 ption;%0A%0A
+  void put(K key, V value);%0A%0A
   void e
@@ -1379,16 +1379,22 @@
         
+final 
 SoftRefe
@@ -1446,24 +1446,30 @@
 ;%0A          
+final 
 CachedValue%3C
@@ -1743,208 +1743,96 @@
     
-cachedValue = new CachedValue%3CV%3E(value, Clock.currentTimeInMillis());%0A              cacheMap.put(key, new SoftReference%3CCachedValue%3CV%3E%3E(cachedValue));%0A              // TODO: implement cleanup cycle.%0A 
+put(key, value);%0A            %7D else %7B%0A              cacheMap.remove(key);%0A            %7D%0A
     
@@ -1829,33 +1829,32 @@
     %7D%0A          
- 
 %7D else %7B%0A       
@@ -1862,36 +1862,71 @@
     
-  cacheMap.remove(key);%0A    
+value = cachedValue.value;%0A          %7D%0A          return value;%0A
     
@@ -1927,32 +1927,33 @@
 ;%0A        %7D%0A
+%0A
         
   %7D else %7B%0A 
@@ -1944,40 +1944,86 @@
     
-  %7D else %7B%0A            v
+public void put(K key, V value) %7B%0A          CachedValue%3CV%3E cachedV
 alue = 
-c
+new C
 ache
@@ -2032,50 +2032,124 @@
 alue
-.
+%3CV%3E(
 value
-;%0A          %7D%0A          return value
+, Clock.currentTimeInMillis());%0A          cacheMap.put(key, new SoftReference%3CCachedValue%3CV%3E%3E(cachedValue))
 ;%0A  
@@ -2248,24 +2248,26 @@
 ;%0A        %7D%0A
+%0A%0A
       %7D;%0A   

Clean up cache code a little.