/*
 * Copyright (c) 2004 Scott Ullrich <GeekGod@GeekGod.com> 
 * Portions Copyright (c) 2004 Chris Pressey <cpressey@catseye.mine.nu>
 *
 * Copyright (c) 2004 The DragonFly Project.
 * All rights reserved.
 *
 * This code is derived from software contributed to The DragonFly Project
 * by Scott Ullrich and Chris Pressey (see above for e-mail addresses).
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 *
 * 3. Neither the name of The DragonFly Project nor the names of its
 *    contributors may be used to endorse or promote products derived
 *    from this software without specific, prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
 * COPYRIGHT HOLDERS, CONTRIBUTORS OR VOICES IN THE AUTHOR'S HEAD
 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY
 * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
 * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
 * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
 * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
 * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING,
 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

/* 
 * lua_dfui - LibDFUI Bindings for Lua
 * Written with much help from:
 * http://lua-users.org/wiki/BindingWithMetatableAndClosures
 *
 * $Id: lua_dfui.c,v 1.26 2004/08/10 19:56:52 cpressey Exp $
 */

#include <stdio.h>
#include <unistd.h>

#include "lua.h"
#include "lauxlib.h"
#include "lualib.h"

#include <dfui/dfui.h>
#include <dfui/dump.h>

#include <installer/confed.h>
#include <installer/commands.h>
#include <installer/functions.h>

static void			 usage(char *);

/* utility */

static char			*lua_access_table_string(lua_State *, int, const char *);

/* DFUIConnection */

static void			 lua_dfui_register(lua_State *);

static struct dfui_connection	*lua_check_dfui_connection(lua_State *, int);
static struct dfui_connection	*lua_push_dfui_connection(lua_State *, struct dfui_connection *);

static int			 lua_dfui_connection_new(lua_State *);
static int			 lua_dfui_connection_destroy(lua_State *);

static int			 lua_dfui_be_start(lua_State *);
static int			 lua_dfui_be_stop(lua_State *);
static int			 lua_dfui_be_present(lua_State *);

static struct dfui_form		*dfui_form_from_lua_table(lua_State *, int);
static struct dfui_action	*dfui_action_from_lua_table(lua_State *, int);
static struct dfui_field	*dfui_field_from_lua_table(lua_State *, int);
static struct dfui_dataset	*dfui_dataset_from_lua_table(lua_State *, int);

static int			 lua_table_from_dfui_response(lua_State *, struct dfui_response *);

/* InstallerContext */

void				 lua_installercontext_register(lua_State *L);

static struct i_fn_args *	 lua_check_installercontext(lua_State *L, int index);
static struct i_fn_args *	 lua_push_installercontext(lua_State *L, struct i_fn_args *c);

static int			 lua_installercontext_new(lua_State *L);
static int                       lua_installercontext_destroy(lua_State *L);

/* Commands */

static void			 lua_commands_register(lua_State *L);

static struct commands		*lua_check_commands(lua_State *, int);
static struct commands		*lua_push_commands(lua_State *, struct commands *);

static int			 lua_commands_new(lua_State *L);
static int			 lua_commands_destroy(lua_State *L);

static int			 lua_commands_add(lua_State *L);
static int			 lua_commands_execute(lua_State *L);

/* ConfigVars */

static int			 lua_config_var_set(lua_State *L);
static int			 lua_config_var_get(lua_State *L);

/******** MAIN ********/

