#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <unistd.h>

#include "NTDb.h"

#include <string>

namespace NT {

using namespace std;

NTDb* NTDb::global_dbconn = NULL;

NTStmt::NTStmt() {
	dbconn = NULL;
	hstmt = NULL;
}

NTStmt::NTStmt( NTDb *conn ) {
	hstmt = NULL;
	dbconn = conn;
	numofresultcolumns = 0;
	SQLAllocStmt( dbconn->GetHDBC() , &hstmt );
}

NTStmt::~NTStmt() {
	FreeFetchedData();
	if (hstmt) {
		SQLFreeStmt( hstmt , SQL_DROP );
	}
}

bool NTStmt::Fetch( void ) {
	FreeFetchedData();
	SQLRETURN retcode = SQLFetch( hstmt );
	return ((retcode==SQL_SUCCESS) || (retcode==SQL_SUCCESS_WITH_INFO));
}

void NTStmt::FetchRecordsetInfo( void ) {
	if (!hstmt) return;
	SQLSMALLINT numofcols;
	SQLRETURN sqlret = SQLNumResultCols( hstmt , &numofcols );
	if (sqlret!=SQL_SUCCESS && sqlret!=SQL_SUCCESS_WITH_INFO) return;
	numofresultcolumns = numofcols;

	// load the columns
	unsigned int i;
	for (i=0;i<numofresultcolumns;i++) {
		char colname[256];
		SQLSMALLINT colnamelength = 0;
		SQLSMALLINT datatype = 0;
		SQLULEN colsize = 0;
		SQLSMALLINT decdigits = 0;
		SQLSMALLINT nullvalues = 0;

		sqlret = SQLDescribeCol( hstmt , i+1 ,
			(SQLCHAR *)colname , 256 , &colnamelength ,
			&datatype,
			&colsize,
			&decdigits,
			&nullvalues );
		if (sqlret!=SQL_SUCCESS && sqlret!=SQL_SUCCESS_WITH_INFO) continue;
		fieldnames[colname]=i+1;
		NTStmtColumn col;
		col.colname=colname;
		if (datatype<0) datatype=-datatype;	// mi ez az abszolut gecizes?
		col.coltype = datatype;
		col.colsize = colsize;
		col.fetched = false;
		col.fetchsize = 0;
		col.isset = false;
		col.data = NULL;
		columns.push_back( col );
	}
}

int NTStmt::GetIndexFromName( string colname ) {
	map<NTString, int>::iterator iter;
	fieldnames.begin();
	iter = fieldnames.find( colname );
	if (iter==fieldnames.end() ) return 0;
	return iter->second;
}

int NTStmt::GetInteger( int index , int *indicator ) {
	FetchCol( index - 1 );
	NTStmtColumn *col = &columns.at(index-1);
	if (indicator) *indicator = col->indicator;
	return *((int*)col->data);
}

int NTStmt::GetInteger( string colname , int *indicator ) {
	int index = GetIndexFromName( colname );
	if (!index) return 0;
	return GetInteger( index , indicator );
}

bool NTStmt::GetBoolean( int index , int *indicator ) {
	string retval = GetString( index , indicator );
	return (retval=="0") ? false : true;
}

bool NTStmt::GetBoolean( string colname , int *indicator ) {
	int index = GetIndexFromName( colname );
	if (!index) return 0;
	return GetBoolean( index , indicator );
}


float NTStmt::GetFloat( int index , int *indicator ) {
	FetchCol( index - 1 );
	NTStmtColumn *col = &columns.at(index-1);
	if (indicator) *indicator = col->indicator;
	return (float)(*((double*)col->data));
}

float NTStmt::GetFloat( string colname , int *indicator ) {
	int index = GetIndexFromName( colname );
	if (!index) return 0;
	return GetFloat( index , indicator );
}

double NTStmt::GetDouble( int index , int *indicator ) {
	FetchCol( index - 1 );
	NTStmtColumn *col = &columns.at(index-1);
	if (indicator) *indicator = col->indicator;
	return *((double*)col->data);
}

double NTStmt::GetDouble( string colname , int *indicator ) {
	int index = GetIndexFromName( colname );
	if (!index) return 0;
	return GetDouble( index , indicator );
}

string NTStmt::GetString( int index , int *indicator ) {
	FetchCol( index - 1 );
	NTStmtColumn *col = &columns.at(index-1);
	if (indicator) *indicator = col->indicator;
	string retval = *((string*)col->data);
	return retval;
}

string NTStmt::GetString( string colname , int *indicator ) {
	int index = GetIndexFromName( colname );
	if (!index) return string("");
	return GetString( index , indicator );
}

bool NTStmt::Isset( string colname ) {
	int index = GetIndexFromName( colname );
	if (!index) return false;

	FetchCol( index - 1 );
	NTStmtColumn *col = &columns.at(index-1);
	return (col->indicator!=SQL_NULL_DATA);
}

bool NTStmt::Bind( int numofcol , int type , void *buffer , int maxlen , int *index ) {
	SQLBindCol( hstmt , numofcol , type , buffer , maxlen , (SQLLEN *)index );
	return true;
}

void NTStmt::FetchCol( unsigned int colnum ) {		// zero based
	if (colnum>=columns.size()) return;	// baszdmeg ag
	NTStmtColumn *col = &columns.at(colnum);
	if (col->fetched) return;

	switch (col->coltype) {
	case SQL_CHAR:
	case SQL_VARCHAR:
	default:
		{
			string *a = new string();
#define NT_STRING_BLOCK_SIZE (4096)
			char tempstring[NT_STRING_BLOCK_SIZE];
			SQLRETURN retval;
			SQLLEN myind;
			memset( tempstring , 0 , NT_STRING_BLOCK_SIZE );
			retval = SQLGetData( hstmt , colnum + 1 , SQL_C_CHAR , tempstring , NT_STRING_BLOCK_SIZE , &myind );
			col->indicator = (int)myind;
			while (retval!=SQL_NO_DATA) {
				*a+=tempstring;
				if (myind==SQL_NULL_DATA) break;
				if (myind<NT_STRING_BLOCK_SIZE-1) break;
				retval = SQLGetData( hstmt , colnum + 1 , SQL_C_CHAR , tempstring , NT_STRING_BLOCK_SIZE , &myind );
				col->indicator = (int)myind;
			}
#undef NT_STRING_BLOCK_SIZE
			col->data = (void *)a;
		}
		break;
	case SQL_NUMERIC:
	case SQL_FLOAT:
	case SQL_REAL:
	case SQL_DOUBLE:
		{
			double *a = new double;
			SQLLEN myind;
			SQLGetData( hstmt , colnum + 1 , SQL_C_DOUBLE , a , sizeof( double ) , &myind );
			col->indicator = (int)myind;
			col->data = a;
		}
		break;
	case SQL_DECIMAL:
	case SQL_INTEGER:
	case SQL_SMALLINT:
		{
			int *a = new int;
			SQLLEN myind;
			SQLGetData( hstmt , colnum + 1 , SQL_C_LONG , a , sizeof( int ) , &myind );
			col->indicator = (int)myind;
			col->data = (void *)a;
		}
		break;
	}
	col->fetched = true;
}

void NTStmt::FreeFetchedData( void ) {
	unsigned int i,len = columns.size();
	for (i=0;i<len;i++) {
		NTStmtColumn *col = &columns.at(i);
		if (!col->fetched) continue;
		switch (col->coltype) {
		case SQL_CHAR:
		case SQL_VARCHAR:
		default:
			{
				string *temp = (string *)col->data;
				delete temp;
			}
			break;
		case SQL_NUMERIC:
		case SQL_FLOAT:
		case SQL_REAL:
		case SQL_DOUBLE:
			{
				double *temp = (double *)col->data;
				delete temp;
			}
			break;
		case SQL_DECIMAL:
		case SQL_INTEGER:
		case SQL_SMALLINT:
			{
				int *temp = (int *)col->data;
				delete temp;
			}
			break;
		}
		col->fetched=false;
		col->fetchsize = 0;
		col->isset=false;
		col->data = NULL;
	}
}

NTDb::NTDb() {
	SQLRETURN retcode;
	hdbc = NULL;
	retcode = SQLAllocEnv( &henv );
	// printf( "db constructor\n" );
}


NTDb::~NTDb() {
	// printf( "db destructor\n" );
}

bool NTDb::Connect(
	const char *username,
	const char *password,
	const char *database ) {

	SQLRETURN retcode;
	retcode = SQLAllocConnect( henv , &hdbc );
	if (retcode!=SQL_SUCCESS) {
		return false;
	}
#if 0
	string constring="FILEDSN=bolt.dsn";
	SQLCHAR tempstring[2048];
	SQLSMALLINT length;
	retcode = SQLDriverConnect(
		hdbc , NULL,
		(SQLCHAR *) constring.c_str(),
		SQL_NTS,
		tempstring,
		2048,
		&length,
		SQL_DRIVER_COMPLETE );
#else
	retcode = SQLConnect( hdbc,
		(SQLCHAR *)database,
		SQL_NTS,
		(SQLCHAR *)username,
		SQL_NTS,
		(SQLCHAR *)password,
		SQL_NTS );
#endif
	if (retcode != SQL_SUCCESS) {
		return false;
	}
	return true;
}

NTStmt* NTDb::Execute( const char *query ) {
	struct stat buf;
	SQLRETURN retcode;
	NTStmt *retval;
	retval = new NTStmt( this );
	struct timeval tv_before,tv_after;
	gettimeofday( &tv_before , NULL );
	retcode = SQLExecDirect( retval->GetHSTMT() , (SQLCHAR *)query , SQL_NTS );
	gettimeofday( &tv_after , NULL );
	if (retcode!=SQL_SUCCESS && retcode!=SQL_SUCCESS_WITH_INFO) {
		FILE *x = fopen( "./dberr.log" , "a" );
		SQLCHAR errormessage[4096];
		SQLINTEGER nativeerror;
		SQLSMALLINT errorlength;
		SQLGetDiagRec( SQL_HANDLE_STMT , retval->GetHSTMT(), 1 , NULL , &nativeerror ,
			errormessage , 4096 , &errorlength );
		if (x) {
			fprintf( x , "query failed: %s\n" , query );
			if (errorlength) fprintf( x , " with %s\n\n" , errormessage );
			fclose( x );
		}
		delete retval;
		return NULL;
	} else if (!stat( "./dbcalls.log" , &buf )) {
		int diffsecs = tv_after.tv_sec - tv_before.tv_sec;
		int diffusecs = tv_after.tv_usec - tv_before.tv_usec;
		int usecs = (diffsecs*1000000) + diffusecs;
		
		FILE *x = fopen( "./dbcalls.log" , "a" );
		if (x) {
			time_t mytime = time( NULL );
			struct tm *tm = localtime( &mytime );
			fprintf( x , "%04d-%02d-%02d %02d:%02d:%02d - %5d - %s\n  => %d ms (%d usecs)\n" ,
				tm->tm_year+1900,
				tm->tm_mon+1,
				tm->tm_mday,
				tm->tm_hour,
				tm->tm_min,
				tm->tm_sec,
				getpid(),
				query,
				usecs / 1000,
				usecs );
			fclose( x );
		}
	}
	// success
	retval->FetchRecordsetInfo();
	return retval;
}

void NTDb::DirectExecute( string query ) {
	NTStmt *res = Execute( query );
	if (res) delete res;
}


NTStmt* NTDb::Execute( string query ) {
	return Execute( query.c_str() );
}

int NTDb::ExecuteAndGetInteger( const char *query ) {
	NTStmt *res = Execute( query );
	if (!res) return 0;
	if (!res->Fetch()) return 0;
	int retval = res->GetInteger( 1 , NULL );
	delete res;
	return retval;
}

int NTDb::ExecuteAndGetInteger( string query ) {
	return ExecuteAndGetInteger( query.c_str() );
}

NTString NTDb::ExecuteAndGetString( const char *query ) {
	NTStmt *res = Execute( query );
	if (!res) return "";
	if (!res->Fetch()) return "";
	NTString retval = res->GetString( 1 , NULL );
	delete res;
	return retval;
}

NTString NTDb::ExecuteAndGetString( string query ) {
	return ExecuteAndGetString( query.c_str() );
}

bool NTDb::ExecuteAndGetBoolean( const char *query ) {
	NTStmt *res = Execute( query );
	if (!res) return 0;
	if (!res->Fetch()) return 0;
	bool retval = res->GetBoolean( 1 , NULL );
	delete res;
	return retval;
}

bool NTDb::ExecuteAndGetBoolean( string query ) {
	return ExecuteAndGetBoolean( query.c_str() );
}

HDBC NTDb::GetHDBC( void ) { return hdbc; }
HENV NTDb::GetHENV( void ) { return henv; }
void NTDb::SetGlobalDBConn( class NTDb *db ) { global_dbconn = db; }
class NTDb *NTDb::GetGlobalDBConn( void  ) { return global_dbconn; }


}


