/*
 *   Copyright (c) 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007
 *   NOVELL (All rights reserved)
 *
 *   This program is free software; you can redistribute it and/or
 *   modify it under the terms of version 2 of the GNU General Public
 *   License published by the Free Software Foundation.
 *
 *   This program is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU General Public License for more details.
 *
 *   You should have received a copy of the GNU General Public License
 *   along with this program; if not, contact Novell, Inc.
 */

#include <assert.h>
#include <ctype.h>
#include <stdlib.h>
#include <stdio.h>
#include <stdarg.h>
#include <string.h>
#include <linux/limits.h>

#include <string>

/* #define DEBUG */

#include "parser.h"
#include "profile.h"
#include "mount.h"
#include "dbus.h"
#include "symtab.h"

/* doesn't handle variables in options atm */
int expand_entry_variables(char **name)
{
	return variable::expand_by_alternation(name);
}

static int process_variables_in_entries(struct cod_entry *entry_list)
{
	int error = 0;
	struct cod_entry *entry;

	list_for_each(entry_list, entry) {
		error = expand_entry_variables(&entry->name);
		if (error)
			return error;
		if (entry->link_name) {
			error = expand_entry_variables(&entry->link_name);
			if (error)
				return error;
		}
	}

	return 0;
}

static int process_variables_in_rules(Profile &prof)
{
	for (RuleList::iterator i = prof.rule_ents.begin(); i != prof.rule_ents.end(); i++) {
		if ((*i)->skip())
			continue;
		int error = (*i)->expand_variables();
		if (error)
			return error;
	}

	return 0;
}


static int process_variable_in_attach_disconnected(char **disconnected)
{
	int error = expand_entry_variables(disconnected);
	if (error)
		return error;
	filter_slashes(*disconnected);
	// TODO: semantic check should go somewhere else
	if ((*disconnected)[0] != '/')
		yyerror(_("attach_disconnected path must begin with a /"));
	int n = strlen(*disconnected);
	// removing trailing / */
	while (n && (*disconnected)[n-1] == '/')
		(*disconnected)[--n] = 0;
	return error;
}

static int process_variables_in_name(Profile &prof)
{
	/* this needs to be done before alias expansion, ie. altnames are
	 * setup
	 */
	int error = expand_entry_variables(&prof.name);
	if (!error) {
		if (prof.attachment)
			error = expand_entry_variables(&prof.attachment);
		else if (prof.name[0] == '/') {
			/* had to wait to do this until after processing the
			 * variables in the profile name
			 */
			prof.attachment = strdup(local_name(prof.name));
			if (!prof.attachment) {
				errno = ENOMEM;
				return -1;
			}
			filter_slashes(prof.attachment);
		}
	}

	if (!error && prof.flags.disconnected_path)
		error = process_variable_in_attach_disconnected(&prof.flags.disconnected_path);
	if (!error && prof.flags.disconnected_ipc)
		error = process_variable_in_attach_disconnected(&prof.flags.disconnected_ipc);
	return error;
}

static std::string escape_re(std::string str)
{
	for (size_t i = 0; i < str.length(); i++) {
		if (str[i] == '\\') {
			/* skip \ and follow char. Skipping \ and first
			 * char is enough for multichar escape sequence
			 */
			i++;
			continue;
		}
		if (strchr("{}[]*?", str[i]) != NULL) {
			str.insert(i++, "\\");
		}
	}

	return str;
}

// saved_var adds a variable to the global symbol table upon destruction.
class saved_var final {
	std::string var_name;
	const variable *saved = nullptr;

	public:
	saved_var() = default;
	saved_var(const saved_var &) = delete;
	saved_var &operator=(const saved_var &) = delete;

	// ~saved_var restores the global symbol table variable previously saved with [save].
	~saved_var() {
		if (var_name.empty()) return;
		auto tmp = symtab::delete_var(var_name.c_str());
		delete tmp;
		if (saved) {
			(void)symtab::add_var(*saved); // We ignore errors since the variable was just removed.
			delete saved;
		}
	}

