/**************************************************************
 * 
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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 testlib.uno;

import com.sun.star.beans.PropertyValue;
import com.sun.star.beans.PropertyState;
import com.sun.star.beans.XPropertySet;
import com.sun.star.container.ElementExistException;
import com.sun.star.container.XNameAccess;
import com.sun.star.frame.XStorable;
import com.sun.star.lang.XMultiServiceFactory;
import com.sun.star.sdb.XDocumentDataSource;
import com.sun.star.sdb.XOfficeDatabaseDocument;
import com.sun.star.sdbc.SQLException;
import com.sun.star.sdbc.XCloseable;
import com.sun.star.sdbc.XConnection;
import com.sun.star.sdbc.XDataSource;
import com.sun.star.sdbc.XStatement;
import com.sun.star.sdbcx.XAppend;
import com.sun.star.sdbcx.XTablesSupplier;
import com.sun.star.uno.UnoRuntime;
import com.sun.star.util.CloseVetoException;

import java.util.HashMap;
import java.util.Iterator;
import java.util.Set;
import java.io.File;

import org.openoffice.test.common.FileUtil;


public class DBUtil {
	// the service factory
	protected static XMultiServiceFactory m_orb;
	// the URL of the temporary file used for the database document
	protected static String m_databaseDocumentFile;
	// the database document
	protected static XOfficeDatabaseDocument m_databaseDocument;
	// the data source belonging to the database document
	protected static XDataSource m_dataSource;
	// the default connection
	protected static XConnection m_connection;


	static public void createNewDocument(final XMultiServiceFactory orb)
			throws Exception {
		m_orb = orb;
		createDBDocument();
	}

	static public void loadNewDocument(final XMultiServiceFactory orb,
			final String _existingDocumentURL) throws Exception {
		m_orb = orb;
		getDocument(_existingDocumentURL);
	}

	/**
	 * creates an empty database document in a temporary location
	 */
	public static void createDBDocument() throws Exception {
		final File documentFile = File.createTempFile("testdb", ".odb");
		if (documentFile.exists())
			documentFile.delete();
		m_databaseDocumentFile = FileUtil.getUrl(documentFile);
		m_databaseDocument = (XOfficeDatabaseDocument) UnoRuntime
				.queryInterface(
						XOfficeDatabaseDocument.class,
						m_orb.createInstance("com.sun.star.sdb.OfficeDatabaseDocument"));
		m_dataSource = m_databaseDocument.getDataSource();

		final XPropertySet dsProperties = (XPropertySet) UnoRuntime
				.queryInterface(XPropertySet.class,
						m_databaseDocument.getDataSource());
		dsProperties.setPropertyValue("URL", "sdbc:embedded:hsqldb");

		final XStorable storable = (XStorable) UnoRuntime.queryInterface(
				XStorable.class, m_databaseDocument);
		storable.storeAsURL(m_databaseDocumentFile,
				new PropertyValue[] { new PropertyValue("PickListEntry", 0,
						false, PropertyState.DIRECT_VALUE) });
	}

	
	public static void getDocument(final String _docURL) throws Exception {
		m_databaseDocumentFile = _docURL;

		final XNameAccess dbContext = (XNameAccess) UnoRuntime.queryInterface(
				XNameAccess.class,
				m_orb.createInstance("com.sun.star.sdb.DatabaseContext"));
		final XDocumentDataSource dataSource = (XDocumentDataSource) UnoRuntime.queryInterface(
				XDocumentDataSource.class, dbContext.getByName(_docURL));

		m_databaseDocument = dataSource.getDatabaseDocument();
		m_dataSource = m_databaseDocument.getDataSource();
	}

	/**
	 * drops the table with a given name
	 * 
	 * @param _name
	 *            the name of the table to drop
	 * @param _ifExists
	 *            TRUE if it should be dropped only when it exists.
	 */
	static public void dropTable(final String _name, final boolean _ifExists)
			throws SQLException {
		final StringBuffer dropStatement = new StringBuffer("DROP TABLE \"");
		dropStatement.append(_name);
		if (_ifExists) {
			dropStatement.append("\" IF EXISTS");
		}
		executeSQL(dropStatement.toString());
	}

	static public void createTable(String _name,
			HsqlColumnDescriptor[] _columns, final boolean _dropIfExists)
			throws SQLException {
		if (_dropIfExists) {
			dropTable(_name, true);
		}
		createTable(_name, _columns);
	}

	/**
	 * creates a table
	 */
	static public void createTable(String _name, HsqlColumnDescriptor[] _columns)
			throws SQLException {
		StringBuffer createStatement = new StringBuffer(
				"CREATE CACHED TABLE \"");
		createStatement.append(_name);
		createStatement.append("\" ( ");

		String primaryKeyList = "";

		final HashMap foreignKeys = new HashMap();
		final HashMap foreignKeyRefs = new HashMap();

		final HsqlColumnDescriptor[] columns = _columns;
		for (int i = 0; i < columns.length; ++i) {
			if (i > 0) {
				createStatement.append(", ");
			}

			createStatement.append("\"" + columns[i].getName());
			createStatement.append("\" " + columns[i].getTypeName());

			if (columns[i].isRequired()) {
				createStatement.append(" NOT NULL");
			}

			if (columns[i].isPrimaryKey()) {
				if (primaryKeyList.length() > 0) {
					primaryKeyList += ", ";
				}
				primaryKeyList += "\"" + columns[i].getName() + "\"";
			}

			if (columns[i].isForeignKey()) {
				final String foreignTable = columns[i].getForeignTable();

				String foreignKeysForTable = foreignKeys
						.containsKey(foreignTable) ? (String) foreignKeys
						.get(foreignTable) : "";
				if (foreignKeysForTable.length() > 0) {
					foreignKeysForTable += ", ";
				}
				foreignKeysForTable += "\"" + columns[i].getName() + "\"";
				foreignKeys.put(foreignTable, foreignKeysForTable);

				final StringBuffer foreignKeyRefsForTable = new StringBuffer(
						foreignKeyRefs.containsKey(foreignTable) ? (String) foreignKeyRefs
								.get(foreignTable) : "");
				if (foreignKeyRefsForTable.length() > 0) {
					foreignKeyRefsForTable.append(", ");
				}
				foreignKeyRefsForTable.append("\""
						+ columns[i].getForeignColumn() + "\"");
				foreignKeyRefs.put(foreignTable,
						foreignKeyRefsForTable.toString());
			}
		}

		if (primaryKeyList.length() > 0) {
			createStatement.append(", PRIMARY KEY (");
			createStatement.append(primaryKeyList);
			createStatement.append(')');
		}

		final Set foreignKeyTables = foreignKeys.keySet();
		for (final Iterator foreignKey = foreignKeyTables.iterator(); foreignKey
				.hasNext();) {
			final String foreignTable = (String) foreignKey.next();

			createStatement.append(", FOREIGN KEY (");
			createStatement.append((String) foreignKeys.get(foreignTable));
			createStatement.append(") REFERENCES \"");
			createStatement.append(foreignTable);
			createStatement.append("\"(");
			createStatement.append((String) foreignKeyRefs.get(foreignTable));
			createStatement.append(')');
		}

		createStatement.append(')');

		// System.err.println( createStatement );
		executeSQL(createStatement.toString());
	}


	/**
	 * executes the given SQL statement via the defaultConnection
	 */
	static public void executeSQL(final String statementString)
			throws SQLException {
		final XStatement statement = defaultConnection().createStatement();
		statement.execute(statementString);
	}

	/**
	 * returns a connection to the database
	 * 
	 * Multiple calls to this method return the same connection. The
	 * DbaseDatabase object keeps the ownership of the connection, so you don't
	 * need to (and should not) dispose/close it.
	 */
	static public XConnection defaultConnection() throws SQLException {
		if (m_connection == null)
			m_connection = m_databaseDocument.getDataSource().getConnection("",
					"");

		return m_connection;
	}

	/**
	 * closes the database document
	 * 
	 * Any CloseVetoExceptions fired by third parties are ignored, and any
	 * reference to the database document is released.
	 */
	static public void close() {
		// close connection
		final XCloseable closeConn = (XCloseable) UnoRuntime.queryInterface(
				XCloseable.class, m_connection != null ? m_connection : null);
		if (closeConn != null) {
			try {
				closeConn.close();
			} catch (SQLException e) {
			}
		}
		m_connection = null;

		// close document
		final com.sun.star.util.XCloseable closeDoc = (com.sun.star.util.XCloseable) UnoRuntime
				.queryInterface(com.sun.star.util.XCloseable.class,
						m_databaseDocument);
		if (closeDoc != null) {
			try {
				closeDoc.close(true);
			} catch (CloseVetoException e) {
			}
		}
		m_databaseDocument = null;
	}

	/**
	 * returns the underlying database document
	 */
	static public XOfficeDatabaseDocument getDatabaseDocument() {
		return m_databaseDocument;
	}

	static public String getDocumentURL() {
		return m_databaseDocumentFile;
	}
}
