/*
 * SQLUtilities.java
 *
 * Created on 2 de agosto de 2004, 15:39
 */

package com.fenalco.utilities.database;

import java.math.BigDecimal;
import java.sql.Connection;
import java.sql.Date;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.sql.Statement;
import java.sql.Timestamp;
import java.util.Hashtable;
import java.util.Vector;

/**
 * 
 * @author Andrés Romero Rodríguez
 * 
 * esta clase proporciona acceso a la base de datos mediante consultas sql.
 * Todos los métodos que proporciona son estáticos
 */
public class SQLUtilities
{
	/**
	 * este método ejecuta una consulta sql a la base de datos y retorna el
	 * resultado como un Vector en donde cada fila representa un registro, ese
	 * registro esta almacenado como un Hashtable en donde cada llave es el
	 * nombre del campo y al valor es el contenido de ese campo
	 * 
	 * @throws QueryException
	 *             si no se puede ejecutar la consulta
	 * @param sql
	 *            la cadena sql que se va a ejecutar
	 * @return un Vector de hashtables; en donde cada hashtable representa una
	 *         fila dentro de la tabla resultante de la consulta y los
	 *         resultados están organizados de forma que el key es el nombre de
	 *         la columna y el value es el valor de esa columna en la fila
	 *         indicada
	 */
	public static Vector query( String sql ) throws QueryException
	{
		Vector res = new Vector();
		DataBaseConnection dbc = null;
		Connection c = null;
		try
		{
			dbc = DataBaseConnection.getInstance();
			c = dbc.getConnection();
			//            c.setAutoCommit( true );
			Statement st = c.createStatement();

			// aqui reparo la consulta ////
			sql = repairQuery(sql, dbc);
			///////////////

//			System.out.println( sql );
			ResultSet rs = st.executeQuery(sql);

			ResultSetMetaData metaData = rs.getMetaData();
			Vector columnNames = new Vector();
			//            Vector columnTypes = new Vector();
			int count = metaData.getColumnCount();

			for( int i = 1; i <= count; i++ )
			{
				columnNames.addElement(metaData.getColumnName(i));
				//                columnTypes.addElement( metaData.getColumnClassName( i ) );
			}

			while( rs.next() )
			{
				Hashtable row = new Hashtable();

				for( int i = 0; i < count; i++ )
				{
					String columnName = (String) columnNames.elementAt(i);
					try
					{
						Object obj = rs.getObject(columnName);
						// si viene un BigDecimal (caso Oracle) lo convierto a
						// Integer
						if( obj.getClass().toString().equals(
								"java.math.BigDecimal")
								|| obj instanceof BigDecimal )
						{
							Integer in = new Integer(((BigDecimal) obj)
									.intValue());
							row.put(columnName.toLowerCase(), in);
							
							Double d = new Double( ( ( BigDecimal )obj ).doubleValue() );
							row.put( columnName.toLowerCase() + "2", d );
							
							Float f = new Float( ( ( BigDecimal )obj ).floatValue() );
							row.put( columnName.toLowerCase() + "3", f );
						}
						else if( obj.getClass().toString().equals( "java.sql.TimeStamp")
									|| obj instanceof Timestamp )
						{
							Timestamp time = ( Timestamp )obj;
							Date date = new Date( time.getTime() );
							row.put( columnName.toLowerCase(), date );
						}
						else if( obj.getClass().toString().equals( "java.lang.Double" ) || obj instanceof Double )
						{
							row.put( columnName.toLowerCase() + "2", obj );
						}
						else if( obj.getClass().toString().equals( "java.lang.Float" ) || obj instanceof Float )
						{
							row.put( columnName.toLowerCase() + "3", obj );
						}
						else
						{
							row.put(columnName.toLowerCase(), obj);
						}
					}
					catch (NullPointerException npe)
					{
						//                        System.out.println( "en la re-mala" );
					}
				}

				res.addElement(row);
			}
			st.close();

		}
		catch (Exception e)
		{
			dbc.freeConnection(c);
			throw new QueryException(sql + " --> " + e);
		}
		finally
		{
			dbc.freeConnection(c);
		}

		return res;
	}

