// **********************************************************************
//
// Copyright (c) 2003-2006 ZeroC, Inc. All rights reserved.
//
// This copy of Ice is licensed to you under the terms described in the
// ICE_LICENSE file included in this distribution.
//
// **********************************************************************

package Freeze;

class SharedDbEnv implements com.sleepycat.db.ErrorHandler, Runnable
{
    public static SharedDbEnv
    get(Ice.Communicator communicator, String envName, com.sleepycat.db.Environment dbEnv)
    {
	MapKey key = new MapKey(envName, communicator);

	SharedDbEnv result;

	synchronized(_map) 
	{
	    result = (SharedDbEnv)_map.get(key);
	    if(result == null)
	    {
		try
		{
		    result = new SharedDbEnv(key, dbEnv);
		}
		catch(com.sleepycat.db.DatabaseException dx)
		{
		    DatabaseException ex = new DatabaseException();
		    ex.initCause(dx);
		    ex.message = errorPrefix(envName) + "creation: " + dx.getMessage();
		    throw ex;
		}

		Object previousValue = _map.put(key, result);
		assert(previousValue == null);
	    }
	    else
	    {
		result._refCount++;
	    }
	}

	//
	// Make sure the result if fully initialized
	//
	result.init();
	return result;
    }
    
    public String 
    getEnvName()
    {
	return _key.envName;
    }

    public Ice.Communicator
    getCommunicator()
    {
	return _key.communicator;
    }

    public com.sleepycat.db.Environment
    getEnv()
    {
	return _dbEnv;
    }

    public SharedDb
    getCatalog()
    {
	return _catalog;
    }

    public void
    close()
    {
	synchronized(_map) 
	{
	    if(--_refCount == 0)
	    {	    
		//
		// Remove from map
		//
		Object value = _map.remove(_key);
		assert(value == this);

		//
		// Join thread
		//
		synchronized(this)
		{
		    _done = true;
		    notify();
		}

		for(;;)
		{
		    if(_thread != null)
		    {
			try
			{
			    _thread.join();
			    _thread = null;
			    break;
			}
			catch(InterruptedException ex)
			{
			}
		    }
		}

		//
		// Release catalog
		//
		if(_catalog != null)
		{
		    _catalog.close();
		    _catalog = null;
		}

		if(_trace >= 1)
		{
		    _key.communicator.getLogger().trace("Freeze.DbEnv", "closing database environment \"" +
							_key.envName + "\"");
		}

		//
		// Keep lock to prevent somebody else from re-opening this DbEnv
		// before it's closed.
		//
		try
		{
		    _dbEnv.close();
		}
		catch(com.sleepycat.db.DatabaseException dx)
		{
		    DatabaseException ex = new DatabaseException();
		    ex.initCause(dx);
		    ex.message = errorPrefix(_key.envName) + "close: " + dx.getMessage();
		    throw ex;
		}
	    }
	}
    }

    public void
    run()
    {
	for(;;)
	{
	    synchronized(this)
	    {
		while(!_done)
		{
		    try
		    {
			wait(_checkpointPeriod);
		    }
		    catch(InterruptedException ex)
		    {
			continue;
		    }
		    break;
		}
		if(_done)
		{
		    return;
		}
	    }
	    
	    if(_trace >= 2)
	    {
		_key.communicator.getLogger().trace("Freeze.DbEnv", "checkpointing environment \"" + _key.envName +
						    "\"");
	    }

	    try
	    {
		com.sleepycat.db.CheckpointConfig config = new com.sleepycat.db.CheckpointConfig();
		config.setKBytes(_kbyte);
		_dbEnv.checkpoint(config);
	    }
	    catch(com.sleepycat.db.DatabaseException dx)
	    {
		_key.communicator.getLogger().warning("checkpoint on DbEnv \"" + _key.envName +
						      "\" raised DbException: " + dx.getMessage());
	    }
	}
    }
    
    public void 
    error(com.sleepycat.db.Environment env, String errorPrefix, String message)
    {
	_key.communicator.getLogger().error("Freeze database error in DbEnv \"" + _key.envName + "\": " + message);
    }

    protected void 
    finalize()
    {
	assert(_refCount == 0);
    }