int
main(int argc, char **argv)
{
	lua_State *L;
	char line[2048], *prog_name, *script_name;
	int opt, verbose = 0, done = 0;

	prog_name = argv[0];
	script_name = NULL;

	/*
	 * Get command-line arguments.
	 */
	while ((opt = getopt(argc, argv, "v")) != -1) {
		switch(opt) {
		case 'v':
			verbose++;
			break;
		case '?':
		default:
			usage(prog_name);
		}
	}
	argc -= optind;
	argv += optind;

	if (argc > 0)
		script_name = argv[0];

	/*
	 * Create a new Lua-interpreting engine.
	 */
	L = lua_open();

	/*
	 * Add Lua libraries to the interpreter.
	 */
	luaopen_base(L);
	luaopen_table(L);
	luaopen_io(L);
	luaopen_string(L);
	luaopen_math(L);
	luaopen_debug(L);

	/*
	 * Add our custom bindings to the interpreter.
	 */
	lua_dfui_register(L);
	lua_commands_register(L);
	lua_installercontext_register(L);
	/*
	 * Interpret the given Lua program, either from a file
	 * or interactively.
	 */
	if (script_name != NULL) {
		lua_dofile(L, script_name);
	} else {
		while (!done) {
			fprintf(stdout, "==> ");
			fflush(stdout);
			if (fgets(line, 2048, stdin) != NULL)
				lua_dostring(L, line);
			else
				done = 1;
		}
	}

	/*
	 * Shut down the Lua interpreter and exit.
	 */
	lua_close(L);
	return(0);
}

void
usage(char *prog_name)
{
	fprintf(stderr, "Usage: %s [script]\n", prog_name);
	exit(1);
}

/*-------- utilities ---------*/

/*
 * Retrieve a string from a Lua table.
 */
static char *
lua_access_table_string(lua_State *L, int table_idx, const char *key)
{
	char *s;

	lua_pushlstring(L, key, strlen(key));
	lua_gettable(L, table_idx);
	if (lua_isstring(L, lua_gettop(L))) {
		s = (char *)luaL_checkstring(L, lua_gettop(L));
	} else {
		s = "";
	}

	/* fprintf(stderr, "key(%s) -> value(%s)\n", key, s); */
	return(s);
}

int
get_transport(const char *transport_string)
{
	if (strcmp(transport_string, "tcp") == 0) {
		return(DFUI_TRANSPORT_TCP);
	} else if (strcmp(transport_string, "caps") == 0) {
		return(DFUI_TRANSPORT_CAPS);
	} else if (strcmp(transport_string, "npipe") == 0) {
		return(DFUI_TRANSPORT_NPIPE);
	} else {
		return(-1);
	}
}

/************************ CUSTOM BINDING: DFUI ************************/

/**** Binding Tables ****/

static const luaL_reg dfui_connection_methods[] = {
	{"new",		lua_dfui_connection_new },
	{"start",	lua_dfui_be_start },
	{"stop",	lua_dfui_be_stop },
	{"present",	lua_dfui_be_present },
	{"configset", 	lua_config_var_set },
	{"configget",	lua_config_var_get },
	{0, 0}
};

static const luaL_reg dfui_connection_meta_methods[] = {
	{"__gc",	lua_dfui_connection_destroy },
	{0, 0}
};


/*** REGISTRATION ***/

void
lua_dfui_register(lua_State *L)
{
	int metatable_idx, methods_idx;

	lua_pushliteral(L, "DFUIConnection");	/* name of our 'class' table */
	lua_newtable(L);			/* Create connection methods table */
	methods_idx = lua_gettop(L);         	/* Find its position on the stack */
	lua_newtable(L);			/* Create connection metatable */
	metatable_idx = lua_gettop(L);		/* Find its position on the stack */
	lua_pushliteral(L, "__index");		/* add index event to metatable */
	lua_pushvalue(L, methods_idx);
	lua_settable(L, metatable_idx);		/* metatable.__index = methods */
	lua_pushliteral(L, "__metatable");	/* hide metatable */
	lua_pushvalue(L, methods_idx);
	lua_settable(L, metatable_idx);		/* metatable.__metatable = methods */
	luaL_openlib(L, 0, dfui_connection_meta_methods,  0);	/* fill metatable */
	luaL_openlib(L, 0, dfui_connection_methods, 1); /* fill connection methods table */
	lua_settable(L, LUA_GLOBALSINDEX);	/* add DFUIConnection to globals */
}

/*** UTILITIES ***/

/*
 * Check that the Lua data object at `index' on Lua's stack is
 * a userdata object of type dfui_connection.  If not, complain,
 * but if so, return the object.
 */
