package es.ejie.frmk.infraestructura.logs.base.impl;

import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.sql.Statement;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.Map;

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

import org.apache.log4j.helpers.LogLog;
import org.apache.log4j.jdbc.JDBCAppender;
import org.apache.log4j.spi.LoggingEvent;

import es.ejie.frmk.control.excepciones.Q70GestorDeExcepcionesServidor;
import es.ejie.frmk.infraestructura.logs.base.Q70ConstantesLogs;
import es.ejie.frmk.infraestructura.logs.base.interfaces.Q70GestorDatosComunes;

/**
 * Appender de Log4j específico para jdbc con datasources.
 * recibe el Datasource, la tabla y los campos y valores con los que debe
 * realizar la insert. No utiliza layout para formatear el sql.
 * 
 * @author Carlos Rodriguez
 * @version 1.0  fecha: 10-dic-03
 * 
 */
public class Q70DataSourceJDBCAppender extends JDBCAppender {
	private String dataSourceName;
	//Nombre del datasource que proporciona la conexión.
	private String table; //tabla en la que se realiza la insert.
	private String[] camposMDC;
	//campos genéricos que deben buscarse en la hash MDC
	private DataSource ds; //dataSouce que proporciona la conexión.
	private Map mapColumnSize;
	Q70GestorDatosComunes commonDataManager = null;
	public Q70DataSourceJDBCAppender(Q70GestorDatosComunes commonDataManager) {
		this.commonDataManager = commonDataManager;
	}
	
	/**
	 * Sobreescribe el método del padre para asegurarnos de que se cierre la conexión
	 * @param sql
	 */
	protected void execute(String sql) throws SQLException {
		Connection con = null;
		Statement stmt = null;
		
		try {
			con = getConnection();
			
			stmt = con.createStatement();
			stmt.executeUpdate(sql);
		} catch (Exception e) {
			//Ha habido algún problema insertando el log en BD;
			//saco un aviso por consola, pues en BD podría volver a fallar
			System.out.println("Error insertando log en BD: \n"+e.getMessage()+"\n"+sql);				
			
			if (stmt!=null)
				stmt.close();
			if (!con.isClosed()) {
				con.close();
			}
			throw (SQLException)e;
		}
		stmt.close();
		closeConnection(con);
	}

	/**
	 * devuelve el nombre de la tabla 
	 * @return El nombre de tabla
	 */
	public String getTable() {
		return table;
	}

	/**
	 * establece el nombre de la tabla en la que realizará el insert.
	 * @param string Nombre de la tabla
	 */
	public void setTable(String string) {
		table = string;
		if (dataSourceName != null) {
			mapColumnSize = getColumnsSizeInfoFromLogTable(table);
		}
	}

	/**
	 * Devuelve el nombre del datasource 
	 * @return Nombre del datasource
	 */
	public String getDataSourceName() {
		return dataSourceName;
	}

	/**
	 * Establece el nombre del datasource con el que se obtiene la conexión
	 * @param dataSourceName
	 */
	public void setDataSourceName(String dataSourceName) {
		this.dataSourceName = dataSourceName;
		try {
			LogLog.debug("Inicio Contexto y lookup" + dataSourceName);
			InitialContext ctx = new InitialContext();
			ds = (DataSource) ctx.lookup(dataSourceName);
			LogLog.debug("Fin Contexto y lookup");
			if (table != null) {
				mapColumnSize = getColumnsSizeInfoFromLogTable(table);
			}
		} catch (NamingException e) {
			LogLog.debug("Naming Exception - " + e.getMessage());
		}
	}

	private Map getColumnsSizeInfoFromLogTable(String logTable) {
		Map map = new Hashtable();
		Connection conn = null;
		ResultSet rs = null;

		try {
			conn = getConnection();
	//		DatabaseMetaData metaData = conn.getMetaData();
	//		rs = metaData.getColumns("%", "%", logTable, "%");
			Statement st = conn.createStatement();
			rs = st.executeQuery("select * from " +  logTable + " where 1=2"); 
			ResultSetMetaData rsmd=  rs.getMetaData();
			for(int i=1; i<=rsmd.getColumnCount();i++)
			{
				map.put(rsmd.getColumnName(i),new Integer(rsmd.getColumnDisplaySize(i)));
			}
			
/*			//Printout table data
			while (rs.next()) {
				map.put(rs.getString(4), new Integer(rs.getInt(7)));
			}
*/
		} catch (SQLException e1) {
			Q70GestorDeExcepcionesServidor.getSingleton().recogerThrowable(e1);
		}
		finally{
			try{
				if(rs != null)
					rs.close();
				if(conn != null && !conn.isClosed())
					conn.close();
			}catch(SQLException sqle){
				Q70GestorDeExcepcionesServidor.getSingleton().recogerThrowable(sqle);
			}
		}
		return map;

	}

