package com.trendrr.beanstalk;

import java.util.Date;
import java.util.HashSet;
import java.util.Set;
import java.util.logging.Logger;

public class BeanstalkPool {

    protected Logger log = Logger.getLogger("com.trendrr.beanstalk");

    Set<BeanstalkClient> clients = new HashSet<>();
    int maxClients = 30;

    long maxUseTime = 20 * 60 * 1000; //max checkout time is 20 minutes.

    long maxIdleTime = 20 * 60 * 1000; //connection will be removed after no use.

    private String addr;
    private int port;
    private String tube = null;

    /**
     * setup a new pool.
     *
     * @param addr        address of the beanstalkd server to connection to
     * @param port        port of the beanstalkd server
     * @param maxPoolSize maximum number of clients allowed in the pool (0 for infinity)
     * @param tube        All operations for the client will work on the tube.
     */
    public BeanstalkPool(String addr, int port, int maxPoolSize, String tube) {
        this.addr = addr;
        this.port = port;
        this.maxClients = maxPoolSize;
        this.tube = tube;
    }

    /**
     * setup a new pool.
     *
     * @param addr        address of the beanstalkd server to connection to
     * @param port        port of the beanstalkd server
     * @param maxPoolSize maximum number of clients allowed in the pool (0 for infinity)
     */
    public BeanstalkPool(String addr, int port, int maxPoolSize) {
        this(addr, port, maxPoolSize, null);
    }

    /**
     * returns a client to the pool
     *
     * @param client
     */
    public synchronized void done(BeanstalkClient client) {
        client.inUseSince = null;
    }

    /**
     * This gets a client from the pool.  will throw a BeanstalkException if
     * there are more then the maximum number of clients checked out.
     *
     * @return
     */
    public synchronized BeanstalkClient getClient() throws BeanstalkException {
        /*
         * synchronized, but should be fast as the client initialization code happens lazily.
		 */
        Set<BeanstalkClient> toRemove = new HashSet<>();
        Date max = new Date(new Date().getTime() - this.maxUseTime);
        Date maxIdle = new Date(new Date().getTime() - this.maxIdleTime);
        BeanstalkClient returnClient = null;
        try {
            /*
             * Here we iterate over all the clients and reap any that need reaping.
			 * TODO: we could restrict this to only loop over once every minute or so.
			 * for now I don't see it being a huge problem.
			 */
            for (BeanstalkClient client : clients) {
                if (client.inUseSince != null && client.inUseSince.before(max)) client.reap = true;
                if (client.lastUsed != null && client.lastUsed.before(maxIdle)) client.reap = true;
                if (client.con != null && !client.con.isOpen()) client.reap = true;
                if (client.reap) toRemove.add(client);
                else if (returnClient == null && client.inUseSince == null) {
                    client.inUseSince = new Date();
                    client.lastUsed = new Date();
                    returnClient = client;
                }
            }
        } finally {
            for (BeanstalkClient c : toRemove) {
                log.finer("REAPING Client: " + c);
                c.pool = null;
                this.clients.remove(c);
                c.close();
            }
        }
        if (returnClient != null) return returnClient;
        if (this.maxClients > 0 && this.clients.size() >= this.maxClients) {
            log.severe("Too many clients in use!");
            throw new BeanstalkException("To many clients in use");
        }
        BeanstalkClient client = new BeanstalkClient(this.addr, this.port, this.tube, this);
        this.clients.add(client);
        client.inUseSince = new Date();
        return client;
    }

    /**
     * returns the number of active clients in the pool
     *
     * @return
     */
    public int getPoolSize() {
        return this.clients.size();
    }
}
