#include "PgDatabaseCatalog.h"

#include "ASyncDBConnection.h"
#include "PgAmContainer.h"
#include "PgAttributeContainer.h"
#include "PgAuthIdContainer.h"
#include "PgClassContainer.h"
#include "PgConstraintContainer.h"
#include "PgDatabaseContainer.h"
#include "PgIndexContainer.h"
#include "PgNamespaceContainer.h"
#include "PgTablespaceContainer.h"
#include "PgTriggerContainer.h"
#include "PgTypeContainer.h"
#include "PgProcContainer.h"
#include "PgCollationContainer.h"
#include "PgInheritsContainer.h"
#include "PgLanguageContainer.h"
#include "PgSequenceContainer.h"
#include "Pgsql_Connection.h"
#include "Pgsql_oids.h"

#include <QThread>
#include <boost/timer/timer.hpp>
#include <boost/chrono/system_clocks.hpp>

using namespace Pgsql;

QString getRoleNameFromOid(const PgDatabaseCatalog &cat, Oid oid)
{
	QString name;
	auto auth_ids = cat.authIds();
	if (auth_ids) {
		const PgAuthId* auth_id = auth_ids->getByKey(oid);
		if (auth_id) {
			name = auth_id->objectName();
		}
	}
	return name;
}

QString getRoleDisplayString(const PgDatabaseCatalog &cat, Oid oid)
{
	QString name = getRoleNameFromOid(cat, oid);
	return name;
}

QString getClassDisplayString(const PgDatabaseCatalog &cat, Oid oid)
{
	QString result;
	auto l = cat.classes();
	auto e = l->getByKey(oid);
	if (e)
		result = e->objectName();
	return result;

}

QString getIndexDisplayString(const PgDatabaseCatalog &cat, Oid oid)
{
	QString result;
//	auto l = cat.indexes();
//	auto e = l->getByKey(oid);
//	if (e)
		result = getClassDisplayString(cat, oid);
	return result;
}

QString getTablespaceDisplayString(const PgDatabaseCatalog &cat, Oid oid)
{
	// TODO load list and lookup name
	if (oid == 0) {
		auto dbname = cat.getDBName();
		oid = cat.databases()->getByName(dbname)->tablespace;
		auto ts = cat.tablespaces()->getByKey(oid);
		return ts->objectName() + " (inherited)";
	}
	else {
		auto ts = cat.tablespaces()->getByKey(oid);
		return ts->objectName();
	}
}

QString getTypeDisplayString(const PgDatabaseCatalog &cat, Oid oid, int32_t typmod)
{
	if (oid == 0) {
		return QString();
	}

	auto tc = cat.types();
	auto t = tc->getByKey(oid);
	if (t == nullptr) {
		return "(invalid/unknown)";
	}
	QString s;
	if (t->category == TypCategory::Array) {
//		auto et = tc->getByKey(t.elem);
//		s = et.name;
		s = getTypeDisplayString(cat, t->elem, typmod);
		s += "[]";
	}
	else {
		s = t->objectName();
		switch (oid) {
		case varchar_oid:
		case char_oid:
		case text_oid:
			if (typmod > 4)
				s += QString::asprintf("(%d)", typmod-4);
			break;
		case numeric_oid:
			if (typmod > 4) {
				int prec = (typmod - 4) / 65536;
				int scale = (typmod - 4) % 65536;
				if (scale > 0)
					s += QString::asprintf("(%d,%d)", prec, scale);
				else
					s += QString::asprintf("(%d)", prec);
			}
			break;
		}
	}
	return s;
}


PgDatabaseCatalog::PgDatabaseCatalog()
{
}

PgDatabaseCatalog::~PgDatabaseCatalog()
{
}

