#include "PgAcl.h"
#include <QStringBuilder>

namespace {

	QString privilegeLetterToKeyword(char c)
	{
		switch (c) {
		case 'r': return "SELECT";
		case 'w': return "UPDATE";
		case 'a': return "INSERT";
		case 'd': return "DELETE";
		case 'D': return "TRUNCATE";
		case 'x': return "REFERENCES";
		case 't': return "TRIGGER";
		case 'X': return "EXECUTE";
		case 'U': return "USAGE";
		case 'C': return "CREATE";
		case 'c': return "CONNECT";
		case 'T': return "TEMPORARY";
		}
		throw std::runtime_error("Unexpected value for c in privilegeLetterToKeyword");
	}

}

char privilegeToChar(Privilege p)
{
	switch (p) {
	case Privilege::Select: return 'r';
	case Privilege::Update: return 'w';
	case Privilege::Insert: return 'a';
	case Privilege::Delete: return 'd';
	case Privilege::Truncate: return 'D';
	case Privilege::References: return 'x';
	case Privilege::Trigger: return 't';
	case Privilege::Execute: return 'X';
	case Privilege::Usage: return 'U';
	case Privilege::Create: return 'C';
	case Privilege::Connect: return 'c';
	case Privilege::Temporary: return 'T';
	}
	throw std::runtime_error("Unexpected value for privilege in privilegeToChar");
}

PgAcl::PgAcl(const QString &acl)
{
	setFromString(acl);
}

void PgAcl::setFromString(const QString &acl)
{
	m_grantee = acl.section('=', 0, 0);
	auto t = acl.section('=', 1);
	m_privileges = t.section('/', 0, 0);
	m_grantor = t.section('/', 1);
}

PrivValue PgAcl::privilege(Privilege priv) const
{
	char c = privilegeToChar(priv);
	return privilege(c);
}

PrivValue PgAcl::privilege(char c) const
{
	int pos = m_privileges.indexOf(c);
	if (pos >= 0) {
		if ( (pos+1) < m_privileges.length() && m_privileges[pos+1] == '*')
			return PrivValue::YesWithGrant;
		return PrivValue::Yes;
	}
	return PrivValue::No;
}

QString PgAcl::singleString() const
{
	return m_grantee % "=" % m_privileges % "/" % m_grantor;
}

//void operator<<(AclList &s, const Pgsql::Value &v)
void operator<<(PgAcl &acl, const Pgsql::Value &v)
{
	acl.setFromString(v);
}

namespace Pgsql {
	template <>
	PgAcl StringToArrayElem<PgAcl>(std::string_view sv)
	{
		return PgAcl(QString::fromUtf8(sv.data(), static_cast<int>(sv.length())));
	}
}

namespace {

	void appendColumn(QString &rights, const QString &column)
	{
		if (!column.isEmpty()) {
			rights += "(" % column % ")";
		}
	}

	QString privilegeGrant(const QString &all_pattern, const QString &acl, const QString &grantOnObject, QString user, const QString &column)
	{
		QString rights;

		if (user.isEmpty())
			user = "PUBLIC";

		if (all_pattern.length() > 1 && acl == all_pattern) {
			rights = "ALL";
			appendColumn(rights, column);
		}
		else {
			for (auto c : acl) {
				if (!rights.isEmpty())
					rights += ", ";
				rights += privilegeLetterToKeyword(c.toLatin1());
				appendColumn(rights, column);
			}
		}
		QString grant;
		if (rights.isEmpty()) {
			grant += "REVOKE ALL";
			appendColumn(grant, column);
		}
		else
			grant += "GRANT " + rights;

		grant += " ON " + grantOnObject;

		if (rights.isEmpty())
			grant += " FROM ";
		else
			grant += " TO ";

		grant += user;

		return grant;
	}

}


QString PgAcl::sql(const QString &all_pattern, const QString &grant_on_object, const QString &column) const
{
	QString acl, acl_with_grant;
	// Doorloop acl en scheid de rechten met GRANT optie van die zonder
	for (auto c : all_pattern) {
		switch(privilege(c.toLatin1())) {
		case PrivValue::No:
			break;
		case PrivValue::Yes:
			acl += c;
			break;
		case PrivValue::YesWithGrant:
			acl_with_grant += c;
			break;
		}
	}

	QString sql;
	// Call when acl is not empty or both are empty
	// needs to be calledwhen both are empty to generate REVOKE
	if (!acl.isEmpty() || acl_with_grant.isEmpty()) {
		sql += privilegeGrant(all_pattern, acl, grant_on_object, m_grantee, column)
				+ ";\n";
	}
	if (!acl_with_grant.isEmpty()) {
		sql += privilegeGrant(all_pattern, acl_with_grant, grant_on_object, m_grantee, column)
				+ " WITH GRANT OPTION;\n";
	}
	return sql;
}

bool PgAcl::empty() const
{
	return m_privileges.isEmpty();
}
