package org.postgresql.coreDriver;

import java.sql.SQLException;
import java.util.StringTokenizer;
import java.util.Vector;

import org.postgresql.Field;
import org.postgresql.PG_Stream;
import org.postgresql.jdbc3.ParserSQL;
import org.postgresql.util.FailOverException;
import org.postgresql.util.PSQLException;

/*
 * Executes a query on the backend.
 *
 * <p>The lifetime of a QueryExecutor object is from sending the query
 * until the response has been received from the backend.
 *
 * $Id: QueryExecutorContext.java,v 1.1 2005/03/02 16:07:32 jsalas Exp $
 */

public class QueryExecutorContext
{
	
	private static final int INT_FIELD_SIZE = 4; //JSalas
	
	
	private final String[] m_sqlFrags;
	private final Object[] m_binds;
	private final java.sql.Statement statement;
	private PG_Stream pg_stream;
	private final org.postgresql.jdbc1.AbstractJdbc1Connection connection;
	
	public QueryExecutorContext(String[] p_sqlFrags, Object[] p_binds,
			java.sql.Statement statement,
			PG_Stream pg_stream,
			java.sql.Connection connection)
	throws SQLException
	{
		
		// Filtering conflictive select of jboss
		StringTokenizer st = new StringTokenizer (p_sqlFrags[0]);
		String token = new String();
		int i;
		if (st.countTokens() > 10) {
			for (i = 0; i < 11; i++) {
				token = st.nextToken(" \t");
			}
			if (token.equals("CASE")) {
				p_sqlFrags[0] = "select NULL AS TABLE_CAT, NULL AS TABLE_SCHEM, "
					+ "relName AS TABLE_NAME, typeName AS TABLE_TYPE, NULL AS REMARKS "
					+ "FROM pg_class p, relkindTable r where relname not like 'pg\\_%' "
					+ "and p.relkind = r.relkind "
					+ "order by TABLE_TYPE, TABLE_NAME;";
				((org.postgresql.jdbc1.AbstractJdbc1Connection)connection).setType('S');
			}
		}
		
		
		// repair INSERT and DELETE to postgres-r
		p_sqlFrags[0] = ParserSQL.repairSQL (p_sqlFrags[0]);
		
		this.m_sqlFrags = p_sqlFrags;
		this.m_binds = p_binds;
		this.statement = statement;
		this.pg_stream = pg_stream;
		this.connection = (org.postgresql.jdbc1.AbstractJdbc1Connection)connection;
		
		if (statement != null)
			maxRows = statement.getMaxRows();
		else
			maxRows = 0;
	}
	
	private Field[] fields = null;
	private Vector tuples = new Vector();
	private boolean binaryCursor = false;
	private String status = null;
	private int update_count = 1;
	private long insert_oid = 0;
	private int maxRows;
	
	
	private void clearTuples () {
		tuples.clear();
	}
	
	private void clearFields () {
		fields = null;
	}
	
	/* Add by jsalas */
	private boolean GetNewLocalReplica() throws SQLException{
		// Middleware : replica is shutdown
		try {
			Thread.sleep(10000);
		} catch (InterruptedException ie) {  }
		//System.out.println("QueryExecutor.execute: call newLocalReplica 1");
		connection.newLocalreplica();
		return false;
	}
	
	private void clearAll() throws SQLException {
		connection.clearNotification();
		clearTuples();
		connection.clearWarnings();
		clearFields();
	}
	
	/*private boolean send_suspect(){
		return true;
	}*/
	
