#include "global.h"
#include <commctrl.h>
#include <stdlib.h>
#include "cdatabase.h"
#include "resource.h"
#include "cdatabase.h"
#include "cinfowin.h"
#include "clexical.h"

/************************************************************************************/
/* Database
/************************************************************************************/

CDatabase::CDatabase()
{
	connection = NULL;
	structure = NULL;
}

CDatabase::~CDatabase()
{
	if (connection)
		Disconnect();
}

bool CDatabase::Connect()
{
	if (!app.options->connection.password)
	{
		app.options->SetSettings();
		if (!app.options->connection.password)
			return false;
	}

	CInfoWindow *win = _NEW(CInfoWindow());
	win->SetText("Pripájam sa k databáze");
	char* tmp;
	if (app.options->connection.host)
	{
		tmp = CMemory::printstr("user=%s port=%i host=%s dbname=%s password=%s connect_timeout=%i %s",//options='PGCLIENTENCODING=sql_ascii'",
			app.options->connection.user, app.options->connection.port, app.options->connection.host, app.options->connection.dbname,
			app.options->connection.password, app.options->connection.timeout,
			app.options->connection.ssl ? "sslmode=require" : "");
	}
	else
	{
		tmp = CMemory::printstr("user=%s port=%i hostaddr=%s dbname=%s password=%s connect_timeout=%i %s",//options='PGCLIENTENCODING=sql_ascii'",
			app.options->connection.user, app.options->connection.port, app.options->connection.host_ip, app.options->connection.dbname,
			app.options->connection.password, app.options->connection.timeout,
			app.options->connection.ssl ? "sslmode=require" : "");
	}

	connection = PQconnectdb(tmp); _DELETE(tmp);
	bool result = (PQstatus(connection) == CONNECTION_OK);

	_DELETE(win);

	if (!result)
	{
#ifdef _DEBUG
		::MessageBox(::GetActiveWindow(), PQerrorMessage(connection), "CDatabase::Connect", MB_ICONSTOP | MB_OK);
#else
		::MessageBox(::GetActiveWindow(), PQerrorMessage(connection), "Chyba pri pripájaní sa k databáze", MB_ICONSTOP | MB_OK);
#endif
		PQfinish(connection);
		connection = NULL;
		return false;
	}
	return true;
}

BOOL CALLBACK CDatabase::DialogProc(HWND hwnd,UINT msg,WPARAM wp,LPARAM lp)
{
	return 0;
}

void CDatabase::Disconnect()
{
	if (connection)
	{
		PQfinish(connection);
		connection = NULL;
		_DELETE(structure); structure = NULL;
	}
}

bool CDatabase::TableExists(const char*name)
{
	for (DWORD i=0; i<structure->nTables; i++)
	{
		if (!strcmp(structure->tables[i].name, name))
			return true;
	}
	return false;
}

void CDatabase::FillTreeViewWithAtoms(HWND hwnd)
{
	if (!structure)
		return;

	//zmaz vsetky, ktore sa tam uz nachadzaju
	::SendMessage(hwnd, WM_SETREDRAW, FALSE, 0);
	::SendMessage(hwnd, TVM_DELETEITEM, 0, (LPARAM)TVI_ROOT);

	char *s = _NEW(char[200]);

	TVINSERTSTRUCT is;
	ZeroMemory(&is, sizeof(is));
	is.hInsertAfter = TVI_SORT;
	is.itemex.mask = TVIF_TEXT | TVIF_PARAM;
	is.itemex.pszText = s;

	for (DWORD i=0; i<structure->nTables; i++)
	{
		//vloz "tabulka(parametre,...)"
		strcpy(s, structure->tables[i].name);
		strcat(s,"(");
		for (DWORD j=0; j<structure->tables[i].nAttributes; j++)
		{
			strcat(s, structure->tables[i].attributes[j].name);
			if (j < structure->tables[i].nAttributes - 1)
				strcat(s,",");
		}
		strcat(s,")");
		is.itemex.lParam = 1;//tabulka
		is.hParent = TVI_ROOT;
		HTREEITEM item = (HTREEITEM) SendMessage(hwnd,TVM_INSERTITEM,0,(LPARAM)&is);

		for (j=0; j<structure->tables[i].nAttributes; j++)
		{
			//vloz "atribut : typ"
			strcpy(s, structure->tables[i].attributes[j].name);
			strcat(s," : ");

			//strcat(s, structure->tables[i].attributes[j].type);
			strcat(s, GetType(structure->tables[i].attributes[j].type));

			is.itemex.lParam = 0;
			is.hParent = item;
			SendMessage(hwnd,TVM_INSERTITEM,0,(LPARAM)&is);
		}
	}

	_DELETE(s);

	::SendMessage(hwnd, WM_SETREDRAW, TRUE, 0);
	::InvalidateRect(hwnd, NULL, TRUE);
}

const CDbStructure* CDatabase::GetStructure()
{
	return structure;
}

