/*
 * 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.v2.async;

import java.util.Collections;
import java.util.List;
import java.util.LinkedList;
import com.mchange.v2.log.MLevel;
import com.mchange.v2.log.MLog;
import com.mchange.v2.log.MLogger;
import com.mchange.v2.util.ResourceClosedException;

public class CarefulRunnableQueue implements RunnableQueue, Queuable, StrandedTaskReporting
{
    private final static MLogger logger = MLog.getLogger( CarefulRunnableQueue.class );

    private List taskList = new LinkedList();
    private TaskThread t  = new TaskThread();

    private boolean shutdown_on_interrupt;

    private boolean gentle_close_requested = false;

    private List strandedTasks = null;

    public CarefulRunnableQueue(boolean daemon, boolean shutdown_on_interrupt)
    {
	this.shutdown_on_interrupt = shutdown_on_interrupt;
	t.setDaemon( daemon );
	t.start();
    }

    public RunnableQueue asRunnableQueue()
    { return this; }

    public synchronized void postRunnable(Runnable r)
    {
	try
	    {
		if (gentle_close_requested)
		    throw new ResourceClosedException("Attempted to post a task to a closing " +
						      "CarefulRunnableQueue.");
		
		taskList.add(r);
		this.notifyAll();
	    }
	catch (NullPointerException e)
	    {
		//e.printStackTrace();
		if (Debug.DEBUG)
		    {
			if ( logger.isLoggable( MLevel.FINE ) )
			    logger.log( MLevel.FINE, "NullPointerException while posting Runnable.", e );
		    }
		if (taskList == null)
		    throw new ResourceClosedException("Attempted to post a task to a CarefulRunnableQueue " +
						      "which has been closed, or whose TaskThread has been " +
						      "interrupted.");
		else 
		    throw e;
	    }
    }

    public synchronized void close( boolean skip_remaining_tasks )
    {
	if (skip_remaining_tasks)
	    {
		t.safeStop();
		t.interrupt();
	    }
	else
	    gentle_close_requested = true;
    }

    public synchronized void close()
    { this.close( true ); }

    public synchronized List getStrandedTasks()
    {
	try
	    {
		while (gentle_close_requested && taskList != null)
		    this.wait();
		return strandedTasks;
	    }
	catch (InterruptedException e) 
	    {
		// very, very rare I think...
		// if necessary I'll try a more complex solution, but I don't think
		// it's worth it.
		//e.printStackTrace();
		if ( logger.isLoggable( MLevel.WARNING ) )
		    logger.log( MLevel.WARNING, 
				Thread.currentThread() + " interrupted while waiting for stranded tasks from CarefulRunnableQueue.",
				e );

		throw new RuntimeException(Thread.currentThread() + 
					   " interrupted while waiting for stranded tasks from CarefulRunnableQueue.");
	    }
    }

    private synchronized Runnable dequeueRunnable()
    {
	Runnable r = (Runnable) taskList.get(0);
	taskList.remove(0);
	return r;
    }

    private synchronized void awaitTask() throws InterruptedException
    {
	while (taskList.size() == 0) 
	    {
		if ( gentle_close_requested )
		    {
			t.safeStop(); // remember t == Thread.currentThread()
			t.interrupt();
		    }
		this.wait();
	    }
    }

    class TaskThread extends Thread
    {
	boolean should_stop = false;

	TaskThread()
	{ super("CarefulRunnableQueue.TaskThread"); }

	public synchronized void safeStop()
	{ should_stop = true; }

	private synchronized boolean shouldStop()
	{ return should_stop; }

	public void run()
	{
	    try
		{
		    while (! shouldStop() )
			{
			    try
				{
				    awaitTask();
				    Runnable r = dequeueRunnable();
				    try
					{ r.run(); }
				    catch (Exception e)
					{
					    //System.err.println(this.getClass().getName() + " -- Unexpected exception in task!");
					    //e.printStackTrace();

					    if ( logger.isLoggable( MLevel.WARNING ) )
						logger.log(MLevel.WARNING, this.getClass().getName() + " -- Unexpected exception in task!", e);
					}
				}
			    catch (InterruptedException e)
				{
				    if (shutdown_on_interrupt)
					{
					    CarefulRunnableQueue.this.close( false );
// 					    if (Debug.DEBUG && Debug.TRACE >= Debug.TRACE_MED ) 
// 						System.err.println( this.toString() + 
// 								    " interrupted. Shutting down after current tasks" +
// 								    " have completed." );
					    if ( logger.isLoggable( MLevel.INFO ) )
						logger.info(this.toString() + 
							    " interrupted. Shutting down after current tasks" +
							    " have completed." );
					}
				    else
					{
// 					    if (Debug.DEBUG && Debug.TRACE >= Debug.TRACE_MED ) 
// 						System.err.println( this.toString() + 
// 								    " received interrupt. IGNORING." );
					    logger.info(this.toString() + " received interrupt. IGNORING." );
					}
				}
			}
		}
// 	    catch (ThreadDeath td) //DEBUG ONLY -- remove soon, swaldman 08-Jun-2003
// 		{
// 		    System.err.print("c3p0-TRAVIS: ");
// 		    System.err.println(this.getName() + ": Some bastard used the deprecated stop() method to kill me!!!!");
// 		    td.printStackTrace();
// 		    throw td;
// 		}
// 	    catch (Throwable t) //DEBUG ONLY -- remove soon, swaldman 08-Jun-2003
// 		{
// 		    System.err.print("c3p0-TRAVIS: ");
// 		    System.err.println(this.getName() + ": Some unexpected Throwable occurred and killed me!!!!");
// 		    t.printStackTrace();
// 		    if (t instanceof Error)
// 			throw (Error) t;
// 		    else if (t instanceof RuntimeException)
// 			throw (RuntimeException) t;
// 		    else
// 			throw new InternalError( t.toString() ); //we don't expect any checked Exceptions can happen here.
// 		}
	    finally
		{
		    synchronized ( CarefulRunnableQueue.this )
			{
			    strandedTasks = Collections.unmodifiableList( taskList );
			    taskList = null;
			    t = null;
			    CarefulRunnableQueue.this.notifyAll(); //if anyone is waiting for stranded tasks...
			    //System.err.print("c3p0-TRAVIS: ");
			    //System.err.println("TaskThread dead. strandedTasks: " + strandedTasks);
			}
		}
	}
    }
}