	/*
	 * Execute a query on the backend.
	 */
	public java.sql.ResultSet execute() throws SQLException
	{
		//StringBuffer errorMessage = null;
		//int c = 0;
		//boolean l_endQuery = false;
		//int step = 0;
		ContextTask ctxt = new ContextTask();
		
		boolean ok = false;
		boolean firstStep = true;
		String s;
		int idConnection, petition;
		//boolean confirm_suspect = false;
		
		idConnection = connection.getIdConnection();
		petition = connection.getPetition();
		
		connection.addPetition ();
		
		if (pg_stream == null)
		{
			throw new PSQLException("postgresql.con.closed");
		}
		
		while (! ok) {
			
			if ((! firstStep) && (!ok)) {//TODO FailOver
				connection.closePGStream();
				//System.out.println("connectToMiddleware 5");
				ok = connection.connectToMiddleware();
				//System.out.println("connectToMiddleware 6");
				pg_stream = connection.getPGStream();
			}
			firstStep = false;
			
			synchronized (pg_stream) {
				try {
					//System.out.println("Enviando consulta");
					//System.out.println ("sql is <" + m_sqlFrags[0] + "> length " + m_sqlFrags[0].length());
					if (m_sqlFrags[0].length() == 1) {
						return null;
					}
					sendQuery(idConnection, petition);//TODO FailOver
					ok = true;
				} catch (FailOverException e) {
					ok = GetNewLocalReplica();
					continue;
				}
				
				while (!ctxt.isL_endQuery()) {
					//confirm_suspect = false;
					while (!ok) {
						try {
							ctxt.setType(pg_stream.ReceiveChar());
							ok = true;
						} catch (FailOverException e) {
							//confirm_suspect = send_suspect();
							//if (confirm_suspect){
								ok = GetNewLocalReplica();
								clearAll();
								ctxt.setErrorMessage(null);
								ctxt.setL_endQuery(true);
								continue;
							//}
						}
					}
					
					//Modificar poner el try catch y dentro una operacion segun caracter;
					//confirm_suspect = false;
					while (!ok) {
						try {
							performTask(ctxt);
							ok = true;
							ctxt.setStep(0);
						} catch (FailOverException e) {
							//confirm_suspect = send_suspect();
							//if (confirm_suspect) {
								ok = GetNewLocalReplica();
								clearAll();
								ctxt.setErrorMessage(null);
								ctxt.setL_endQuery(true);
								continue;
							//}
						}
					}
				}
			}
		} // while (!ok)
		
		// did we get an error during this query?
		if (ctxt.getErrorMessage() != null){
			throw new SQLException(ctxt.getErrorMessage().toString());
		}
		
		return connection.getResultSet(statement, fields, tuples, status, update_count, insert_oid, binaryCursor);
	}
	
