/*
 * Copyright 2007-2010 WorldWide Conferencing, LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package net.liftweb {
package util {

import _root_.java.lang.ref.{ReferenceQueue,SoftReference};
import _root_.java.util._
import _root_.java.util.concurrent.locks._
import _root_.net.liftweb.util._
import Map._
import Helpers._
import ActorPing._
import common._

/**
 * Companion module that has the role of monitoring garbage collected references and remove the orphaned
 * keys from the cache. The monitor is started by calling <i>initialize</i> function and terminated by
 * calling <i>shutDown</i>. It monitors all SoftReferenceCache instances in the context of the same classloader.
 * It can also be used as a factory for obtaining new instances of SoftReferenceCache class
 */
object SoftReferenceCache {

  @volatile
  private var terminated = false;

  private[util] val refQueue = new ReferenceQueue[Any]();

  /**
   * Create a new SoftReferenceCache instance
   */
  def apply[K, V](size: Int) = new SoftReferenceCache[K,V](size)

  /**
   * Initialize the orphan keys monitor
   */
  def initialize = {
    // A daemon thread is more approapriate here then an Actor as
    // we'll do blocking reads from the reference queue
    val thread = new Thread(new Runnable(){
                              def run(){
                                processQueue
                              }
                            })
    thread.setDaemon(true)
    thread.start
  }

  /**
   * ShutDown the monitoring
   */
  def shutDown = {
    terminated = true;
  }

  private def processQueue {
    while (!terminated) {
      tryo {
        // Wait 30 seconds for something to appear in the queue.
        val sftVal = refQueue.remove(30000).asInstanceOf[SoftValue[_,_]];
        if (sftVal != null) {
          sftVal.cache.remove(sftVal.key);
        }
      }
    }
  }
}

case object ProcessQueue
case object Done

/**
 * A Map that holds the values as SoftReference-s. It also applies a LRU policy for the cache entries.
 */
class SoftReferenceCache[K, V](cacheSize: Int) {

  val cache = new LinkedHashMap[K, SoftValue[K, V]]() {
    override def removeEldestEntry(eldest: Entry[K, SoftValue[K, V]]): Boolean = {
      return size() > cacheSize;
    }
  }

  val rwl = new ReentrantReadWriteLock();

  val readLock = rwl.readLock
  val writeLock = rwl.writeLock

  private def lock[T](l: Lock)(block: => T): T = {
    l lock;
    try {
      block
    } finally {
      l unlock
    }
  }

  /**
   * Returns the cached value mapped with this key or Empty if not found
   *
   * @param key
   * @return Box[V]
   */
  def apply(key: K): Box[V] = lock(readLock) {
    Box.!!(cache.get(key)) match {
      case Full(value) =>
         Box.!!(value.get) or {
            remove(key);
            Empty
         }
      case _ => Empty
    }
  }

  /**
   * Puts a new keyed entry in cache
   * @param tuple: (K, V)*
   * @return this
   */
  def += (tuple: (K, V)*) = {
    lock(writeLock) {
      for (t <- tuple) yield {
        cache.put(t._1, new SoftValue(t._1, t._2, this, SoftReferenceCache.refQueue));
      }
    }
    this
  }

  /**
   * Removes the cache entry mapped with this key
   *
   * @returns the value removed
   */
  def remove(key: Any): Box[V] = {
    lock(writeLock) {
      for {value <- Box.!!(cache.remove(key).asInstanceOf[SoftValue[K, V]])
           realValue <- Box.!!(value.get)
      } yield realValue
    }
  }


  def keys = cache.keySet

}

class SoftValue[K, V](k: K,
                      v: V,
                      lruCache: SoftReferenceCache[K, V],
                      queue: ReferenceQueue[Any]) extends SoftReference[V](v, queue) {
    def key: K = k
    def cache: SoftReferenceCache[K, V] = lruCache
}

}
}