CSQLresult* CDatabase::ExecSQL(const char *command)
{
	CSQLresult *res = _NEW(CSQLresult());
	PGresult *pg_res = PQexec(connection,command);
	if (!pg_res)
	{
		_DELETE(res);
#ifdef _DEBUG
		app.mainWindow->GetOutputWindow()->AddLine("DEBUG: ExecSQL : pg_res=NULL, dotaz na databázu nebol vykonaný");
#else
		app.mainWindow->GetOutputWindow()->AddLine("FATÁLNA CHYBA: Dotaz na databázu nebol vykonaný! (zlyhanie servera)");
#endif
		throw 1;
	}
	res->res = pg_res;
	DWORD r = res->GetResult();
	if ( (r!=PGRES_COMMAND_OK) && (r!=PGRES_TUPLES_OK) )
	{
		res->ok = false;
//#ifdef _DEBUG
		const char *str;
		str = PQresultErrorField(res->res, PG_DIAG_MESSAGE_DETAIL);
		if (!str)
			str = PQresultErrorField(res->res, PG_DIAG_MESSAGE_PRIMARY);
		if (str)
		{
			DWORD len = strlen(str)+1;
			if (res->error) {_DELETE(res->error);}
			res->error = _NEW(char[len]);
			::CopyMemory(res->error, str, len);
		}
		else
		{
			res->error = NULL;
		}
//#else
//		res->error = NULL;
//#endif
	}
	else
		res->ok = true;
	return res;
}

BYTE CDatabase::IsNumericType(const char *type)
{
	if (!strcmp(type, "smallint") || !strcmp(type, "integer") || !strcmp(type, "bigint") ||
		!strncmp(type,"decimal", 7) || !strncmp(type,"numeric",7) || !strcmp(type, "real") ||
		!strcmp(type, "double precision") || !strcmp(type, "double precision") ||
		!strcmp(type, "serial") || !strcmp(type, "bigserial"))
		return 1;
	return 0;
}

BYTE CDatabase::IsCharacterType(const char *type)
{
	if ( !strncmp(type, "character", 9) )
		return 1;
	if ( !strncmp(type, "text", 4) )
		return 1;
	if ( !strncmp(type, "timestamp", 9) )
		return 2;
	if ( !strcmp(type, "date") )
		return 2;
	if ( !strcmp(type, "time") )
		return 2;
	return false;
}

const char *CDatabase::GetType(DWORD type)
{
	switch (type)
	{
	case DATABASE_TYPE_NUMBER:
		return typeNumeric;
	case DATABASE_TYPE_DATE:
		return typeDate;
	case DATABASE_TYPE_STRING:
		return typeString;
	default:
#ifdef _DEBUG
		{
			char str[20]; itoa(type, str, 10);
			//::MessageBox(0, str, "DEBUG: CDatabase::GetType(DWORD) - neznamy typ", MB_ICONWARNING|MB_OK);
		}
#endif
		return "undefined";
	}
}

DWORD CDatabase::GetType(const char *type)
{
	if (IsNumericType(type))
		return DATABASE_TYPE_NUMBER;
	DWORD i = IsCharacterType(type);
	switch (i)
	{
	case 1:
		return DATABASE_TYPE_STRING;
	case 2:
		return DATABASE_TYPE_DATE;
	}
#ifdef _DEBUG
	::MessageBox(0, type, "CDatabase::GetType(const char*) - neznamy typ", MB_ICONWARNING|MB_OK);
#endif
	return DATABASE_TYPE_UNKNOWN;
}