static struct dfui_connection *
lua_check_dfui_connection(lua_State *L, int index)
{
	luaL_checktype(L, index, LUA_TUSERDATA);
	lua_getmetatable(L, index);
	if (!lua_equal(L, lua_upvalueindex(1), -1))
		luaL_typerror(L, index, "DFUIConnection");  /* die */
	lua_pop(L, 1);
	return((struct dfui_connection *)lua_unboxpointer(L, index));
}

/*
 * Put a dfui_connection on Lua's stack.
 */
static struct dfui_connection *
lua_push_dfui_connection(lua_State *L, struct dfui_connection *c)
{
	lua_boxpointer(L, c);
	lua_pushvalue(L, lua_upvalueindex(1));
	lua_setmetatable(L, -2);
	return(c);
}

/*** CONSTRUCTOR & DESTRUCTOR ***/

static int
lua_dfui_connection_new(lua_State *L)
{
	const char *transport_string, *rendezvous;
	int transport;
	struct dfui_connection *c;

	transport_string = luaL_checkstring(L, 1);
	rendezvous = luaL_checkstring(L, 2);
	if ((transport = get_transport(transport_string)) < 0) {
		lua_pushnil(L);
		return(1);
	}
	c = dfui_connection_new(transport, (char *)rendezvous);
	lua_push_dfui_connection(L, c);
	return(1);
}

static int
lua_dfui_connection_destroy(lua_State *L)
{
	struct dfui_connection *c;

	c = (struct dfui_connection *)lua_unboxpointer(L, 1);
	if (c != NULL)
		dfui_connection_free(c);
	return(0);
}

/*** BOUND METHODS ***/

static int
lua_dfui_be_start(lua_State *L)
{
	struct dfui_connection *c;
	int result;

	c = lua_check_dfui_connection(L, 1);
	result = dfui_be_start(c);
	lua_pushnumber(L, result);

	return(1);
}

static int
lua_dfui_be_stop(lua_State *L)
{
	struct dfui_connection *c;
	int result;

	c = lua_check_dfui_connection(L, 1);
	result = dfui_be_stop(c);
	lua_pushnumber(L, result);

	return(1);
}

static int
lua_dfui_be_present(lua_State *L)
{
	struct dfui_connection *c;
	struct dfui_form *f;
	struct dfui_response *r;

	c = lua_check_dfui_connection(L, 1);
	luaL_checktype(L, 2, LUA_TTABLE);

	f = dfui_form_from_lua_table(L, 2);

	if (dfui_be_present(c, f, &r)) {
		lua_table_from_dfui_response(L, r);
	} else {
		lua_pushnil(L);
	}

	dfui_response_free(r);
	dfui_form_free(f);
	return(1);
}

/*** TRANSLATORS ***/

/*
 * Pop a Lua table representing a DFUI form from the Lua stack,
 * create a new DFUI form from it, and return it.
 */