	// save saves the current state of the given variable to restore later.
	void save(const char *name) {
		if (saved) {
			delete saved;
			saved = nullptr;
		}
		var_name = name;
		saved = symtab::delete_var(name);
	}
};

int process_profile_variables(Profile *prof)
{
	int error = 0;
	saved_var saved_profile_name;
	saved_var saved_exec_path;
	saved_var saved_attach_path;

	/* needs to be before PROFILE_NAME_VARIABLE so that variable will
	 * have the correct name
	 */
	error = process_variables_in_name(*prof);
	if (error)
		return error;

	/* escape profile name elements that could be interpreted as
	 * regular expressions.
	 */

	saved_profile_name.save(PROFILE_NAME_VARIABLE);
	error = symtab::add_var(PROFILE_NAME_VARIABLE, escape_re(prof->get_name(false)).c_str());
	if (error)
		return error;

	if (prof->attachment) {
		/* IF we didn't want a path based profile name to generate
		 * an attachment. The code could be moved here. Add the
		 * output fed into the vars directly instead of setting
		 * the attachment.
		 */
		/* need to take into account alias, but not yet */
		saved_attach_path.save(PROFILE_ATTACH_VAR);
		error = symtab::add_var(PROFILE_ATTACH_VAR, (const char*) prof->attachment);
		if (error)
			return error;
		/* update to use kernel vars if available */
		saved_exec_path.save(PROFILE_EXEC_VAR);
		error = symtab::add_var(PROFILE_EXEC_VAR, (const char*) prof->attachment);
		if (error)
			return error;
	}

	error = process_variables_in_entries(prof->entries);
	if (error)
		return error;
	error = process_variables_in_rules(*prof);

	return error;
}

#ifdef UNIT_TEST

#include "unit_test.h"

int test_split_string(void)
{
	int rc = 0;
	char *tst_string;
	const char *prefix = "abcdefg";
	const char *var = "boogie";
	const char *suffix = "suffixication";
	std::tuple<std::string, std::string, std::string> result;
	std::string result_prefix;
	std::string result_var;
	std::string result_suffix;
	char *pvar;

	asprintf(&tst_string, "%s@{%s}%s", prefix, var, suffix);
	result = extract_variable(tst_string);
	result_prefix = std::get<0>(result);
	result_var = std::get<1>(result);
	result_suffix = std::get<2>(result);
	pvar = variable::process_var(result_var.c_str());
	MY_TEST(strcmp(result_prefix.c_str(), prefix) == 0, "split string 1 prefix");
	MY_TEST(strcmp(pvar, var) == 0, "split string 1 var");
	MY_TEST(strcmp(result_suffix.c_str(), suffix) == 0, "split string 1 suffix");
	free(pvar);
	free(tst_string);

	asprintf(&tst_string, "@{%s}%s", var, suffix);
	result = extract_variable(tst_string);
	result_prefix = std::get<0>(result);
	result_var = std::get<1>(result);
	result_suffix = std::get<2>(result);
	pvar = variable::process_var(result_var.c_str());
	MY_TEST(result_prefix.empty(), "split string 2 prefix");
	MY_TEST(strcmp(pvar, var) == 0, "split string 2 var");
	MY_TEST(strcmp(result_suffix.c_str(), suffix) == 0, "split string 2 suffix");
	free(pvar);
	free(tst_string);

	asprintf(&tst_string, "%s@{%s}", prefix, var);
	result = extract_variable(tst_string);
	result_prefix = std::get<0>(result);
	result_var = std::get<1>(result);
	result_suffix = std::get<2>(result);
	pvar = variable::process_var(result_var.c_str());
	MY_TEST(strcmp(result_prefix.c_str(), prefix) == 0, "split string 3 prefix");
	MY_TEST(strcmp(pvar, var) == 0, "split string 3 var");
	MY_TEST(result_suffix.empty(), "split string 3 suffix");
	free(pvar);
	free(tst_string);

	asprintf(&tst_string, "@{%s}", var);
	result = extract_variable(tst_string);
	result_prefix = std::get<0>(result);
	result_var = std::get<1>(result);
	result_suffix = std::get<2>(result);
	pvar = variable::process_var(result_var.c_str());
	MY_TEST(result_prefix.empty(), "split string 4 prefix");
	MY_TEST(strcmp(pvar, var) == 0, "split string 4 var");
	MY_TEST(result_suffix.empty(), "split string 4 suffix");
	free(pvar);
	free(tst_string);

	asprintf(&tst_string, "%s%s%s", prefix, var, suffix);;
	result = extract_variable(tst_string);
	result_prefix = std::get<0>(result);
	result_var = std::get<1>(result);
	result_suffix = std::get<2>(result);
	MY_TEST(result_prefix.empty(), "split string 5 prefix");
	MY_TEST(result_var.empty(), "split string 5 var");
	MY_TEST(result_suffix.empty(), "split string 5 suffix");
	free(tst_string);

	return rc;
}

