/*
 * Distributed as part of mchange-commons-java 0.2.11
 *
 * Copyright (C) 2015 Machinery For Change, Inc.
 *
 * Author: Steve Waldman <swaldman@mchange.com>
 *
 * This library is free software; you can redistribute it and/or modify
 * it under the terms of EITHER:
 *
 *     1) The GNU Lesser General Public License (LGPL), version 2.1, as 
 *        published by the Free Software Foundation
 *
 * OR
 *
 *     2) The Eclipse Public License (EPL), version 1.0
 *
 * You may choose which license to accept if you wish to redistribute
 * or modify this work. You may offer derivatives of this work
 * under the license you have chosen, or you may provide the same
 * choice of license which you have been offered here.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 *
 * You should have received copies of both LGPL v2.1 and EPL v1.0
 * along with this software; see the files LICENSE-EPL and LICENSE-LGPL.
 * If not, the text of these licenses are currently available at
 *
 * LGPL v2.1: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html
 *  EPL v1.0: http://www.eclipse.org/org/documents/epl-v10.php 
 * 
 */

package com.mchange.v1.util;

import java.util.List;
import java.util.LinkedList;
import java.util.Set;
import java.util.HashSet;
import java.util.Iterator;


/**
 * @deprecated use com.mchange.v1.resourcepool.AbstractResourcePool
 */
public abstract class AbstractResourcePool
{
    private final static boolean TRACE = true;
    private final static boolean DEBUG = true;

    private static RunnableQueue sharedQueue = new SimpleRunnableQueue();

    Set  managed = new HashSet();
    List unused  = new LinkedList();

    int start;
    int max;
    int inc;

    int num_acq_attempts  = Integer.MAX_VALUE;
    int acq_attempt_delay = 50;

    RunnableQueue rq;
    
    boolean initted = false;
    boolean broken  = false;
    

    protected AbstractResourcePool(int start, int max, int inc)
    {this(start, max, inc, sharedQueue);}

    protected AbstractResourcePool(int start, int max, int inc, RunnableQueue rq)
    {
	this.start = start;
	this.max   = max;
	this.inc   = inc;
	this.rq    = rq;
    }

    protected abstract Object acquireResource() throws Exception;

    /** Called on checkout! */
    protected abstract void   refurbishResource(Object resc) throws BrokenObjectException;

    protected abstract void   destroyResource(Object resc) throws Exception;

    /**
     * We defer actual acquisition of the resources to a 
     * method outside the constructor because subclasses
     * may need to do prep work in their own constructor
     * before resource acquisition can occur. This method
     * will usually be called at the end of a subclasses
     * constructor.
     */
    protected synchronized void init() throws Exception
    {
	for (int i = 0; i < start; ++i) assimilateResource();

	this.initted = true;
    }

    //we synchronize in the delegate method
    protected Object checkoutResource() 
	throws BrokenObjectException, InterruptedException, Exception
    { return checkoutResource( 0 ); }

    /* Note that if an any exception occurs on refurbishResource(), */
    /* we remove and retry our checkout                             */
    protected synchronized Object checkoutResource(long timeout) 
	throws BrokenObjectException, InterruptedException, TimeoutException, Exception
    {
	if (!initted) init();
	ensureNotBroken();

	int sz = unused.size();
	 //System.err.println("pool size: " + sz);
	if (sz == 0)
	    {
		int msz = managed.size();
		if (msz < max)
		    postAcquireMore();
		awaitAvailable(timeout);
	    }
	Object resc = unused.get(0);
	unused.remove(0);
	try
	    {
		refurbishResource(resc);
	    }
	catch (Exception e)
	    {
		//uh oh... bad resource...
		if (DEBUG) e.printStackTrace();
		removeResource(resc);
		return checkoutResource(timeout);
	    }
	if (TRACE) trace();
	return resc;
    }

    protected synchronized void checkinResource(Object resc) throws BrokenObjectException
    {
	//we permit straggling resources to be checked in 
	//without exception even if we are broken
	if (!managed.contains(resc))
	    throw new IllegalArgumentException("ResourcePool: Tried to check-in a foreign resource!");
	unused.add(resc);
	this.notifyAll();
	if (TRACE) trace();
    }

    protected synchronized void markBad(Object resc) throws Exception
    { removeResource( resc ); }

    protected synchronized void close() throws Exception
    {
				//we permit closes when we are already broken, so
				//that resources that were checked out when the break
				//occured can still be cleaned up
	this.broken = true;
	for (Iterator ii = managed.iterator(); ii.hasNext();)
	    {
		try
		    {removeResource(ii.next());}
		catch (Exception e)
		    {if (DEBUG) e.printStackTrace();}
	    }
    }



    //the following methods should only be invoked from 
    //sync'ed methods / blocks...

    private void postAcquireMore() throws InterruptedException
    {
	rq.postRunnable(new AcquireTask());
    }

    private void awaitAvailable(long timeout) throws InterruptedException, TimeoutException
    {
	int avail;
	while ((avail = unused.size()) == 0) this.wait(timeout);
	if (avail == 0)
	    throw new TimeoutException();
    }

    private void acquireMore() throws Exception
    {
	int msz = managed.size();
	for (int i = 0; i < Math.min(inc, max - msz); ++i)
	    assimilateResource();
    }

    private void assimilateResource() throws Exception
    {
	Object resc = acquireResource();
	managed.add(resc);
	unused.add(resc);
	//System.err.println("assimilate resource... unused: " + unused.size());
	this.notifyAll();
	if (TRACE) trace();
    }

    private void removeResource(Object resc) throws Exception
    {
	managed.remove(resc);
	unused.remove(resc);
	destroyResource(resc);
	if (TRACE) trace();
    }

    private void ensureNotBroken() throws BrokenObjectException
    {if (broken) throw new BrokenObjectException(this);}

    //same as close, but we do not destroy checked out
    //resources
    private synchronized void unexpectedBreak()
    {
	this.broken = true;
	for (Iterator ii = unused.iterator(); ii.hasNext();)
	    {
		try
		    {removeResource(ii.next());}
		catch (Exception e)
		    {if (DEBUG) e.printStackTrace();}
	    }
    }

    private void trace()
    {
	System.err.println(this + "  [managed: " + managed.size() + ", " +
			   "unused: " + unused.size() + ']');
    }

    class AcquireTask implements Runnable
    {
	boolean success = false;
	
	public void run()
	{
	    for (int i = 0; !success && i < num_acq_attempts; ++i)
	    {
		try
		    {
			if (i > 0)
			    Thread.sleep(acq_attempt_delay); 
			synchronized (AbstractResourcePool.this)
			    { acquireMore(); }
			success = true;
		    }
		catch (Exception e)
		    {if (DEBUG) e.printStackTrace();}
	    }
	    if (!success) unexpectedBreak();
	}
    }

    protected class TimeoutException extends Exception
    {}
}