static struct dfui_form *
dfui_form_from_lua_table(lua_State *L, int table_idx)
{
	struct dfui_form *f;
	struct dfui_action *a;
	struct dfui_field *fi;
	struct dfui_dataset *ds;
	char *id, *name, *short_desc, *long_desc;
	int list_idx, subtable_idx, counter, done;

	/*
	 * Get the basic properties of the form.
	 */
	id = lua_access_table_string(L, table_idx, "id");
	name = lua_access_table_string(L, table_idx, "name");
	short_desc = lua_access_table_string(L, table_idx, "short_desc");
	long_desc = lua_access_table_string(L, table_idx, "long_desc");

	/*
	 * Create the initial form.
	 */
	f = dfui_form_new(dfui_id_new(id),
	    dfui_info_new(name, short_desc, long_desc));

	/*
	 * Get the list of actions attached to the form.
	 */
	lua_pushliteral(L, "actions");
	lua_gettable(L, table_idx);
	list_idx = lua_gettop(L);
	if (lua_istable(L, list_idx)) {
		/*
		 * Loop through all entries in this table, creating
		 * and attaching a new action for each one.
		 */
		counter = 1;
		done = 0;
		while (!done) {
			lua_pushnumber(L, counter++);
			lua_gettable(L, list_idx);
			subtable_idx = lua_gettop(L);
			if (lua_istable(L, subtable_idx)) {
				a = dfui_action_from_lua_table(L, subtable_idx);
				dfui_form_action_attach(f, a);
			} else {
				done = 1;
			}
		}
	} else {
		/* No actions */
	}
	lua_pop(L, 1);

	/*
	 * Get the list of fields attached to the form.
	 */
	lua_pushliteral(L, "fields");
	lua_gettable(L, table_idx);
	list_idx = lua_gettop(L);
	if (lua_istable(L, list_idx)) {
		/*
		 * Loop through all entries in this table, creating
		 * and attaching a new field for each one.
		 */
		counter = 1;
		done = 0;
		while (!done) {
			lua_pushnumber(L, counter++);
			lua_gettable(L, list_idx);
			subtable_idx = lua_gettop(L);
			if (lua_istable(L, subtable_idx)) {
				fi = dfui_field_from_lua_table(L, subtable_idx);
				dfui_form_field_attach(f, fi);
			} else {
				done = 1;
			}
		}
	} else {
		/* No fields */
	}
	lua_pop(L, 1);

	/*
	 * Get the list of datasets attached to the form.
	 */
	lua_pushliteral(L, "datasets");
	lua_gettable(L, table_idx);
	list_idx = lua_gettop(L);
	if (lua_istable(L, list_idx)) {
		/*
		 * Loop through all entries in this table, creating
		 * and attaching a new dataset for each one.
		 */
		counter = 1;
		done = 0;
		while (!done) {
			lua_pushnumber(L, counter++);
			lua_gettable(L, list_idx);
			subtable_idx = lua_gettop(L);
			if (lua_istable(L, subtable_idx)) {
				ds = dfui_dataset_from_lua_table(L, subtable_idx);
				dfui_form_dataset_add(f, ds);
			} else {
				done = 1;
			}
		}
	} else {
		/* No datasets */
	}
	lua_pop(L, 1);

	/*
	 * Finally, delete the table representing the form.
	 * XXX This assumes the table is on the top of the stack
	 * (which is probably a reasonable assumption to make.)
	 */
	lua_pop(L, 1);

	return(f);
}

/*
 * Pop a Lua table representing a DFUI action from the Lua stack,
 * create a new DFUI action from it, and return it.
 */
static struct dfui_action *
dfui_action_from_lua_table(lua_State *L, int table_idx)
{
	struct dfui_action *a;
	char *id, *name, *short_desc, *long_desc;

	/*
	 * Get the basic properties of the action.
	 */
	id = lua_access_table_string(L, table_idx, "id");
	name = lua_access_table_string(L, table_idx, "name");
	short_desc = lua_access_table_string(L, table_idx, "short_desc");
	long_desc = lua_access_table_string(L, table_idx, "long_desc");

	/*
	 * Create the initial action.
	 */
	a = dfui_action_new(dfui_id_new(id),
	    dfui_info_new(name, short_desc, long_desc));

	lua_pop(L, 1);
	return(a);
}

/*
 * Pop a Lua table representing a DFUI field from the Lua stack,
 * create a new DFUI field from it, and return it.
 */
static struct dfui_field *
dfui_field_from_lua_table(lua_State *L, int table_idx)
{
	struct dfui_field *fi;
	char *id, *name, *short_desc, *long_desc;

	/*
	 * Get the basic properties of the field.
	 */
	id = lua_access_table_string(L, table_idx, "id");
	name = lua_access_table_string(L, table_idx, "name");
	short_desc = lua_access_table_string(L, table_idx, "short_desc");
	long_desc = lua_access_table_string(L, table_idx, "long_desc");

	/*
	 * Create the initial field.
	 */
	fi = dfui_field_new(dfui_id_new(id),
	    dfui_info_new(name, short_desc, long_desc));

	lua_pop(L, 1);
	return(fi);
}