	/** 
	 * Construye el sql a ejecutar.
	 * @param event Contiene toda la información necesaria para obtener el mensaje a grabar.
	 */
	protected String getLogStatement(LoggingEvent event) {

		//Se da por supuesto que el mensage viene en una hashmap
		HashMap map = (HashMap) event.getMessage();

		//CONSTRUCCIÓN DEL SQL
		//insert into table (campos) values (valores)
		//campos es la concatenacion de los campos genericos (camposMDC) más los propios de cada
		//mensaje que se obtienen de las claves del objeto map.
		//valores es la concatenacion de los valores de los campos genéricos obtenidos a través
		//del MDC con los valores obtenidos del objeto map.
		//El timestamp es un campo fijo para todos los logs
		StringBuffer campos = new StringBuffer("(TIMESTAMP,");
		StringBuffer valores = new StringBuffer("('");
		SimpleDateFormat formatter =
			new SimpleDateFormat(Q70ConstantesLogs.DATE_FORMAT);
		valores.append(formatter.format(new Date()));
		valores.append("',");
		String valor = null;
		//Iterar por los valores genéricos
		for (int i = 0; i < camposMDC.length; i++){
			if(mapColumnSize.get(camposMDC[i])!=null)
			{
				campos.append(camposMDC[i]);
				campos.append(", ");
				//valor = (String) event.getMDC(camposMDC[i]);
				valor = this.commonDataManager.getProperty(camposMDC[i]);
				if (valor == null)
					valores.append("NULL,");
				else
				{
					valores.append("'");
				//	truncar el campo si el valor excede del tamaño definido 
					int longitudCampo =
					((Integer) mapColumnSize.get(camposMDC[i])).intValue();
					if (valor.length() > longitudCampo)
					{
						LogLog.debug("truncar" + valor);
						valor = valor.substring(0, longitudCampo);
					}
					//fin truncar
					valores.append(valor);
					valores.append("', ");
				}
			}
		}
		//Iterar por el mensaje.
		Iterator keyIterator = map.keySet().iterator();
		while (keyIterator.hasNext()) {
			Object key = keyIterator.next().toString();
			campos.append(key.toString());
			campos.append(", ");
			valor = (String) map.get(key);
			//truncar el campo si el valor excede del tamaño definido 
			

			if (valor == null)
				valores.append("NULL, ");
			else
			{
				valores.append("'");
				int longitudCampo =	((Integer) mapColumnSize.get(key.toString())).intValue();
				if (valor.length() > longitudCampo)
				{
					LogLog.debug("truncar:" + valor);
					valor = valor.substring(0, longitudCampo);
				}
				//fin truncar
				valores.append(valor);
				valores.append("', ");
			}
		
		}

		int camposLength = campos.length();
		campos.delete(camposLength - 2, camposLength);

		int valoresLength = valores.length();
		valores.delete(valoresLength - 2, valoresLength);
		campos.append(")");
		valores.append(")");

		//unir valores y campos para formar el sql.
		StringBuffer sql = new StringBuffer("insert into ");
		sql.append(table);
		sql.append(" ");
		sql.append(campos.toString());
		sql.append("values ");
		sql.append(valores.toString());

		LogLog.debug(sql.toString());
		return sql.toString();

	}

	/** 
	 * @return la conexión utilizada para ejecutar el Sql. 
	 */
	protected Connection getConnection() {
		Connection connection = null;
		try {
			connection = ds.getConnection();

		} catch (SQLException e) {
			LogLog.debug("SQL Exception - " + e.getMessage());
		}
		return connection;
	}

	/**
	 * Cierra la conexión.
	 * @param con conexión a cerrar.
	 */
	protected void closeConnection(Connection con) {
		try {
			LogLog.debug("inicio cerrar conexión");
			con.close();
			LogLog.debug("fin cerrar conexión");
		} catch (SQLException e) {
			LogLog.debug("SQL exception - " + e.getMessage());
		}
	}

	/**
	 * @return atributos genéricos
	 */
	public String[] getMDCAttributes() {
		return camposMDC;
	}

	/**
	 * Establece los atributos genéricos a incluir en la insert.
	 * @param strings Atributos genéricos 
	 */
	public void setMDCAttributes(String[] strings) {
		camposMDC = new String[strings.length];
		for (int i = 0; i < strings.length; i++) {
			int posParentesis = strings[i].lastIndexOf("(");
			if (posParentesis > 0) {
				camposMDC[i] = strings[i].substring(0, posParentesis);
			} else
				camposMDC[i] = strings[i];

		}
		//camposMDC = strings;
	}

}