void CDatabase::UpdateStructure()
{
	CInfoWindow *win = _NEW(CInfoWindow());
	win->SetText("Načítavam EDB predikáty");

	static char *select1 = "SELECT c.relname,c.oid FROM pg_catalog.pg_class c LEFT JOIN pg_catalog.pg_user u ON u.usesysid = c.relowner \
					LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace WHERE c.relkind='r' AND u.usename=current_user AND \
					n.nspname NOT IN ('pg_catalog', 'pg_toast') AND pg_catalog.pg_table_is_visible(c.oid);";
	static char *select2 = "SELECT a.attname,pg_catalog.format_type(a.atttypid,NULL) FROM pg_catalog.pg_attribute a WHERE a.attrelid='%hs' AND a.attnum>0 AND NOT a.attisdropped;";

	if (structure)
	{
		_DELETE(structure);
	}

	structure = _NEW(CDbStructure());

	CSQLresult *res = NULL,*res2 = NULL;

	char *tmp;

	app.mainWindow->GetOutputWindow()->Clear();

	DWORD count = 0;
	try
	{
		res = ExecSQL(select1);
		DWORD countAll = res->GetnTuples();
		if (!res->ok) throw 1;

		//vytvor miesto pre tabulky (pre vsetky, ale mozno vsetky nevyuzijeme - ak ma stlpec
		//niektorej tabulky zly typ, tabulku nezarad do zoznamu)
		structure->tables = _NEW(DB_TABLE[countAll]);

		for (DWORD i=0; i<countAll; i++)
		{
			win->NextStep();
			//vrat meno tabulky
			const char *name = res->Value(i,0);

			if (CLexicalAnalyzer::IsKeyword(name))
			{
				char *tmp = CMemory::printstr("Meno databázovej tabuľky ’%s’ je zhodné s kľúčovým slovom. Tabuľka sa bude ignorovať.",
					name);
				app.mainWindow->GetOutputWindow()->AddLine(tmp); _DELETE(tmp);
				continue;
			}

			//uloz meno tabulky
			structure->tables[count].name = _NEW(char[strlen(name)+1]);
			strcpy(structure->tables[count].name, name);

			//zisti nazvy atributov a cislo typu atributov
			tmp = CMemory::printstr(select2, res->Value(i,1));
			res2 = ExecSQL(tmp); _DELETE(tmp);
			if (!res2->ok) throw 1;

			int pocet = res2->GetnTuples();

			//definuj pocet atributov
			structure->tables[count].nAttributes = pocet;
			//vytvor miesto pre atributy
			structure->tables[count].attributes = _NEW(DB_TABLE_ATTRIBUTE[pocet]);

			bool stop = false;
			for (DWORD j=0; j<(DWORD)pocet; j++)
			{
				//uloz meno atributu
				name = res2->Value(j,0);
				structure->tables[count].attributes[j].name = _NEW(char[strlen(name)+1]);
				strcpy(structure->tables[count].attributes[j].name, name);

				//uloz typ
				name = res2->Value(j,1);
				DWORD type = GetType(name);
				if (type == DATABASE_TYPE_UNKNOWN)
				{
					char *tmp = CMemory::printstr("Databázová tabuľka ’%s’ obsahuje stĺpec s nepodporovaným typom. Bude sa ignorovať.",
						structure->tables[count].name);
					app.mainWindow->GetOutputWindow()->AddLine(tmp); _DELETE(tmp);

					//tabulka obsahuje stlpec, ktory ma nepodporovany typ
					//vymaz tabulku
					_DELETE(structure->tables[count].name);
					//zmaz doteraz vytvorene nazvy atributov
					for (DWORD k=0; k<=j; k++)
					{
						_DELETE(structure->tables[count].attributes[k].name);
					}
					_DELETE(structure->tables[count].attributes);
					stop = true;
				}
				if (stop)
				{
					count--;//o chvilu pripocitame a tomu sa musime vyhnut
					break;//zober dalsiu tabulku
				}
				structure->tables[count].attributes[j].type = (BYTE)type;
			}
			_DELETE(res2); res2 = NULL;
			count++;
		}
	} catch (int err)
	{
		err;
		DEBUGPRINT("DEBUG: ERROR: CDatabase :: CreateStructure");
		_DELETE(structure); structure = NULL;
		if (res) {_DELETE(res);}
		if (res2) {_DELETE(res2);}
	}

	structure->nTables = count;

	if (res) {_DELETE(res);}
	if (res2) {_DELETE(res2);}

	_DELETE(win);
}

/************************************************************************************/
/* DbStructure
/************************************************************************************/
CDbStructure::CDbStructure()
{
	nTables = 0;
	tables = NULL;
}

CDbStructure::~CDbStructure()
{
	if (tables)
	{
		for (DWORD i=0; i<nTables; i++)
		{
			for (DWORD j=0; j<tables[i].nAttributes; j++)
			{
				_DELETE(tables[i].attributes[j].name);
				//_DELETE(tables[i].attributes[j].type);
			}
			//delete[] tables[i].attributes; _mem_new_--;
			_DELETE(tables[i].attributes);
			_DELETE(tables[i].name);
		}
		//delete[] tables; _mem_new_--;
		_DELETE(tables);
	}
}

/*******************************************************************************************/
/* CSQLresult
/*******************************************************************************************/
ExecStatusType CSQLresult::GetResult()
{
	return PQresultStatus(res);
}

int CSQLresult::GetnTuples()
{
	return PQntuples(res);
}

const char *CSQLresult::Value(int row, int column)
{
	return PQgetvalue(res, row, column);
}

const char *CSQLresult::GetErrorCode()
{
	return PQresultErrorField(res, PG_DIAG_SQLSTATE);
}
char *CSQLresult::GetError()
{
	return error;
	/*char *str;
	str = PQresultErrorField(res, PG_DIAG_MESSAGE_DETAIL);
	if (!str)
		str = PQresultErrorField(res, PG_DIAG_MESSAGE_PRIMARY);
	return str;*/
}

CSQLresult::CSQLresult()
{
	res = NULL; error = NULL;
}

CSQLresult::~CSQLresult()
{
	if (error) {_DELETE(error);}

	PQclear(res);

}

char *CDatabase::typeNumeric = "numeric";
char *CDatabase::typeString = "text";
char *CDatabase::typeDate = "date";