// This software is released into the Public Domain.  See copying.txt for details.
package org.openstreetmap.osmosis.pgsnapshot.common;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.sql.DataSource;

import org.apache.commons.dbcp.BasicDataSource;
import org.openstreetmap.osmosis.core.OsmosisRuntimeException;
import org.openstreetmap.osmosis.core.database.DatabaseLoginCredentials;
import org.openstreetmap.osmosis.core.lifecycle.Closeable;


/**
 * Creates or obtains a datasource and manages its lifecycle based on the login
 * credentials provided.
 * 
 * @author Brett Henderson
 */
public final class DataSourceManager implements Closeable {
	
	private static final Logger LOG = Logger.getLogger(DataSourceManager.class.getName());
	
	private DataSource dataSource;
	private BasicDataSource localDataSource;
	private DatabaseLoginCredentials credentials;


	/**
	 * Creates a new instance.
	 * 
	 * @param credentials
	 *            Contains all information required to connect to the database.
	 */
	public DataSourceManager(DatabaseLoginCredentials credentials) {
		this.credentials = credentials;
	}
	
	
	private void createDataSource() {
		localDataSource = new BasicDataSource();
		
		localDataSource.setDriverClassName("org.postgresql.Driver");
		localDataSource.setUrl("jdbc:postgresql://" + credentials.getHost() + "/" + credentials.getDatabase()
    			/*+ "?loglevel=2"*/);
        
		localDataSource.setUsername(credentials.getUser());
		localDataSource.setPassword(credentials.getPassword());
        
        dataSource = localDataSource;
	}
	
	
	private void loadDatasource() {
		InitialContext cxt;
		String jndiLocation;
		
		jndiLocation = credentials.getDatasourceJndiLocation();
		
		try {
			cxt = new InitialContext();
		} catch (NamingException e) {
			throw new OsmosisRuntimeException("Unable to create an initial JNDI context.", e);
		}
		
		try {
			dataSource = (DataSource) cxt.lookup(jndiLocation);
		} catch (NamingException e) {
			throw new OsmosisRuntimeException("Unable to locate the datasource (" + jndiLocation + ")", e);
		}
	}
	
	
	private Connection createConnectionFromDriverManager() {
		try {
			// Register the database driver.
			try {
				Class.forName("org.postgresql.Driver");
			} catch (ClassNotFoundException e) {
				throw new OsmosisRuntimeException("Unable to find database driver.", e);
			}
			
			return DriverManager.getConnection(
				"jdbc:postgresql://" + credentials.getHost() + "/"
				+ credentials.getDatabase(),
		    	// + "?logLevel=2"
				credentials.getUser(),
				credentials.getPassword()
		    );
			
		} catch (SQLException e) {
			throw new OsmosisRuntimeException("Unable to establish a new database connection.", e);
		}
	}
	
	
	/**
	 * Obtains a data source.
	 * 
	 * @return The database source.
	 */
	public DataSource getDataSource() {
		if (dataSource == null) {
			String jndiLocation;
			
			jndiLocation = credentials.getDatasourceJndiLocation();
			
			if (jndiLocation != null) {
				LOG.finer("Retrieving a data source from JNDI.");
				
				loadDatasource();
				
			} else {
				LOG.finer("Creating a new locally managed data source.");
				
				createDataSource();
			}
		}
		
		return dataSource;
	}
	
	
	/**
	 * Obtains a single connection.
	 * 
	 * @return The connection.
	 */
	public Connection getConnection() {
		String jndiLocation;
		Connection connection;
		
		jndiLocation = credentials.getDatasourceJndiLocation();
		
		if (dataSource == null && jndiLocation != null) {
			LOG.finer("Retrieving a data source from JNDI.");
			loadDatasource();
		}
		
		if (dataSource == null) {
			LOG.finer("Creating a new database connection from DriverManager.");
			connection = createConnectionFromDriverManager();
		} else {
			try {
				connection = dataSource.getConnection();
			} catch (SQLException e) {
				throw new OsmosisRuntimeException("Unable to obtain a connection from the datasource.", e);
			}
		}
		
		return connection;
	}


	/**
	 * {@inheritDoc}
	 */
	@Override
	public void close() {
		if (localDataSource != null) {
			try {
				localDataSource.close();
			} catch (SQLException e) {
				LOG.log(Level.WARNING, "Unable to cleanup the database connection pool.", e);
			}
			
			localDataSource = null;
			dataSource = null;
		}
	}
}