	public void performTask(ContextTask ctxt) throws SQLException, FailOverException {
		String s;
		
		// Modificado por mi
		//System.out.println("Caracter recibido despues del query " + (char) ctxt.getType());
		switch (ctxt.getType()) {
		case 'A': // Asynchronous Notify
			int pid = 0;
			String msg;
			
			if (ctxt.getStep() == 0) {
				pid = pg_stream.ReceiveInteger(4);
				ctxt.incStep();
			}
						
			msg = pg_stream.ReceiveString(connection.getEncoding());
			// Modificado por mi
			//System.out.println("Recibido mensaje : " + msg);
			connection.addNotification(new org.postgresql.coreDriver.Notification(msg, pid));
			break;
			
		case 'B': // Binary Data Transfer
			receiveTuple(true);	        
			break;
			
		case 'C': // Command Status
			receiveCommandStatus();	     
			break;
			
		case 'D': // Text Data Transfer		    
			receiveTuple(false);
			break;
			
		case 'E': // Error Message
			
			//System.out.println("Error in middler");
			// it's possible to get more than one error message for a query
			// see libpq comments wrt backend closing a connection
			// so, append messages to a string buffer and keep processing
			// check at the bottom to see if we need to throw an exception
			
			if (ctxt.getErrorMessage() == null){
				ctxt.setErrorMessage(new StringBuffer());
			}
			
			s = pg_stream.ReceiveString(connection.getEncoding());
			//System.out.println("Error in middler");
			//System.out.println("Error in middler");
			
			ctxt.appendErrorMessage(s);
			//System.out.println(errorMessage);
			// keep processing
			break;
			
		case 'I': // Empty Query
			pg_stream.ReceiveChar();
			// Modificado por mi
			//System.out.println("Query vacia");	
			break;
			
		case 'N': // Error Notification		   
			s = pg_stream.ReceiveString(connection.getEncoding());	      	
			connection.addWarning(s);
			break;
			
		case 'P': // Portal Name		   
			pg_stream.ReceiveString(connection.getEncoding());	    	
			break;
			
		case 'T': // MetaData Field Description		    
			receiveFields(ctxt);	      	
			break;
			
		case 'Z':
			ctxt.setL_endQuery(true);
			// Modificado por mi
			//System.out.println("Final de Query");
			break;
			
		default:
			throw new PSQLException("postgresql.con.type", new Character( (char) ctxt.getType()));
		}
	}
	
	
	/*
	 * Send the tables acceded (:R:W:ALL:)
	 */
	private void sendTables() throws FailOverException
	{
		int i;
		ParserSQL p = connection.getParser();
		Vector r = p.getR();
		Vector w = p.getW();
		Vector all = p.getAll();
		String element;
		
		pg_stream.SendChar(':');
		
		for (i = 0; i < r.size(); i++) {
			element = (String) r.elementAt(i);
			if (i != 0) {
				pg_stream.SendChar('.');
			}
			pg_stream.Send(element.getBytes());
		}
		
		pg_stream.SendChar(':');
		
		for (i = 0; i < w.size(); i++) {
			element = (String) w.elementAt(i);
			if (i != 0) {
				pg_stream.SendChar('.');
			}
			pg_stream.Send(element.getBytes());
		}
		
		pg_stream.SendChar(':');
		
		for (i = 0; i < all.size(); i++) {
			element = (String) all.elementAt(i);
			if (i != 0) {
				pg_stream.SendChar('.');
			}
			pg_stream.Send(element.getBytes());
		}
		
		pg_stream.SendChar(':');
	}
	
	/*
	 * Send a query to the backend.
	 */
	private void sendQuery(int idConnection, int petition) throws SQLException, FailOverException
	{		
		pg_stream.SendChar('Q');
		pg_stream.SendInteger(petition, 2);
		pg_stream.SendInteger(idConnection, 2);
		// Middleware : Send tables acceded to pgserver
		// (Q:TablesR:TablesW:TablesALL:sql;type)
		sendTables();
		
		for (int i = 0 ; i < m_binds.length ; ++i)
		{
			if (m_binds[i] == null) {
				throw new PSQLException("postgresql.prep.param", new Integer(i + 1));
			}
			pg_stream.Send(connection.getEncoding().encode(m_sqlFrags[i]));
			// Modificado por mi
			//System.out.println("Query To Send: " + m_sqlFrags[i]);
			//System.out.println("   sqlFrags : " + m_sqlFrags[i]);
			
			pg_stream.Send(connection.getEncoding().encode(m_binds[i].toString()));
			// Modificado por mi
			//System.out.println("   m_binds : " + m_binds[i].toString());
		}
		pg_stream.Send(connection.getEncoding().encode(m_sqlFrags[m_binds.length]));
		// Modificado por mi
		//System.out.println("   m_sqlFrags(m_binds.length) : " + m_sqlFrags[m_binds.length]);
		//System.out.println("   tamanyo : " + m_sqlFrags[m_binds.length].length());
		// Middleware : send type of sql
		if ((m_sqlFrags[m_binds.length]).indexOf(';') == -1) {
			pg_stream.SendChar(';');
		}
		//System.out.println("typeSQL = " + connection.getType());
		pg_stream.SendChar(connection.getType());
		pg_stream.SendChar(0);
		pg_stream.flush();
	}
	
