/*
 * 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.    
 */
#include "apache/das/rdb/Connection.h"

namespace apache {
	namespace das {
		namespace rdb {

Connection::Connection(string dsn, string user, string password) {
	SQLRETURN result;

	SQLINTEGER error;
	SQLCHAR sqlStat;
	SQLCHAR * message = 0;
	SQLSMALLINT messageLength;

	//Alloc environment handle
	result = SQLAllocHandle(SQL_HANDLE_ENV,SQL_NULL_HANDLE,&environment);

	if ((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO))
		throw SQLException(result, "Error to alloc the environment handle - SQLHENV");

	//Set the environment
	result = SQLSetEnvAttr(environment, SQL_ATTR_ODBC_VERSION,(void*)SQL_OV_ODBC3, 0);

	if ((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO)){
		SQLFreeHandle(SQL_HANDLE_ENV, environment);
		throw SQLException(result, "Error to set the environment handle - SQLHENV");
	}	

	//Allocate connection handle
	result = SQLAllocHandle(SQL_HANDLE_DBC, environment, &connection);

	if ((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO)){
		SQLFreeHandle(SQL_HANDLE_ENV, environment);
		throw SQLException(result, "Error to alloc the connection handle - SQLHDBC");
	}

	setAutoCommit(false);

	//Connect to the datasource
	result = SQLConnect(connection,  reinterpret_cast<SQLCHAR *>(const_cast<char *> (dsn.c_str())), SQL_NTS,
									 reinterpret_cast<SQLCHAR *>(const_cast<char *> (user.c_str())), SQL_NTS,
									 reinterpret_cast<SQLCHAR *>(const_cast<char *> (password.c_str())), SQL_NTS);

	if ((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO)){

		SQLGetDiagRec(SQL_HANDLE_DBC, connection,1, 
		              &sqlStat, &error,message,100,&messageLength);

		SQLFreeHandle(SQL_HANDLE_DBC, connection);
		SQLFreeHandle(SQL_HANDLE_ENV, environment);

		string error("Error to establish the connection.\nSQLSTATE: ");
		error += reinterpret_cast<char*>(&sqlStat);
		throw SQLException(result, error);
	}

}

Connection::Connection(string connectString) {
	SQLRETURN result;

	SQLINTEGER error;
	SQLCHAR sqlStat;
	SQLCHAR * message = 0;
	SQLSMALLINT messageLength;
	SQLCHAR     outConnectString[1024];
	
	//Alloc environment handle
	result = SQLAllocHandle(SQL_HANDLE_ENV,SQL_NULL_HANDLE,&environment);

	if ((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO))
		throw SQLException(result, "Error to alloc the environment handle - SQLHENV");

	//Set the environment
	result = SQLSetEnvAttr(environment, SQL_ATTR_ODBC_VERSION,(void*)SQL_OV_ODBC3, 0);

	if ((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO)){
		SQLFreeHandle(SQL_HANDLE_ENV, environment);
		throw SQLException(result, "Error to set the environment handle - SQLHENV");
	}	

	//Allocate connection handle
	result = SQLAllocHandle(SQL_HANDLE_DBC, environment, &connection);

	if ((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO)){
		SQLFreeHandle(SQL_HANDLE_ENV, environment);
		throw SQLException(result, "Error to alloc the connection handle - SQLHDBC");
	}

	setAutoCommit(false);

	//Connect to the datasource
	result = SQLDriverConnect( connection, 0, (SQLCHAR*) (char*) connectString.c_str(), SQL_NTS,
                             (SQLCHAR*)outConnectString, sizeof(outConnectString),
                             &messageLength, SQL_DRIVER_COMPLETE );

	if ((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO)){

		SQLGetDiagRec(SQL_HANDLE_DBC, connection,1, 
		              &sqlStat, &error,message,100,&messageLength);

		SQLFreeHandle(SQL_HANDLE_DBC, connection);
		SQLFreeHandle(SQL_HANDLE_ENV, environment);

		string outC = (char*) outConnectString;

		string error("Error to establish the connection.\nSQLSTATE: ");
		error += reinterpret_cast<char*>(&sqlStat);
		throw SQLException(result, error);
	}

}

Connection::~Connection(void){
	std::list<StatementPtr*>::iterator it;

	for (it = statements.begin() ; it != statements.end() ; it++) {
		
		if (**it) {
			delete **it;
			delete *it;

		}

	}

	SQLDisconnect(connection);
    SQLFreeHandle(SQL_HANDLE_DBC,connection);
    SQLFreeHandle(SQL_HANDLE_ENV, environment);

}

SQLHDBC Connection::getODBCConnection(void) const{
	return connection;
}

void Connection::commit(void){
	SQLRETURN result = SQLEndTran(SQL_HANDLE_DBC, connection, SQL_COMMIT);
	
	if ((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO)){
		throw SQLException(result, "Commit error");
	}

}

void Connection::rollback(void){
	SQLRETURN result = SQLEndTran(SQL_HANDLE_DBC, connection, SQL_ROLLBACK);
	
	if ((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO)){
		throw SQLException(result, "Rollback error");
	}

}

void Connection::setAutoCommit(bool autoCommit){
	if(autoCommit)
		SQLSetConnectAttr(connection,SQL_ATTR_AUTOCOMMIT,reinterpret_cast<SQLPOINTER>(SQL_AUTOCOMMIT_ON), SQL_IS_INTEGER );
	else
		SQLSetConnectAttr(connection,SQL_ATTR_AUTOCOMMIT,SQL_AUTOCOMMIT_OFF, SQL_IS_INTEGER );
}

StatementPtr Connection::createStatement(void) {
	SQLHSTMT statementHandle;
	SQLRETURN result = SQLAllocHandle(SQL_HANDLE_STMT, connection, &statementHandle);

	if ((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO)){
		throw SQLException(result, "Error to alloc the statement handle - SQLSTMT");
	}

	std::list<StatementPtr*>::iterator it;

	for (it = statements.begin() ; it != statements.end() ; ) {
		
		if (**it) {
			it++;

		} else {
			std::list<StatementPtr*>::iterator aux = it;
			it++;
			statements.erase(aux);

		}

	}

	Statement* stmt = new Statement(*this, statementHandle);
	StatementPtr ret(stmt);
	statements.push_back(new StatementPtr(stmt, false));

	return ret;

}

		};
	};
};