	/**
	 * Ejecuta una instrucción sql. La instrucción puede ser cualquier cosa:
	 * select, insert, update; aunque para select se recomienda que se use query
	 * ya que este retorna un vector con los resultados
	 * 
	 * @throws ExecuteSQLException
	 *             si no se puede ejecutar la instrucción
	 * @param sql
	 *            la instrucción que se va a ejecutar
	 */
	public static void execute( String sql ) throws ExecuteSQLException
	{
		DataBaseConnection dbc = null;
		Connection c = null;
		try
		{
			dbc = DataBaseConnection.getInstance();
			c = dbc.getConnection();
			//            c.setAutoCommit( true );

			// aqui reparo la consulta ////
			sql = repairQuery(sql, dbc);
			///////////////

//			System.out.println( sql );
			Statement st = c.createStatement();
			st.execute(sql);
			st.close();
		}
		catch (Exception e)
		{
			dbc.freeConnection(c);
			throw new ExecuteSQLException(sql + " --> " + e);
		}
		finally
		{
			dbc.freeConnection(c);
		}
	}

	/**
	 * este método ejecuta un conjunto de instrucciones sql dentro de una
	 * transacción.
	 * 
	 * @throws TransactionException
	 *             si no se puede ejecutar la transacción, antes de lanzar la
	 *             excepción se intenta hacer un rollback.
	 * 
	 * @param sqls
	 *            es un vector de Strings en donde cada uno representa una de
	 *            las instrucciones sql que debe estar en la transacción.
	 */
	public static void executeOnTransaction( Vector sqls )
			throws TransactionException
	{
		DataBaseConnection dbc = null;
		Connection c = null;
		try
		{
			dbc = DataBaseConnection.getInstance();
			c = dbc.getConnection();
			c.setAutoCommit(false);
//			System.out.println( "Iniciando la transacción" );
			c.setTransactionIsolation(Connection.TRANSACTION_SERIALIZABLE);
			Statement st = c.createStatement();
			for( int i = 0; i < sqls.size(); i++ )
			{
				String sql = (String) sqls.elementAt(i);
				sql = repairQuery( sql, dbc );
//				System.out.println( sql );
				st.execute(sql);
			}
			c.commit();
			st.close();
		}
		catch (Exception e)
		{
			if( c != null )
			{
				try
				{
					c.rollback();
				}
				catch (SQLException ex)
				{
					try
					{
						c.setAutoCommit(true);
//						System.out.println( "Terminando la transacción" );
					}
					catch (SQLException sqlex)
					{

					}
					dbc.freeConnection(c);
					throw new TransactionException(
							"Error executing transaction and error rolling back --> "
									+ e);
				}
				finally
				{
					try
					{
						c.setAutoCommit(true);
//						System.out.println( "Terminando la transacción" );
					}
					catch (SQLException sqlex)
					{

					}
					dbc.freeConnection(c);
				}
			}
			else
			{
				dbc.freeConnection(c);
			}
			throw new TransactionException(
					"Error executing transaction, Rolled back --> " + e);
		}
		finally
		{
			try
			{
				c.setAutoCommit(true);
//				System.out.println( "Terminando la transacción" );
			}
			catch (SQLException sqlex)
			{

			}
			dbc.freeConnection(c);
		}
	}

	/**
	 * Esta función retorna el siguiente valor el campo definido por el
	 * parámetro field en la tabla definida por el parámetro table
	 * 
	 * @param table
	 *            nombre de la tabla
	 * @param field
	 *            nombre del campo de la tabla
	 * @return int que representa el id para el caso de autonuméricos
	 */
	public static int getNextId( String table, String field )
			throws QueryException
	{
		try
		{
			String query = new String("SELECT MAX(" + field + ") as id  FROM "
					+ table);
			Vector results = new Vector();
			results = SQLUtilities.query(query);
			Hashtable temp = new Hashtable();
			temp = (Hashtable) results.elementAt(0);
			int res = (((Integer) temp.get("id")).intValue() + 1);
//			System.out.println("El nuevo registro de " + table + " es " + res);
			return res;
		}
		catch (QueryException e1)
		{
			return 1;
		}
		catch (Exception e2)
		{
			throw new QueryException("Error buscando el id de la tabla "
					+ table);
		}

	}

	public static String repairQuery( String sql, DataBaseConnection dbc )
	{
		String res = "";
		if( dbc.getDataBase().equals("ORACLE") )
		{
			if( sql.endsWith(";") )
			{
				res = sql.substring(0, sql.length() - 1);
			}
			else
			{
				res = sql;
			}
		}
		else
		{
			res = sql;
		}
		return res;
	}

}