int test_split_out_var(void)
{
	int rc = 0;
	char *tst_string, *tmp;
	const char *prefix = "abcdefg";
	const char *var = "boogie";
	const char *var2 = "V4rW1thNum5";
	const char *var3 = "boogie_board";
	const char *suffix = "suffixication";
	std::tuple<std::string, std::string, std::string> result;
	std::string result_prefix;
	std::string result_var;
	std::string result_suffix;
	char *pvar = NULL;

	/* simple case */
	asprintf(&tst_string, "%s@{%s}%s", prefix, var, suffix);
	result = extract_variable(tst_string);
	result_prefix = std::get<0>(result);
	result_var = std::get<1>(result);
	result_suffix = std::get<2>(result);
	pvar = variable::process_var(result_var.c_str());
	MY_TEST(pvar != NULL, "extract_variable 1 pvar");
	MY_TEST(strcmp(result_prefix.c_str(), prefix) == 0, "extract_variable 1 prefix");
	MY_TEST(strcmp(pvar, var) == 0, "extract_variable 1 var");
	MY_TEST(strcmp(result_suffix.c_str(), suffix) == 0, "extract_variable 1 suffix");
	if (pvar)
		free(pvar);
	free(tst_string);

	/* no prefix */
	asprintf(&tst_string, "@{%s}%s", var, suffix);
	result = extract_variable(tst_string);
	result_prefix = std::get<0>(result);
	result_var = std::get<1>(result);
	result_suffix = std::get<2>(result);
	pvar = variable::process_var(result_var.c_str());
	MY_TEST(pvar != NULL, "extract_variable 2 pvar");
	MY_TEST(result_prefix.empty(), "extract_variable 2 prefix");
	MY_TEST(strcmp(pvar, var) == 0, "extract_variable 2 var");
	MY_TEST(strcmp(result_suffix.c_str(), suffix) == 0, "extract_variable 2 suffix");
	if (pvar)
		free(pvar);
	free(tst_string);

	/* no suffix */
	asprintf(&tst_string, "%s@{%s}", prefix, var);
	result = extract_variable(tst_string);
	result_prefix = std::get<0>(result);
	result_var = std::get<1>(result);
	result_suffix = std::get<2>(result);
	pvar = variable::process_var(result_var.c_str());
	MY_TEST(pvar != NULL, "extract_variable 3 pvar");
	MY_TEST(strcmp(result_prefix.c_str(), prefix) == 0, "extract_variable 3 prefix");
	MY_TEST(strcmp(pvar, var) == 0, "extract_variable 3 var");
	MY_TEST(result_suffix.empty(), "extract_variable 3 suffix");
	if (pvar)
		free(pvar);
	free(tst_string);

	/* var only */
	asprintf(&tst_string, "@{%s}", var);
	result = extract_variable(tst_string);
	result_prefix = std::get<0>(result);
	result_var = std::get<1>(result);
	result_suffix = std::get<2>(result);
	pvar = variable::process_var(result_var.c_str());
	MY_TEST(pvar != NULL, "extract_variable 4 pvar");
	MY_TEST(result_prefix.empty(), "extract_variable 4 prefix");
	MY_TEST(strcmp(pvar, var) == 0, "extract_variable 4 var");
	MY_TEST(result_suffix.empty(), "extract_variable 4 suffix");
	if (pvar)
		free(pvar);
	free(tst_string);

	/* quoted var, shouldn't split  */
	asprintf(&tst_string, "%s\\@{%s}%s", prefix, var, suffix);
	result = extract_variable(tst_string);
	result_prefix = std::get<0>(result);
	result_var = std::get<1>(result);
	result_suffix = std::get<2>(result);
	MY_TEST(result_prefix.empty(), "extract_variable - quoted @ prefix");
	MY_TEST(result_var.empty(), "extract_variable - quoted @ var");
	MY_TEST(result_suffix.empty(), "extract_variable - quoted @ suffix");
	free(tst_string);

	/* quoted \, split should succeed */
	asprintf(&tst_string, "%s\\\\@{%s}%s", prefix, var, suffix);
	result = extract_variable(tst_string);
	result_prefix = std::get<0>(result);
	result_var = std::get<1>(result);
	result_suffix = std::get<2>(result);
	pvar = variable::process_var(result_var.c_str());
	tmp = strndup(tst_string, strlen(prefix) + 2);
	MY_TEST(pvar != NULL, "extract_variable 5 pvar");
	MY_TEST(strcmp(result_prefix.c_str(), tmp) == 0, "extract_variable 5 prefix");
	MY_TEST(strcmp(pvar, var) == 0, "extract_variable 5 var");
	MY_TEST(strcmp(result_suffix.c_str(), suffix) == 0, "extract_variable 5 suffix");
	if (pvar)
		free(pvar);
	free(tst_string);
	free(tmp);

	/* un terminated var, should fail */
	asprintf(&tst_string, "%s@{%s%s", prefix, var, suffix);
	result = extract_variable(tst_string);
	result_prefix = std::get<0>(result);
	result_var = std::get<1>(result);
	result_suffix = std::get<2>(result);
	MY_TEST(result_prefix.empty(), "extract_variable - un-terminated var prefix");
	MY_TEST(result_var.empty(), "extract_variable - un-terminated var var");
	MY_TEST(result_suffix.empty(), "extract_variable - un-terminated var suffix");
	free(tst_string);

	/* invalid char in var, should fail */
	asprintf(&tst_string, "%s@{%s^%s}%s", prefix, var, var, suffix);
	result = extract_variable(tst_string);
	result_prefix = std::get<0>(result);
	result_var = std::get<1>(result);
	result_suffix = std::get<2>(result);
	pvar = variable::process_var(result_var.c_str());
	MY_TEST(pvar == NULL, "process_var - invalid char in var");
	if (pvar)
		free(pvar);
	free(tst_string);

	/* two vars, should only strip out first */
	asprintf(&tmp, "@{%s}%s}", suffix, suffix);
	asprintf(&tst_string, "%s@{%s}%s", prefix, var, tmp);
	result = extract_variable(tst_string);
	result_prefix = std::get<0>(result);
	result_var = std::get<1>(result);
	result_suffix = std::get<2>(result);
	pvar = variable::process_var(result_var.c_str());
	MY_TEST(pvar != NULL, "extract_variable 6 pvar");
	MY_TEST(strcmp(result_prefix.c_str(), prefix) == 0, "extract_variable 6 prefix");
	MY_TEST(strcmp(pvar, var) == 0, "extract_variable 6 var");
	MY_TEST(strcmp(result_suffix.c_str(), tmp) == 0, "extract_variable 6 suffix");
	if (pvar)
		free(pvar);
	free(tst_string);
	free(tmp);

	/* quoted @ followed by var, split should succeed */
	asprintf(&tst_string, "%s\\@@{%s}%s", prefix, var, suffix);
	result = extract_variable(tst_string);
	result_prefix = std::get<0>(result);
	result_var = std::get<1>(result);
	result_suffix = std::get<2>(result);
	pvar = variable::process_var(result_var.c_str());
	tmp = strndup(tst_string, strlen(prefix) + 2);
	MY_TEST(pvar != NULL, "extract_variable 7 pvar");
	MY_TEST(strcmp(result_prefix.c_str(), tmp) == 0, "extract_variable 7 prefix");
	MY_TEST(strcmp(pvar, var) == 0, "extract_variable 7 var");
	MY_TEST(strcmp(result_suffix.c_str(), suffix) == 0, "extract_variable 7 suffix");
	if (pvar)
		free(pvar);
	free(tst_string);
	free(tmp);

	/* numeric char in var, should succeed */
	asprintf(&tst_string, "%s@{%s}%s", prefix, var2, suffix);
	result = extract_variable(tst_string);
	result_prefix = std::get<0>(result);
	result_var = std::get<1>(result);
	result_suffix = std::get<2>(result);
	pvar = variable::process_var(result_var.c_str());
	MY_TEST(pvar != NULL, "extract_variable numeric var pvar");
	MY_TEST(strcmp(result_prefix.c_str(), prefix) == 0, "split out numeric var prefix");
	MY_TEST(strcmp(pvar, var2) == 0, "split numeric var var");
	MY_TEST(strcmp(result_suffix.c_str(), suffix) == 0, "split out numeric var suffix");
	if (pvar)
		free(pvar);
	free(tst_string);

	/* numeric first char in var, should fail */
	asprintf(&tst_string, "%s@{6%s}%s", prefix, var2, suffix);
	result = extract_variable(tst_string);
	result_prefix = std::get<0>(result);
	result_var = std::get<1>(result);
	result_suffix = std::get<2>(result);
	pvar = variable::process_var(result_var.c_str());
	MY_TEST(pvar == NULL, "process_var - invalid char in var");
	if (pvar)
		free(pvar);
	free(tst_string);

	/* underscore char in var, should succeed */
	asprintf(&tst_string, "%s@{%s}%s", prefix, var3, suffix);
	result = extract_variable(tst_string);
	result_prefix = std::get<0>(result);
	result_var = std::get<1>(result);
	result_suffix = std::get<2>(result);
	pvar = variable::process_var(result_var.c_str());
	MY_TEST(pvar != NULL, "extract_variable underscore var pvar");
	MY_TEST(strcmp(result_prefix.c_str(), prefix) == 0, "split out underscore var prefix");
	MY_TEST(strcmp(pvar, var3) == 0, "split out underscore var");
	MY_TEST(strcmp(result_suffix.c_str(), suffix) == 0, "split out underscore var suffix");
	if (pvar)
		free(pvar);
	free(tst_string);

	/* underscore first char in var, should fail */
	asprintf(&tst_string, "%s@{_%s%s}%s", prefix, var2, var3, suffix);
	result = extract_variable(tst_string);
	result_prefix = std::get<0>(result);
	result_var = std::get<1>(result);
	result_suffix = std::get<2>(result);
	pvar = variable::process_var(result_var.c_str());
	MY_TEST(pvar == NULL, "process_var - invalid char in var");
	if (pvar)
		free(pvar);
	free(tst_string);

	/* empty var name, should fail */
	asprintf(&tst_string, "%s@{}%s", prefix, suffix);
	result = extract_variable(tst_string);
	result_prefix = std::get<0>(result);
	result_var = std::get<1>(result);
	result_suffix = std::get<2>(result);
	pvar = variable::process_var(result_var.c_str());
	MY_TEST(pvar == NULL, "process_var - empty var name");
	if (pvar)
		free(pvar);
	free(tst_string);
	
	return rc;
}
int main(void)
{
	int rc = 0;
	int retval;

	retval = test_split_string();
	if (retval != 0)
		rc = retval;

	retval = test_split_out_var();
	if (retval != 0)
		rc = retval;

	return rc;
}
#endif /* UNIT_TEST */