void PgDatabaseCatalog::loadAll(Pgsql::Connection &conn,
								std::function<bool(int, int)> progress_callback)
{
	loadInfo(conn);
	const int count = 12;
	int n = 0;

	auto pf = [&] () -> bool {
			return progress_callback && !progress_callback(++n, count);
		};

	if (pf()) return;

	// First load server objects
	load2(m_authIds, conn);
	if (pf()) return;

	load2(m_tablespaces, conn);
	if (pf()) return;

	load2(m_databases, conn);
	if (pf()) return;


	// Load database objects
	load2(m_languages, conn);
	if (pf()) return;

	load2(m_namespaces, conn);
	if (pf()) return;

	load2(m_collations, conn);
	if (pf()) return;

	load2(m_classes, conn); // needs namespaces
	if (pf()) return;

	load2(m_attributes, conn);
	if (pf()) return;

	load2(m_constraints, conn);
	if (pf()) return;

	load2(m_indexes, conn);
	if (pf()) return;

	load2(m_ams, conn);
	if (pf()) return;

	load2(m_triggers, conn);
	if (pf()) return;

	load2(m_types, conn);
	if (pf()) return;

	load2(m_procs, conn);
	if (pf()) return;

	load2(m_inherits, conn);
	if (pf()) return;

	load2(m_sequences, conn);
//	{
//		if (!m_sequences)
//			m_sequences = std::make_shared<PgSequenceContainer>(*this);
//		m_sequences->load(conn);

//	}
	pf();

	refreshed(this, All);
}

void PgDatabaseCatalog::loadInfo(Pgsql::Connection &conn)
{
	Pgsql::Result r = conn.query("SHOW server_version_num");
	if (r && r.resultStatus() == PGRES_TUPLES_OK)
		if (r.rows() == 1)
			m_serverVersion << r.get(0, 0);

	r = conn.query("SELECT version()");
	if (r && r.resultStatus() == PGRES_TUPLES_OK)
		if (r.rows() == 1)
			m_serverVersionString = r.get(0, 0).asQString();

	m_dbName = conn.getDBName();
}

const QString& PgDatabaseCatalog::serverVersionString() const
{
	return m_serverVersionString;
}

int PgDatabaseCatalog::serverVersion() const
{
	return m_serverVersion;
}

std::shared_ptr<const PgAttributeContainer> PgDatabaseCatalog::attributes() const
{
	return m_attributes;
}

std::shared_ptr<const PgAuthIdContainer> PgDatabaseCatalog::authIds() const
{
	return m_authIds;
}

std::shared_ptr<const PgClassContainer> PgDatabaseCatalog::classes() const
{
	return m_classes;
}

std::shared_ptr<const PgConstraintContainer> PgDatabaseCatalog::constraints() const
{
	return m_constraints;
}

std::shared_ptr<const PgDatabaseContainer> PgDatabaseCatalog::databases() const
{
	return m_databases;
}

std::shared_ptr<const PgIndexContainer> PgDatabaseCatalog::indexes() const
{
	return m_indexes;
}

std::shared_ptr<const PgAmContainer> PgDatabaseCatalog::ams() const
{
	return m_ams;
}

std::shared_ptr<const PgNamespaceContainer> PgDatabaseCatalog::namespaces() const
{
	return m_namespaces;
}

std::shared_ptr<const PgTablespaceContainer> PgDatabaseCatalog::tablespaces() const
{
	return m_tablespaces;
}

std::shared_ptr<const PgTriggerContainer> PgDatabaseCatalog::triggers() const
{
	return m_triggers;
}

std::shared_ptr<const PgTypeContainer> PgDatabaseCatalog::types() const
{
	return m_types;
}

std::shared_ptr<const PgProcContainer> PgDatabaseCatalog::procs() const
{
	return m_procs;
}

std::shared_ptr<const PgCollationContainer> PgDatabaseCatalog::collations() const
{
	return m_collations;
}

std::shared_ptr<const PgInheritsContainer> PgDatabaseCatalog::inherits() const
{
	return m_inherits;
}

std::shared_ptr<const PgLanguageContainer> PgDatabaseCatalog::languages() const
{
	return m_languages;
}

std::shared_ptr<const PgSequenceContainer> PgDatabaseCatalog::sequences() const
{
	return m_sequences;
}
