#include <lua.h>
#include <lauxlib.h>

#include <rpmcli.h>
#include <rpmlib.h>

#include <errno.h>
#include <string.h>
#include <fcntl.h>

static const char RPM_Database[] = "RPM_Database";
static const char RPM_Header[] = "RPM_Header";

static int luaRPM_dbopen(lua_State *L)
{
	char *prefix = NULL;
	int mode = O_RDONLY;
	int perms = 0;
	rpmdb db;

	if (rpmdbOpen(prefix, &db, mode, perms) != 0) {
		lua_pushnil(L);
		lua_pushstring(L, "rpmdbOpen failed");
		return 2;
	}

	void *ptr = lua_newuserdata(L, sizeof db);
	memmove(ptr, &db, sizeof db);
	luaL_getmetatable(L, RPM_Database);
	lua_setmetatable(L, -2);
	return 1;
}

static int luaRPM_fopen(lua_State *L)
{
	const char *fname = luaL_checkstring(L, 1);
	FD_t fd = Fopen(fname, "r");

	if (fd == 0) {
		lua_pushnil(L);
		lua_pushstring(L, strerror(errno));
		lua_pushstring(L, "open");
		return 3;
	}

	Header hdr;
	rpmRC rc = rpmReadPackageHeader(fd, &hdr, 0, 0, 0);
	Fclose(fd);

	if (rc != RPMRC_OK) {
		lua_pushnil(L);
		lua_pushstring(L, "rpmReadPackageHeader failed");
		lua_pushstring(L, "init");
		return 3;
	}

	void *ptr = lua_newuserdata(L, sizeof hdr);
	memmove(ptr, &hdr, sizeof hdr);
	luaL_getmetatable(L, RPM_Header);
	lua_setmetatable(L, -2);
	return 1;
}

static int db_find(lua_State *L, int tag)
{
	rpmdb db = *(rpmdb *)luaL_checkudata(L, 1, RPM_Database);
	luaL_argcheck(L, db != NULL, 1, "RPM_Database expected");

	const char *name = luaL_checkstring(L, 2);

	rpmdbMatchIterator mi = rpmdbInitIterator(db, tag, name, 0);
	Header hdr;
	int n = 0;

	lua_newtable(L);

	while ((hdr = rpmdbNextIterator(mi)) != NULL) {
		headerLink(hdr);
		lua_pushnumber(L, ++n);
		void *ptr = lua_newuserdata(L, sizeof hdr);
		memmove(ptr, &hdr, sizeof hdr);
		luaL_getmetatable(L, RPM_Header);
		lua_setmetatable(L, -2);
		lua_settable(L, -3);
	}

        rpmdbFreeIterator(mi);
	return 1;
}

static int luaRPM_db_pkg(lua_State *L)
{
	db_find(L, RPMTAG_NAME);
	lua_pushnumber(L, 1);
	lua_gettable(L, -2);
	lua_remove(L, -2);
	return 1;
}

static int luaRPM_db_whatrequires(lua_State *L)
{
	return db_find(L, RPMTAG_REQUIRENAME);
}

static int luaRPM_db_whatprovides(lua_State *L)
{
	return db_find(L, RPMTAG_PROVIDENAME);
}

static int tag2num(const char *tag)
{
	int i;
	for (i = 0; i < rpmTagTableSize; i++)
		if (strcasecmp(tag, rpmTagTable[i].name + 7) == 0)
			return rpmTagTable[i].val;
	return -1;
}