/*
 * Pop a Lua table representing a DFUI dataset from the Lua stack,
 * create a new DFUI dataset from it, and return it.
 */
static struct dfui_dataset *
dfui_dataset_from_lua_table(lua_State *L, int table_idx)
{
	struct dfui_dataset *ds;

	/*
	 * Create the initial dataset.
	 */
	ds = dfui_dataset_new();

	/*
	 * Traverse the table, looking for key->value pairs that we can use.
	 */
	lua_pushnil(L);
	while (lua_next(L, table_idx) != 0) {
		if (lua_isstring(L, -2) && lua_isstring(L, -1)) {
			dfui_dataset_celldata_add(ds,
			    dfui_id_new((char *)lua_tostring(L, -2)),
			    (char *)lua_tostring(L, -1));
		} else {
			/* Bogus, just skip it */
		}

		/*
		 * Remove the value, but leave the key for the next iteration.
		 */
		lua_pop(L, 1);
	}	

	/*
	 * Remove the table.
	 */
	lua_pop(L, 1);
	return(ds);
}

/*
 * Push a new Lua table representing the given DFUI response
 * onto the Lua stack.
 */
static int
lua_table_from_dfui_response(lua_State *L, struct dfui_response *r)
{
	int table_idx, list_idx, subtable_idx;
	dfui_id_t f_id, a_id;
	struct dfui_dataset *ds;
	struct dfui_celldata *cd;
	char *value;
	int counter = 1;

	lua_newtable(L);
	table_idx = lua_gettop(L);

	/*
	 * Add response id's to the table.
	 */
	f_id = dfui_response_get_form_id(r);
	a_id = dfui_response_get_action_id(r);

	lua_pushliteral(L, "form_id");
	lua_pushlstring(L, (const char *)f_id, strlen(f_id));
	lua_settable(L, table_idx);

	lua_pushliteral(L, "action_id");
	lua_pushlstring(L, (const char *)a_id, strlen(a_id));
	lua_settable(L, table_idx);

	/*
	 * Create 'datasets' lists to the table.
	 */
	lua_pushliteral(L, "datasets");
	lua_newtable(L);
	list_idx = lua_gettop(L);
	
	/*
	 * Add response datasets to the 'datasets' list.
	 */
	for (ds = dfui_response_dataset_get_first(r); ds != NULL;
	     ds = dfui_dataset_get_next(ds)) {
		lua_pushnumber(L, counter++);
		lua_newtable(L);
		subtable_idx = lua_gettop(L);
		/*
		 * Populate this subtable with the celldatas...
		 */
		for (cd = dfui_dataset_celldata_get_first(ds); cd != NULL;
		     cd = dfui_celldata_get_next(cd)) {
			f_id = dfui_celldata_get_field_id(cd);
			value = dfui_celldata_get_value(cd);
			lua_pushlstring(L, (const char *)f_id, strlen(f_id));
			lua_pushlstring(L, (const char *)value, strlen(value));
			lua_settable(L, subtable_idx);
		}
		/*
		 * Add this subtable to the list
		 */
		lua_settable(L, list_idx);
	}

	/*
	 * Add the 'datasets' list to the table.
	 */
	lua_settable(L, table_idx);

	return(table_idx);
}

/**************** CUSTOM BINDINGS: INSTALLERCONTEXT (libinstaller) *******/

static const luaL_reg installercontext_methods[] = {
        {"new",         lua_installercontext_new },
        {0, 0}
};

static const luaL_reg installercontext_meta_methods[] = {
        {"__gc",        lua_installercontext_destroy },
        {0, 0}
};

/*** REGISTRATION ***/

