/*
 * Copyright (c) 2004-2017, by Alex Leigh.
 * All rights reserved.
 *
 * THIS IS UNPUBLISHED PROPRIETARY SOURCE CODE
 * The copyright notice above does not evidence any
 * actual or intended publication of such source code.
 */

package lc.mecha.pool;

import lc.mecha.log.MechaLogger;
import lc.mecha.log.MechaLoggerFactory;

import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * Reference implementation of the <code>ObjectPool</code> interface which does not provide any pooling at all.
 * Primary useful for testing and debugging purposes.
 *
 * @param <T>
 */
public class BlockingQueueObjectPool<T> implements ObjectPool<T> {
    private static final MechaLogger logger = MechaLoggerFactory.getLogger(BlockingQueueObjectPool.class);
    private final Factory<T> factory;
    private final LinkedBlockingQueue<T> queue = new LinkedBlockingQueue<>();
    private final AtomicInteger sz = new AtomicInteger(0);
    private final int maxSz = 10;

    public BlockingQueueObjectPool(Factory<T> factory) {
        this.factory = factory;
    }

    private T create() throws Exception {
        T obj = factory.create();

        // There is no guarantee the factory produced a compatible object.
        if (obj instanceof PoolObject)
            ((PoolObject) obj).setPool(this);
        return obj;
    }

    @Override
    public T checkOut() throws Exception {
        // If we are empty - make a new one and return that.
        if (sz.get() == 0) {
            return create();
        }

        // FIXME: Race condition
        // Otherwise, get one from the queue. There's a race here - if someone else beat us to the last object,
        // we'll throw an exception

        T obj = queue.take();

        if (!factory.validate(obj)) {
            factory.destroy(obj);
            // In this case we get them a new one rather than going back to the queue.
            return create();
        }

        sz.decrementAndGet();
        return obj;
    }

    @Override
    public void checkIn(T obj) {

        try {
            factory.reset(obj);
        } catch (Exception e) {
            logger.warn("checkIn(): Unable to reset() object: {} {} ", obj, e);
            factory.destroy(obj);
            return;
        }

        // TODO: Destroy the object if it is so old
        if (sz.get() > maxSz) {
            factory.destroy(obj);
        } else {
            queue.add(obj);
            sz.incrementAndGet();
        }
    }
}