/**
 *   Copyright 2007 Y.Murakamin
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */
package net.murakamin.sticker;

import java.io.File;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.Hashtable;
import java.util.List;
import java.util.Map;
import java.util.Vector;

import net.murakamin.sticker.commands.exception.CommandExecutionException;

/**
 * connection pool that manages data base connection
 * 
 * @author Y.Murakamin
 * @see net.murakamin.sticker.ConnectionPool
 */
final class ConnectionPoolImpl implements ConnectionPool
{
	private String currentConnectionName = null;
	private Connection hsqlDbConnection = null;
	private final List<String> localTableNames = new Vector<String>();
	private final Map<String, Connection> remoteConnections = new Hashtable<String, Connection>();

	/**
	 * constractor
	 */
	ConnectionPoolImpl()
	{
		super();
	}

	/**
	 * The table name made for HSQLDB is set. Sticker judges making the table by
	 * using the name defined here.
	 * 
	 * @param localTableName
	 *            HSQLDB table name
	 * @see #existsLocalTable(String)
	 * @see net.murakamin.sticker.ConnectionPool#addLocalTableName(String)
	 */
	public void addLocalTableName(final String localTableName)
	{
		this.localTableNames.add(localTableName);
	}

	/**
	 * It connects it with HSQLDB.It connects it with HSQLDB of the inmemory
	 * mode.
	 * 
	 * @throws ClassNotFoundException
	 * @throws SQLException
	 */
	void connectToHsqlDb() throws ClassNotFoundException, SQLException
	{
		String connectURL = "jdbc:hsqldb:mem:sticker;shutdown=false";

		Class.forName("org.hsqldb.jdbcDriver");
		this.hsqlDbConnection = DriverManager.getConnection(connectURL, "sa",
		        "");
	}

	/**
	 * It connects it with HSQLDB.It connects it with HSQLDB of the inprocess
	 * mode.
	 * 
	 * @param dataFile
	 *            Directory at HSQLDB data storage destination
	 * @throws ClassNotFoundException
	 * @throws SQLException
	 */
	void connectToHsqlDb(final File dataFile) throws ClassNotFoundException,
	        SQLException
	{
		String connectURL = "jdbc:hsqldb:file:/$$DataFile$$/sticker;shutdown=true";

		Class.forName("org.hsqldb.jdbcDriver");
		connectURL = connectURL.replace("$$DataFile$$", dataFile
		        .getAbsolutePath());

		this.hsqlDbConnection = DriverManager.getConnection(connectURL, "sa",
		        "");
	}

	public void disConnectAllConnections() throws SQLException
	{
		this.disConnectToHsqlDb();
		this.disConnectToRemoteConnections();
	}

	/**
	 * The HSQLDB data base connection is closed.
	 */
	protected void disConnectToHsqlDb()
	{
		if (this.hsqlDbConnection != null)
		{
			try
			{
				if (!this.hsqlDbConnection.isClosed())
				{
					try
					{
						this.hsqlDbConnection.close();
					} catch (Exception e)
					{
						// do nothing
					}
				}
			} catch (SQLException se)
			{
				throw new UnsupportedOperationException(se);
			} finally
			{
				this.hsqlDbConnection = null;
			}
		}
	}

	/**
	 * The all remote data base connection is closed.
	 * 
	 * @throws SQLException
	 */
	public void disConnectToRemoteConnections() throws SQLException
	{
		for (Connection con : this.remoteConnections.values())
		{
			if (con != null)
			{
				if (!con.isClosed())
				{
					try
					{
						con.close();
					} catch (Exception e)
					{
						// do nothing
					}
				}
			}
		}
	}

	/**
	 * When the table of the specified name exists in HSQLDB, true is returned.
	 * There is a possibility that CREATE TABLE is not actually done for making
	 * to use and to judge the name set with {@link #addLocalTableName(String)}
	 * method.
	 * 
	 * @param localTableName
	 *            Table name to be checked
	 * @return When the table of the specified name exists in HSQLDB, true is
	 *         returned.
	 * @see #addLocalTableName(String)
	 * @see net.murakamin.sticker.ConnectionPool#existsLocalTable(String)
	 */
	public boolean existsLocalTable(final String localTableName)
	{
		return this.localTableNames.contains(localTableName);
	}

	/**
	 * The connection of HSQLDB is acquired.
	 * 
	 * @see net.murakamin.sticker.ConnectionPool#getLocalConnection()
	 * @return The connection of HSQLDB
	 * @throws CommandExecutionException
	 */
	public Connection getLocalConnection() throws CommandExecutionException
	{
		return this.hsqlDbConnection;
	}

	/**
	 * The connection to the remote data base that should be connected now is
	 * acquired. The remote data base that should be connected now is set
	 * depending on the {@link #setCurrentConnectionName(String)} method.
	 * 
	 * @return It is a remote data base connection in the specified name for
	 *         being able the string it.
	 * 
	 * @see net.murakamin.sticker.ConnectionPool#getRemoteConnection()
	 * @see #setCurrentConnectionName(String)
	 * @throws CommandExecutionException
	 */
	public Connection getRemoteConnection() throws CommandExecutionException
	{
		Connection result = this.remoteConnections
		        .get(this.currentConnectionName);
		if (result == null)
		{
			throw new CommandExecutionException(
			        Messages
			                .getString("sticker.err.net.murakamin.sticker.ConnectionPoolImpl.cantfindconnection")
			                + this.currentConnectionName);
		}

		return result;
	}

	/**
	 * The remote database connectivility name that should be connected now is
	 * set.
	 * 
	 * @param name
	 *            The remote database connectivility name
	 * @see #getRemoteConnection()
	 * @see net.murakamin.sticker.ConnectionPool#setCurrentConnectionName(String)
	 */
	public void setCurrentConnectionName(final String name)
	{
		this.currentConnectionName = name;
	}

	/**
	 * The connection of a remote data base is set to the connection pool. At
	 * this time, the connection name to identify the connection is specified.
	 * 
	 * @param name
	 *            connection name
	 * @param con
	 *            remote data base connection
	 * 
	 * @see net.murakamin.sticker.ConnectionPool#setRemoteConnection(String,
	 *      Connection)
	 * @see #getRemoteConnection()
	 * @see #setCurrentConnectionName(String)
	 * @throws CommandExecutionException
	 * 
	 */
	public void setRemoteConnection(final String name, final Connection con)
	        throws CommandExecutionException
	{
		if (this.remoteConnections.containsKey(name))
		{
			throw new CommandExecutionException(
			        Messages
			                .getString("sticker.err.net.murakamin.sticker.ConnectionPoolImpl.duplicateconnection")
			                + name);
		}

		this.remoteConnections.put(name, con);
	}
}