void
lua_installercontext_register(lua_State *L)
{
        int metatable_idx, methods_idx;

        lua_pushliteral(L, "InstallerContext"); /* name of our 'class' table */
        lua_newtable(L);                        /* Create commands methods table */
        methods_idx = lua_gettop(L);            /* Find its position on the stack */
        lua_newtable(L);                        /* Create commands metatable */
        metatable_idx = lua_gettop(L);          /* Find its position on the stack */
        lua_pushliteral(L, "__index");          /* add index event to metatable */
        lua_pushvalue(L, methods_idx);
        lua_settable(L, metatable_idx);         /* metatable.__index = methods */
        lua_pushliteral(L, "__metatable");      /* hide metatable */
        lua_pushvalue(L, methods_idx);
        lua_settable(L, metatable_idx);         /* metatable.__metatable = methods */
        luaL_openlib(L, 0, installercontext_meta_methods,  0);   /* fill metatable */
        luaL_openlib(L, 0, installercontext_methods, 1); /* fill commands methods table */
        lua_settable(L, LUA_GLOBALSINDEX);      /* add Commands to globals */
}

/*
 * Check that the Lua data object at `index' on Lua's stack is
 * a userdata object of type `installercontext'.  If not, complain,
 * but if so, return the object.
 */
static struct i_fn_args *
lua_check_installercontext(lua_State *L, int index)
{
        luaL_checktype(L, index, LUA_TUSERDATA);
        lua_getmetatable(L, index);
        if (!lua_equal(L, lua_upvalueindex(1), -1))
                luaL_typerror(L, index, "InstallerContext");  /* die */
        lua_pop(L, 1);
        return((struct i_fn_args *)lua_unboxpointer(L, index));
}

/*
 * Put a `installercontext' object on Lua's stack.
 */
static struct i_fn_args *
lua_push_installercontext(lua_State *L, struct i_fn_args *c)
{
        lua_boxpointer(L, c);
        lua_pushvalue(L, lua_upvalueindex(1));
        lua_setmetatable(L, -2);
        return(c);
}

/*** CONSTRUCTOR/DESTRUCTOR ***/

static int
lua_installercontext_new(lua_State *L)
{
	struct i_fn_args *a;
	const char *os_root, *def_tmp_dir, *transport_string, *rendezvous;
	int transport;

	os_root = luaL_checkstring(L, 1);
	def_tmp_dir = luaL_checkstring(L, 2);
	transport_string = luaL_checkstring(L, 3);
	rendezvous = luaL_checkstring(L, 4);
	if ((transport = get_transport(transport_string)) < 0) {
		lua_pushnil(L);
		return(1);
	}
	if ((a = i_fn_args_new((char *)os_root, (char *)def_tmp_dir,
	    transport, (char *)rendezvous)) == NULL) {
		lua_pushnil(L);
		return(1);
	}
	lua_push_installercontext(L, a);
        return(1);
}

static int
lua_installercontext_destroy(lua_State *L)
{
	struct i_fn_args *a;

	a = (struct i_fn_args *)lua_unboxpointer(L, 1);
	if (a != NULL)
		i_fn_args_free(a);
        return(0);
}

/**************** CUSTOM BINDING: CommandChain (libinstaller) ****************/

/**** Binding Tables ****/

static const luaL_reg commands_methods[] = {
	{"new",		lua_commands_new },
	{"add",		lua_commands_add },
	{"execute",	lua_commands_execute },
	{0, 0}
};

static const luaL_reg commands_meta_methods[] = {
	{"__gc", 	lua_commands_destroy },
	{0, 0}
};

/*** REGISTER ***/

void 
lua_commands_register(lua_State *L)
{
        int metatable_idx, methods_idx;

        lua_pushliteral(L, "CommandChain");	/* name of our 'class' table */
        lua_newtable(L);                        /* Create commands methods table */
        methods_idx = lua_gettop(L);            /* Find its position on the stack */
        lua_newtable(L);                        /* Create commands metatable */
        metatable_idx = lua_gettop(L);          /* Find its position on the stack */
        lua_pushliteral(L, "__index");          /* add index event to metatable */
        lua_pushvalue(L, methods_idx);
        lua_settable(L, metatable_idx);         /* metatable.__index = methods */
        lua_pushliteral(L, "__metatable");      /* hide metatable */
        lua_pushvalue(L, methods_idx);
        lua_settable(L, metatable_idx);         /* metatable.__metatable = methods */
        luaL_openlib(L, 0, commands_meta_methods,  0);   /* fill metatable */
        luaL_openlib(L, 0, commands_methods, 1); /* fill commands methods table */
        lua_settable(L, LUA_GLOBALSINDEX);      /* add Commands to globals */
}

