/*******************************************************************************
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 * 
 * This library 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. See the
 * GNU Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
 * USA
 * 
 * Contact Info:
 * 	Bruce Donald
 * 	Duke University
 * 	Department of Computer Science
 * 	Levine Science Research Center (LSRC)
 * 	Durham
 * 	NC 27708-0129 
 * 	USA
 * 	brd@cs.duke.edu
 * 
 * Copyright (C) 2011 Jeffrey W. Martin and Bruce R. Donald
 * 
 * <signature of Bruce Donald>, April 2011
 * Bruce Donald, Professor of Computer Science
 ******************************************************************************/


package edu.duke.donaldLab.jdshot.disco.cgal;

import java.io.File;
import java.lang.ref.ReferenceQueue;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;

public class ShotCgal
{
	/**************************
	 *   Definitions
	 **************************/
	
	private static final String LibName = "shotCgal." + ShotCgal.getLibTag();
	private static final String LibPath = System.getProperty( "user.dir" ) + File.separator + "lib" + File.separator + System.mapLibraryName( LibName );
	
	
	/**************************
	 *   Data Members
	 **************************/
	
	private static boolean m_isInitialized;
	private static ReferenceQueue<AbstractCleanable> m_referenceQueue;
	private static Set<CleanablePhantomReference> m_references;
	private static Thread m_shutdownHook;
	
	
	/**************************
	 *   Constructors
	 **************************/
	
	static
	{
		m_isInitialized = false;
		m_referenceQueue = null;
		m_references = null;
		m_shutdownHook = new Thread( )
		{
			@Override
			public void run( )
			{
				if( m_isInitialized )
				{
					localCleanup();
				}
			}
		};
	}
	
	private ShotCgal( )
	{
		// do nothing
	}


	/**************************
	 *   Static Methods
	 **************************/
	
	public static void init( )
	{
		if( m_isInitialized )
		{
			return;
		}
		
		// first, try to load the library from the current directory 
		if( new File( LibPath ).exists() )
		{
			try
			{
				System.load( LibPath );
			}
			catch( UnsatisfiedLinkError err )
			{
				String message =
					"Unable to load required library, shotCgal!"
					+ " Library found at path:"
					+ "\n" + LibPath
					+ "\nbut loading failed. If running on linux, make sure CGAL package is installed.";
				throw new Error( message, err );
			}
		}
		else
		{
			try
			{
				System.loadLibrary( LibName );
			}
			catch( UnsatisfiedLinkError err )
			{
				String message =
					"Unable to find required library, shotCgal!"
					+ " These directories were checked:"
					+ "\n" + System.getProperty( "java.library.path" )
					+ "\nOr, try putting it here:"
					+ "\n" + LibPath;
				throw new Error( message, err );
			}
		}
		
		// init defaults
		m_referenceQueue = new ReferenceQueue<AbstractCleanable>();
		m_references = Collections.synchronizedSet( new HashSet<CleanablePhantomReference>() );
		m_isInitialized = true;
		
		// add a jvm shutdown hook to make sure resources get cleaned up
		Runtime.getRuntime().addShutdownHook( m_shutdownHook );
	}
	
	public static void cleanup( )
	{
		if( !m_isInitialized )
		{
			return;
		}
		
		// remove the shutdown hook
		Runtime.getRuntime().removeShutdownHook( m_shutdownHook );
		
		localCleanup();
	}
	
	public static int cleanupUnreferenced( )
	{
		int numCleanedUp = 0;
		CleanablePhantomReference ref = null;
		while( ( ref = (CleanablePhantomReference)m_referenceQueue.poll() ) != null )
		{
			// cleanup native resources
			ref.cleanup();
			
			// break reference and allow the heap memory to be released
			ref.clear();
			m_references.remove( ref );
			
			numCleanedUp++;
		}
		
		return numCleanedUp;
	}
	
	
	/**************************
	 *   Static Functions
	 **************************/
	
	private static String getLibTag( )
	{
		String arch = System.getProperty( "os.arch" ).toLowerCase();
		if( arch.equals( "i386" ) || arch.equals( "x86" ) )
		{
			return "x86";
		}
		if( arch.startsWith( "amd64" ) || arch.startsWith( "x86_64" ) )
		{
			return "x86_64";
		}
		
		String os = System.getProperty( "os.name" );
		throw new Error( "Unable to load ShotCgal library. Your operating system and architecture (" + os + ", " + arch + ") are not currently supported." );
	}
	
	protected static void addReference( AbstractCleanable obj, Cleaner cleaner )
	{
		init();
		m_references.add( new CleanablePhantomReference( obj, m_referenceQueue, cleaner ) );
	}
	
	protected static void localCleanup( )
	{
		// clear out all references
		while( m_referenceQueue.poll() != null );
		m_references.clear();
		m_referenceQueue = null;
		m_references = null;
		
		// free native resources
		nativeCleanup();
		
		m_isInitialized = false;
	}
	
	private static native void nativeCleanup( );
}
