/**
 * Copyright Alex Objelean
 */
package ro.isdc.wro.extensions.processor.support;

import static org.apache.commons.lang3.Validate.notNull;

import org.apache.commons.pool.BasePoolableObjectFactory;
import org.apache.commons.pool.impl.GenericObjectPool;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import ro.isdc.wro.util.ObjectFactory;


/**
 * A generic aware object pool wrapper. Probably not the best name, but it can be changed later. It helps you to avoid
 * the cast and hides the exception handling by throwing {@link RuntimeException} when borrowing or returning object to
 * the pool fails.
 *
 * @author Alex Objelean
 * @created 10 Nov 2011
 * @since 1.4.2
 */
public class ObjectPoolHelper<T> {
  private static final Logger LOG = LoggerFactory.getLogger(ObjectPoolHelper.class);
  private static final int MAX_IDLE = 2;
  private static final long MAX_WAIT = 5L * 1000L;
  private static final long EVICTABLE_IDLE_TIME = 30 * 1000L;
  // Allows using the objects from the pool in a thread-safe fashion.
  private GenericObjectPool<T> objectPool;


  public ObjectPoolHelper(final ObjectFactory<T> objectFactory) {
    notNull(objectFactory);
    objectPool = createObjectPool(objectFactory);
    notNull(objectPool);
  }

  /**
   * Ensure that a not null pool will be created.
   */
  private GenericObjectPool<T> createObjectPool(final ObjectFactory<T> objectFactory) {
    final GenericObjectPool<T> pool = newObjectPool(objectFactory);
    notNull(pool);
    return pool;
  }

  /**
   * Creates a {@link GenericObjectPool}. Override this method to set custom objectPool configurations.
   */
  protected GenericObjectPool<T> newObjectPool(final ObjectFactory<T> objectFactory) {
    final int maxActive = Math.max(2, Runtime.getRuntime().availableProcessors());
    final GenericObjectPool<T> pool = new GenericObjectPool<T>(new BasePoolableObjectFactory<T>() {
      @Override
      public T makeObject()
        throws Exception {
        return objectFactory.create();
      }
    });
    pool.setMaxActive(maxActive);
    pool.setMaxIdle(MAX_IDLE);
    pool.setMaxWait(MAX_WAIT);
    /**
     * Use WHEN_EXHAUSTED_GROW strategy, otherwise the pool object retrieval can fail. More details here:
     * <a>http://code.google.com/p/wro4j/issues/detail?id=364</a>
     */
    pool.setWhenExhaustedAction(GenericObjectPool.WHEN_EXHAUSTED_GROW);
    // make object eligible for eviction after a predefined amount of time.
    pool.setSoftMinEvictableIdleTimeMillis(EVICTABLE_IDLE_TIME);
    pool.setTimeBetweenEvictionRunsMillis(EVICTABLE_IDLE_TIME);
    return pool;
  }

  /**
   * @return object from the pool.
   */
  public T getObject() {
    try {
      return objectPool.borrowObject();
    } catch (final Exception e) {
      // should never happen
      throw new RuntimeException("Cannot get object from the pool", e);
    }
  }


  public void returnObject(final T engine) {
    notNull(engine);
    try {
      objectPool.returnObject(engine);
    } catch (final Exception e) {
      // should never happen
      throw new RuntimeException("Cannot get object from the pool", e);
    }
  }

  /**
   * Use a custom {@link GenericObjectPool}.
   *
   * @param objectPool
   *          to use.
   */
  public final void setObjectPool(final GenericObjectPool<T> objectPool) {
    notNull(objectPool);
    this.objectPool = objectPool;
  }

  /**
   * Close the object pool to avoid the memory leak.
   *
   * @throws Exception
   *           if the close operation failed.
   */
  public void destroy() throws Exception {
    LOG.debug("closing objectPool");
    objectPool.close();
  }
}