static int luaRPM_hdr_tag(lua_State *L)
{
	Header hdr = *(Header *)luaL_checkudata(L, 1, RPM_Header);
	const char *key = luaL_checkstring(L, 2);
	int tag = tag2num(key);

	if (tag < 0) {
		lua_pushnil(L);
		lua_pushfstring(L, "unknown tag: %s", key);
		return 2;
	}

	int type, size;
	char *data;

	if (headerGetEntry(hdr, tag, &type, (void**)&data, &size) == 0) {
		lua_pushnil(L);
		lua_pushfstring(L, "no tag in header: %s", key);
		return 2;
	}

	switch (type) {
	case RPM_NULL_TYPE:
		lua_pushnil(L);
		return 1;
	case RPM_BIN_TYPE:
		lua_pushlstring(L, data, size);
		return 1;
	}

	lua_newtable(L);

	int i;

	switch (type) {
	case RPM_CHAR_TYPE: {
		char *ptr;
		for (ptr = (char *)data, i = 0; i < size; i++, ptr++) {
			lua_pushnumber(L, i + 1);
			lua_pushlstring(L, ptr, 1);
			lua_settable(L, -3);
		}
		break;
	}
	case RPM_INT8_TYPE: {
		int_8 *ptr;
		for (ptr = (int_8 *)data, i = 0; i < size; i++, ptr++) {
			lua_pushnumber(L, i + 1);
			lua_pushnumber(L, *ptr & 0xff);
			lua_settable(L, -3);
		}
		break;
	}
	case RPM_INT16_TYPE: {
		int_16 *ptr;
		for (ptr = (int_16 *)data, i = 0; i < size; i++, ptr++) {
			lua_pushnumber(L, i + 1);
			lua_pushnumber(L, *ptr & 0xffff);
			lua_settable(L, -3);
		}
	}
	case RPM_INT32_TYPE: {
		int_32 *ptr;
		for (ptr = (int_32 *)data, i = 0; i < size; i++, ptr++) {
			lua_pushnumber(L, i + 1);
			lua_pushnumber(L, *ptr);
			lua_settable(L, -3);
		}
		break;
	}
	case RPM_STRING_TYPE:
	case RPM_I18NSTRING_TYPE:
	case RPM_STRING_ARRAY_TYPE: {
                char **ptr;
		if (type == RPM_STRING_TYPE && size == 1) {
			lua_pushnumber(L, 1);
			lua_pushstring(L, data);
			lua_settable(L, -3);
			break;
		}
		for (ptr = (char **)data, i = 0; i < size; i++, ptr++) {
			lua_pushnumber(L, i + 1);
			lua_pushstring(L, *ptr);
			lua_settable(L, -3);
		}
		break;
	}
	default:
		fprintf(stderr, "unimplemented tag type\n");
		break;
	}
	switch (tag) {
	case RPMTAG_ARCH:
	case RPMTAG_ARCHIVESIZE:
	case RPMTAG_BUILDHOST:
	case RPMTAG_BUILDROOT:
	case RPMTAG_BUILDTIME:
	case RPMTAG_COOKIE:
	case RPMTAG_DESCRIPTION:
	case RPMTAG_DISTRIBUTION:
	case RPMTAG_EPOCH:
	case RPMTAG_EXCLUDEARCH:
	case RPMTAG_EXCLUDEOS:
	case RPMTAG_EXCLUSIVEARCH:
	case RPMTAG_EXCLUSIVEOS:
	case RPMTAG_GIF:
	case RPMTAG_GROUP:
	case RPMTAG_ICON:
	case RPMTAG_INSTALLTIME:
	case RPMTAG_LICENSE:
	case RPMTAG_NAME:
	case RPMTAG_OS:
	case RPMTAG_PACKAGER:
	case RPMTAG_RELEASE:
	case RPMTAG_RPMVERSION:
	case RPMTAG_SIZE:
	case RPMTAG_SOURCERPM:
	case RPMTAG_SUMMARY:
	case RPMTAG_URL:
	case RPMTAG_VENDOR:
	case RPMTAG_VERSION:
	case RPMTAG_XPM:
		lua_pushnumber(L, 1);
		lua_gettable(L, -2);
		lua_remove(L, -2);
	}
	return 1;
}

static int luaRPM_hdr_requires(lua_State *L)
{
	lua_pushliteral(L, "REQUIRENAME");
	return luaRPM_hdr_tag(L);
}

static int luaRPM_hdr_provides(lua_State *L)
{
	lua_pushliteral(L, "PROVIDENAME");
	return luaRPM_hdr_tag(L);
}

static int luaRPM_db_gc(lua_State *L)
{
	rpmdb db = *(rpmdb *)luaL_checkudata(L, 1, RPM_Database);
	luaL_argcheck(L, db != NULL, 1, "RPM_Database expected");
	rpmdbClose(db);
	return 0;
}

static int luaRPM_hdr_gc(lua_State *L)
{
	Header hdr = *(Header *)luaL_checkudata(L, 1, RPM_Header);
	headerFree(hdr);
	return 0;
}

static int luaRPM_hdr_tostring(lua_State *L) {
	char buf[48];
	Header hdr = luaL_checkudata(L, 1, RPM_Header);
	snprintf(buf, sizeof(buf), "%s: %p", RPM_Header, hdr);
	lua_pushstring(L, buf);
	return 1;
}

static int luaRPM_db_tostring(lua_State *L) {
	char buf[48];
	rpmdb db = luaL_checkudata(L, 1, RPM_Database);
	snprintf(buf, sizeof(buf), "%s: %p", RPM_Database, db);
	lua_pushstring(L, buf);
	return 1;
}

static const luaL_reg luaRPM_librpm[] = {
	{ "file", luaRPM_fopen },
	{ "database", luaRPM_dbopen },
	{ 0, 0 }
};

static const luaL_reg luaRPM_librpmdb[] = {
	{ "whatrequires", luaRPM_db_whatrequires },
	{ "whatprovides", luaRPM_db_whatprovides },
	{ "pkg", luaRPM_db_pkg },
	{ "__tostring", luaRPM_db_tostring },
	{ "__gc", luaRPM_db_gc },
	{ 0, 0 }
};

static const luaL_reg luaRPM_librpmhdr[] = {
	{ "tag", luaRPM_hdr_tag },
	{ "requires", luaRPM_hdr_requires },
	{ "provides", luaRPM_hdr_provides },
	{ "__tostring", luaRPM_hdr_tostring },
	{ "__gc", luaRPM_hdr_gc },
	{ 0, 0 }
};

LUALIB_API int luaopen_rpm(lua_State *L)
{
	luaL_newmetatable(L, RPM_Header);
	lua_pushliteral(L, "__index");
	lua_pushvalue(L, -2);
	lua_rawset(L, -3);
	luaL_openlib(L, NULL, luaRPM_librpmhdr, 0);
	lua_pop(L, 1);

	luaL_newmetatable(L, RPM_Database);
	lua_pushliteral(L, "__index");
	lua_pushvalue(L, -2);
	lua_rawset(L, -3);
	luaL_openlib(L, NULL, luaRPM_librpmdb, 0);
	lua_pop(L, 1);

        rpmReadConfigFiles(NULL, NULL);
	luaL_openlib(L, "RPM", luaRPM_librpm, 0);
	return 1;
}