/*** UTILITIES ***/

/*
 * Check that the Lua data object at `index' on Lua's stack is
 * a userdata object of type `commands'.  If not, complain,
 * but if so, return the object.
 */
static struct commands *
lua_check_commands(lua_State *L, int index)
{
	luaL_checktype(L, index, LUA_TUSERDATA);
	lua_getmetatable(L, index);
	if (!lua_equal(L, lua_upvalueindex(1), -1))
		luaL_typerror(L, index, "CommandChain");  /* die */
	lua_pop(L, 1);
	return((struct commands *)lua_unboxpointer(L, index));
}

/*
 * Put a `commands' object on Lua's stack.
 */
static struct commands *
lua_push_commands(lua_State *L, struct commands *c)
{
	lua_boxpointer(L, c);
	lua_pushvalue(L, lua_upvalueindex(1));
	lua_setmetatable(L, -2);
	return(c);
}

/*** CONSTRUCTOR/DESTRUCTOR ***/

static int 
lua_commands_new(lua_State *L)
{
	struct commands *cmds;
	cmds = commands_new();
	lua_push_commands(L, cmds);
	return(1);
}

static int 
lua_commands_destroy(lua_State *L)
{
	struct commands *cmds;
	cmds = (struct commands *)lua_unboxpointer(L, 1);
	if (cmds != NULL)
		commands_free(cmds);
	return(0);
}

/******* METHODS *******/

static int 
lua_commands_add(lua_State *L)
{
	const char *command;
	struct commands *cmds;

	cmds = lua_check_commands(L, 1);
	command = luaL_checkstring(L, 2);

	command_add(cmds, "%s", (char *)command);

	return(0);
}

static int 
lua_commands_execute(lua_State *L)
{
	struct i_fn_args *a;
	struct commands *cmds;
	int result;

	cmds = lua_check_commands(L, 1);
	a = lua_check_installercontext(L, 2);

	result = commands_execute(a, cmds);
	lua_pushnumber(L, result);

	return(1);
}

/**************** CUSTOM BINDING: CONFIGVARS (libinstaller) ****************/

/*
 * Get a /etc/rc.conf value 
 *
 */
static int
lua_config_var_get(lua_State *L)
{
	struct config_vars *rc_conf;
	const char *name;
	const char *value;
	const char *os_root;
	const char *cfg_root;
	int status;

	name = luaL_checkstring(L, 1);
	os_root = luaL_checkstring(L, 2);
        cfg_root = luaL_checkstring(L, 3);

	rc_conf = config_vars_new();

	value = config_var_get(rc_conf, (char *)name);

	status = lua_checkstack (L, 256);

	if(status) {
		// There is room on the lua stack
		lua_pushlstring (L, value, 256);
	} else {
		return -1; // no stack available, error back
	}

	return 0;
}

/*
 * Set a /etc/rc.conf like value
 *
 */
static int
lua_config_var_set(lua_State *L)
{
	struct config_vars *rc_conf;
	const char *name;
	const char *value;
	const char *os_root;
	const char *cfg_root;

	name = luaL_checkstring(L, 1);
	value = luaL_checkstring(L, 2);
	os_root = luaL_checkstring(L, 3);
	cfg_root = luaL_checkstring(L, 4);

	rc_conf = config_vars_new();

	config_var_set(rc_conf, (char *)name, (char *)value);

	config_vars_write(rc_conf, CONFIG_TYPE_SH, "%s%setc/rc.conf", os_root, cfg_root);

	config_vars_free(rc_conf);

	return 0; 
}