	/*
	 * Receive a tuple from the backend.
	 *
	 * @param isBinary set if the tuple should be treated as binary data
	 */
	private void receiveTuple(boolean isBinary) throws SQLException, FailOverException
	{
		if (fields == null)
			throw new PSQLException("postgresql.con.tuple");
		// Modificado por mi
		//System.out.println("Recibida una tupla : ");
		Object tuple = pg_stream.ReceiveTuple(fields.length, isBinary);
		if (isBinary)
			binaryCursor = true;
		if (maxRows == 0 || tuples.size() < maxRows)
			tuples.addElement(tuple);
	}
	
	/*
	 * Receive command status from the backend.
	 */
	private void receiveCommandStatus() throws SQLException, FailOverException
	{
		
		status = pg_stream.ReceiveString(connection.getEncoding());
		try
		{
			// Now handle the update count correctly.
			if (status.startsWith("INSERT") || status.startsWith("UPDATE") || status.startsWith("DELETE") || status.startsWith("MOVE"))
			{
				update_count = Integer.parseInt(status.substring(1 + status.lastIndexOf(' ')));
			}
			if (status.startsWith("INSERT"))
			{
				insert_oid = Long.parseLong(status.substring(1 + status.indexOf(' '),
						status.lastIndexOf(' ')));
			}
		}
		catch (NumberFormatException nfe)
		{
			throw new PSQLException("postgresql.con.fathom", status);
		}
		// Modificado por mi
		//System.out.println("Recibido un estado de comando : " + status);
	}
	
	/*
	 * Receive the field descriptions from the back end.
	 */
	private void receiveFields(ContextTask ctxt) throws SQLException, FailOverException
	{
		// Modificado por mi
		//System.out.println ("Recibiendo fields : ");
		if ((fields != null) && (ctxt.getStep() == 0)){
			throw new PSQLException("postgresql.con.multres");
		}
		
		ContextTaskReceiveFields ctxtreceive = null;
		if (ctxt.getTask() == null){
			ctxtreceive = new ContextTaskReceiveFields();
			ctxt.setTask(ctxtreceive);
		}else{
			ctxtreceive = (ContextTaskReceiveFields)ctxt.getTask();
		}		
		
		int size = ctxtreceive.getSize();
		if (ctxt.getStep() == 0){
			size = pg_stream.ReceiveIntegerR(INT_FIELD_SIZE/* JSalas 2 */);
			fields = new Field[size];
			ctxtreceive.setSize(size);
			ctxt.incStep();
		}		
		
		String typeName = ctxtreceive.getTypeName();
		int typeOid = ctxtreceive.getTypeOid();
		int typeLength = ctxtreceive.getTypeLength();
		int typeModifier = ctxtreceive.getTypeModifier();
		for (int i = ctxtreceive.getRound(); i < fields.length; i++)
		{
			if (ctxt.getStep() == 1){
				typeName = pg_stream.ReceiveString(connection.getEncoding());				
				ctxt.incStep();
				ctxtreceive.setTypeName(typeName);
				
			}
			if (ctxt.getStep() == 2){
				typeOid = pg_stream.ReceiveIntegerR(INT_FIELD_SIZE);
				ctxt.incStep();
				ctxtreceive.setTypeOid(typeOid);
			}
			if (ctxt.getStep() == 3){
				typeLength = pg_stream.ReceiveIntegerR(INT_FIELD_SIZE/* JSalas 2 */);
				ctxt.incStep();
				ctxtreceive.setTypeLength(typeLength);
			}
			if (ctxt.getStep() == 4){
				typeModifier = pg_stream.ReceiveIntegerR(INT_FIELD_SIZE);
				ctxt.setStep(1);
				ctxtreceive.setTypeModifier(typeModifier);
			}
			
			// Modificado por mi
			/*
			 System.out.println("Name: " + typeName);
			 System.out.println("TypeOid: " + typeOid);
			 System.out.println("Length: " + typeLength);
			 System.out.println("Modifier: " + typeLength);
			 */
			
			fields[i] = new Field(connection, ctxtreceive.getTypeName(), ctxtreceive.getTypeOid(), ctxtreceive.getTypeLength(), ctxtreceive.getTypeModifier());
			ctxtreceive.setRound(i+1);
		}
	}
}
