#include "DatabaseWorker.hpp"

#include <QList>
#include <QMap>
#include <QSqlDatabase>
#include <QSqlError>
#include <QSqlIndex>
#include <QSqlQuery>
#include <QSqlRecord>
#include <QString>
#include <QVariant>
#include <QVector>

namespace SqlWorker
{
	QString getDatabaseDriver(SqlWorker::DatabaseType type)
	{
		switch (type)
		{
			case DB2:
				return "QDB2";
			case IBASE:
				return "QIBASE";
			case MYSQL:
				return "QMYSQL";
			case OCI:
				return "QOCI";
			case ODBC:
				return "QODBC";
			case POSTGRES:
				return "QPSQL";
			case SQLITE:
				return "QSQLITE";
			default:
				return "UNKOWN";
		}
	}
}

SqlWorker::DatabaseWorker::DatabaseWorker(	const DatabaseType databaseType,
											const QString &sqlServer,
											const QString &database,
											const QString &user,
											const QString &password)
{
	QSqlDatabase db = QSqlDatabase::addDatabase(SqlWorker::getDatabaseDriver(databaseType), database);
	db.setHostName(sqlServer);
	db.setDatabaseName(database);
	db.setUserName(user);
	db.setPassword(password);
	databaseName = database;
}

SqlWorker::DatabaseWorker::~DatabaseWorker()
{
	QSqlDatabase::removeDatabase(this->databaseName);
}

QList<QVector<QString> *>* SqlWorker::DatabaseWorker::searchEntries(const QString &table,
																	  const QMap<QString, QString> *filter,
																	  const QVector<QString> *columns,
																	  const QString sortBy,
																	  const bool sortAscending)
{
	QString queryString = "SELECT ";

	// which columns should appear in the result set?
	if (columns != nullptr)
	{
		for (int i = 0; i < columns->count(); ++i)
		{
			queryString += columns->at(i);
			if (i < columns->count() - 1) { queryString += ", "; }
		}
	}
	else
	{
		queryString += "* ";
	}

	// in which table (or expression) we should search?
	queryString += " FROM " + table;

	// with which conditions we should search?
	if (filter != nullptr)
	{
		queryString += " WHERE ";
		size_t length = filter->size();
		size_t cur_pos = 0;
		auto tuple = filter->begin();
		while (tuple != filter->end())
		{
			queryString += tuple.key() + " LIKE '%" + tuple.value() + "%'";
			if (cur_pos != length - 1)
			{
				queryString += " AND ";
			}
			++tuple;
			++cur_pos;
		}
	}

	// should we order the result?
	if (sortBy != "")
	{
		queryString += " ORDER BY " + sortBy;
		queryString += sortAscending ? " ASC" : " DSC";	// ordering ascending or descening?
	}

	// execution
	QList<QVector<QString>*> *res = new QList<QVector<QString>*>();

	QSqlDatabase db = QSqlDatabase::database(databaseName);
	QSqlQuery query(db);
	if (db.open()&& query.exec(queryString))
	{
		while (query.next())
		{
			int numberOfColumns = query.record().count();
			QVector<QString> *results = new QVector<QString>();
			for (int i = 0; i < numberOfColumns; ++i) { results->append(query.value(i).toString());}
			res->append(results);
		}
	}
	else
	{
		setLastError(query.lastError().text());
		delete res;
		res = nullptr;
	}
	db.close();
	return res;
}

bool SqlWorker::DatabaseWorker::deleteEntry(const QString &table, const QMap<QString, QString> &filter)
{
	QString queryString = "DELETE FROM "  + table + " WHERE ";
	auto tuple = filter.begin();
	size_t size = filter.size();
	size_t cur_pos = 0;
	while (tuple != filter.end())
	{
		queryString += tuple.key() + "=" + "'" + tuple.value() + "'";
		if (cur_pos != size - 1) { queryString += " AND "; }
		++tuple;
		++cur_pos;
	}
	return executeSQL(queryString);
}

bool SqlWorker::DatabaseWorker::insertNewEntry(const QString &table, const QMap<QString, QString> &valueTuples)
{
	int numberOfCols = QSqlDatabase::database(databaseName).record(table).count();

	if (valueTuples.count() != numberOfCols)	// not enough values provided
	{
		lastError = tr("Error: Wrong number of values.\nExpected: %1.\nReceived: %2.")
					.arg(numberOfCols)
					.arg(valueTuples.count());
		return false;
	}

	#pragma region build query string

	QString queryString = "INSERT INTO " + table + " ";

	//columns
	QList<QString> columnNames = valueTuples.keys();
	queryString += "(";
	for (int i = 0; i < columnNames.count(); ++i)
	{
		queryString += columnNames[i];
		if (i < columnNames.count() - 1) { queryString += ","; }
	}
	queryString += ")";

	//values
	QList<QString> values = valueTuples.values();
	queryString += " VALUES (";
	for (int i = 0; i < values.count(); ++i)
	{
		queryString += values[i] == "NULL" ? "NULL" : "'" + values[i] + "'";
		if (i < values.count() - 1) { queryString += ","; }
	}
	queryString += ")";

	#pragma endregion

	return executeSQL(queryString);
}

bool SqlWorker::DatabaseWorker::updateEntries(const QString &table,
												const QMap<QString, QString> &filter,
												const QMap<QString, QString> &newVaules)
{
	QString queryString = "UPDATE " + table;

#pragma region query string part 2 (set)

	queryString += " SET ";
	auto p2_tuple = newVaules.begin();
	size_t p2_size = newVaules.size();
	size_t p2_cur_pos = 0;
	while (p2_tuple != newVaules.end())
	{
		queryString += p2_tuple.key() + "=";
		queryString += p2_tuple.value() == "NULL" ? "NULL" : "'" + p2_tuple.value() + "'";
		if (p2_cur_pos != p2_size - 1) { queryString += ","; }
		++p2_tuple;
		++p2_cur_pos;
	}

#pragma endregion

#pragma region query string part 3 (where)

	queryString += " WHERE ";
	auto p3_tuple = filter.begin();
	size_t p3_size = filter.size();
	size_t p3_cur_pos = 0;
	while (p3_tuple != filter.end())
	{
		queryString += p3_tuple.key() + "=" + "'" + p3_tuple.value() + "'";
		if (p3_cur_pos != p3_size - 1) { queryString += " AND "; }
		++p3_tuple;
		++p3_cur_pos;
	}

#pragma endregion

	// execute and return
	return executeSQL(queryString);
}

QString& SqlWorker::DatabaseWorker::getLastError()
{
	return lastError;
}

void SqlWorker::DatabaseWorker::setLastError(QString additionalError)
{
	lastError = QSqlDatabase::database(this->databaseName).lastError().text();
	if (additionalError != "") { lastError += "\n" + additionalError; }
}

bool SqlWorker::DatabaseWorker::executeSQL(QString &commandString)
{
	QSqlDatabase db = QSqlDatabase::database(this->databaseName);
	bool sucess = db.open();

	QSqlQuery query(db);

	sucess &= query.exec(commandString);
	if(!sucess) { setLastError(query.lastError().text()); }
	db.close();
	return sucess;
}