    private
    SharedDbEnv(MapKey key, com.sleepycat.db.Environment dbEnv)
	throws com.sleepycat.db.DatabaseException
    {	
	_key = key;
	_dbEnv = dbEnv;
	_ownDbEnv = (dbEnv == null);

	Ice.Properties properties = key.communicator.getProperties();
	_trace = properties.getPropertyAsInt("Freeze.Trace.DbEnv");

	if(_ownDbEnv)
	{
	    com.sleepycat.db.EnvironmentConfig config = new com.sleepycat.db.EnvironmentConfig();

	    config.setErrorHandler(this);
	    config.setInitializeLocking(true);
	    config.setInitializeLogging(true);
	    config.setInitializeCache(true);
	    config.setAllowCreate(true);
	    config.setTransactional(true);

	    //
	    // Deadlock detection
	    //
	    config.setLockDetectMode(com.sleepycat.db.LockDetectMode.YOUNGEST);

	    String propertyPrefix = "Freeze.DbEnv." + _key.envName;
	    if(properties.getPropertyAsInt(propertyPrefix + ".DbRecoverFatal") != 0)
	    {
		config.setRunFatalRecovery(true);
	    }
	    else
	    {
		config.setRunRecovery(true);
	    }

	    if(properties.getPropertyAsIntWithDefault(propertyPrefix + ".DbPrivate", 1) != 0)
	    {
		config.setPrivate(true);
	    }

	    if(properties.getPropertyAsIntWithDefault(propertyPrefix + ".OldLogsAutoDelete", 1) != 0)
	    {
		config.setLogAutoRemove(true);
	    }

	    if(_trace >= 1)
	    {
		_key.communicator.getLogger().trace("Freeze.DbEnv", "opening database environment \"" +
						    _key.envName + "\"");
	    }

	    //
	    // TODO: FREEZE_DB_MODE
	    //
	    
	    try
	    {
		String dbHome = properties.getPropertyWithDefault(propertyPrefix + ".DbHome", _key.envName);
		java.io.File home = new java.io.File(dbHome);
		_dbEnv = new com.sleepycat.db.Environment(home, config);
	    }
	    catch(java.io.FileNotFoundException dx)
	    {
		NotFoundException ex = new NotFoundException();
		ex.initCause(dx);
		ex.message = errorPrefix(_key.envName) + "open: " + dx.getMessage();
		throw ex;
	    }
	    
	    //
	    // Default checkpoint period is every 120 seconds
	    //
	    _checkpointPeriod =
		properties.getPropertyAsIntWithDefault(propertyPrefix + ".CheckpointPeriod", 120) * 1000;
	    
	    _kbyte = properties.getPropertyAsIntWithDefault(propertyPrefix + ".PeriodicCheckpointMinSize", 0);

	    String threadName;
	    String programName = properties.getProperty("Ice.ProgramName");
	    if(programName.length() > 0)
	    {
		threadName = programName + "-";
	    }
	    else
	    {
		threadName = "";
	    }
	    threadName += "FreezeCheckpointThread(" + _key.envName + ")";

	    if(_checkpointPeriod > 0)
	    {
		_thread = new Thread(this, threadName);
		_thread.start();
	    }    
	}

	_refCount = 1;
    }

    private synchronized void
    init()
    {
	if(_catalog == null)
	{
	    _catalog = SharedDb.openCatalog(this);
	}
    }

    private static String
    errorPrefix(String envName)
    {
	return "DbEnv(\"" + envName + "\"): ";
    }

    private static class MapKey
    {
	final String envName;
	final Ice.Communicator communicator;
	
	MapKey(String envName, Ice.Communicator communicator)
	{
	    this.envName = envName;
	    this.communicator = communicator;
	}

	public boolean
	equals(Object o)
	{   
	    try
	    {
		MapKey k = (MapKey)o;
		return (communicator == k.communicator) && envName.equals(k.envName);
	    }
	    catch(ClassCastException ex)
	    {
		communicator.getLogger().trace("Freeze.DbEnv", "equals cast failed");
		return false;
	    }
	}
	
	public int hashCode()
	{
	    return envName.hashCode() ^ communicator.hashCode();
	}
    }

    private MapKey _key;
    private com.sleepycat.db.Environment _dbEnv;
    private boolean _ownDbEnv;
    private SharedDb _catalog;
    private int _refCount = 0;
    private boolean _done = false;
    private int _trace = 0;
    private long _checkpointPeriod = 0;
    private int _kbyte = 0;
    private Thread _thread;

    //
    // Hash map of (MapKey, SharedDbEnv)
    //
    private static java.util.Map _map = new java.util.HashMap();
}
