package eu.dnetlib.scholix.api.index;

import eu.dnetlib.scholix.api.ScholixException;
import org.apache.commons.pool2.PooledObjectFactory;
import org.apache.commons.pool2.impl.GenericObjectPool;
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;

/**
 * When using the Java High Level REST Client provided by the Elasticsearch official website, it is found that there is no in the client API.
 *  Connecting to connect the pool, create a new connection every time, this is impact in high concurrency situation, so it is ready to be on the client
 *  API increases the concept of pool.
 *
 *  Fortunately, we don't need to turn your weight to write the implementation of the connection pool, because Apache provides us with the general framework of the connection pool.
 *  Commons-pool2, and we only need to implement some logic according to the frame design. Used in the REDIS client API
 *  Jedispool is based on Commons-pool2 implementation.
 *
 *  Let's take a look at how to achieve it.
 *
 *  First we have to create a pool class, this pool introduces GenericObjectPool in Commons-pool2 through dependent manner. In this class
 *  In, we define how to borrow objects and returns objects from the pool.
 *
 * @param <T> the type parameter
 */
public class Pool<T> implements Cloneable {

    /**
     * The Internal pool.
     */
    protected GenericObjectPool<T> internalPool ;

    /**
     * Instantiates a new Pool.
     */
    public Pool(){
        super();
    }

    /**
     * Instantiates a new Pool.
     *
     * @param poolConfig the pool config
     * @param factory    the factory
     */
    public Pool(final GenericObjectPoolConfig poolConfig, PooledObjectFactory<T> factory){
        initPool(poolConfig, factory);
    }

    /**
     * Init pool.
     *
     * @param poolConfig the pool config
     * @param factory    the factory
     */
    public void initPool(final GenericObjectPoolConfig poolConfig, PooledObjectFactory<T> factory) {

        if (this.internalPool != null) {
            try {
                closeInternalPool();
            } catch (Exception e) {
            }
        }

        this.internalPool = new GenericObjectPool<T>(factory, poolConfig);
    }

    /**
     * Close internal pool.
     *
     * @throws ScholixException the scholix exception
     */
    protected void closeInternalPool() throws ScholixException {
        try {
            internalPool.close();
        } catch (Exception e) {
            throw new ScholixException("Could not destroy the pool", e);
        }
    }

    /**
     * Gets resource.
     *
     * @return the resource
     * @throws ScholixException the scholix exception
     */
    public T getResource() throws ScholixException {
        try {
            return internalPool.borrowObject();
        } catch (Exception e) {
            throw new ScholixException("Could not get a resource from the pool", e);
        }
    }


    /**
     * Return resource.
     *
     * @param resource the resource
     * @throws ScholixException the scholix exception
     */
    public void returnResource(final T resource) throws ScholixException {
        if (resource != null) {
            returnResourceObject(resource);
        }
    }

    private void returnResourceObject(final T resource) throws ScholixException {
        if (resource == null) {
            return;
        }
        try {
            internalPool.returnObject(resource);
        } catch (Exception e) {
            throw new ScholixException("Could not return the resource to the pool", e);
        }
    }

    /**
     * Return broken resource.
     *
     * @param resource the resource
     * @throws ScholixException the scholix exception
     */
    public void returnBrokenResource(final T resource) throws ScholixException {
        if (resource != null) {
            returnBrokenResourceObject(resource);
        }
    }

    private void returnBrokenResourceObject(T resource) throws ScholixException {
        try {
            internalPool.invalidateObject(resource);
        } catch (Exception e) {
            throw new ScholixException("Could not return the resource to the pool", e);
        }
    }

    /**
     * Destroy.
     *
     * @throws ScholixException the scholix exception
     */
    public void destroy() throws ScholixException {
        closeInternalPool();
    }


    /**
     * Gets num active.
     *
     * @return the num active
     */
    public int getNumActive() {
        if (poolInactive()) {
            return -1;
        }

        return this.internalPool.getNumActive();
    }

    /**
     * Gets num idle.
     *
     * @return the num idle
     */
    public int getNumIdle() {
        if (poolInactive()) {
            return -1;
        }

        return this.internalPool.getNumIdle();
    }

    /**
     * Gets num waiters.
     *
     * @return the num waiters
     */
    public int getNumWaiters() {
        if (poolInactive()) {
            return -1;
        }

        return this.internalPool.getNumWaiters();
    }

    /**
     * Gets mean borrow wait time millis.
     *
     * @return the mean borrow wait time millis
     */
    public long getMeanBorrowWaitTimeMillis() {
        if (poolInactive()) {
            return -1;
        }

        return this.internalPool.getMeanBorrowWaitTimeMillis();
    }

    /**
     * Gets max borrow wait time millis.
     *
     * @return the max borrow wait time millis
     */
    public long getMaxBorrowWaitTimeMillis() {
        if (poolInactive()) {
            return -1;
        }

        return this.internalPool.getMaxBorrowWaitTimeMillis();
    }

    private boolean poolInactive() {
        return this.internalPool == null || this.internalPool.isClosed();
    }

    /**
     * Add objects.
     *
     * @param count the count
     * @throws Exception the exception
     */
    public void addObjects(int count) throws Exception {
        try {
            for (int i = 0; i < count; i++) {
                this.internalPool.addObject();
            }
        } catch (Exception e) {
            throw new Exception("Error trying to add idle objects", e);
        }
    }
}