/*
 * SPDX-License-Identifier: MIT
 *
 * Copyright IBM Corp.
 */

#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#include "lib/util_autocomp.h"
#include "lib/util_opt.h"

static const char *bash_script_part1 = "() {\n\
\tlocal current_word options_array\n\
\tCOMPREPLY=()\n\n\
\tcurrent_word=\"${COMP_WORDS[COMP_CWORD]}\"\n\
\toptions_array=\"";

static const char *bash_script_part2 = "\tif [[ ${current_word} == -* ]] ; then\n\
\t\tmapfile -t \"COMPREPLY\" < <(compgen -W \"${options_array}\" -- \"$current_word\")\n\
\telse\n\
\t\tcompopt -o bashdefault -o default\n\
\tfi\n\
\treturn 0\n\
}\n\
complete -F ";

static char *format_name(const char *fmt, char *tool_name)
{
	char *func_name;

	if (asprintf(&func_name, fmt, tool_name) == -1)
		return NULL;
	return func_name;
}

/*
 * The convention for a completion function name is to be the same
 * as the command's name, but prefixed by '_'.
 */
static char *generate_func_name(char *tool_name)
{
	return format_name("_%s", tool_name);
}

static char *generate_bash_filename(char *tool_name)
{
	return format_name("%s.bash", tool_name);
}

static int init_scriptfile(char *file_path)
{
	int fd;

	fd = open(file_path, O_CREAT | O_WRONLY | O_TRUNC, 0644);
	if (fd < 0)
		return -EIO;
	return fd;
}

static int start_bash_scriptfile(int fd, char *func_name)
{
	int len, ret = 0;
	char *str;

	len = asprintf(&str, "%s%s", func_name, bash_script_part1);
	if (len == -1)
		return -EIO;

	if (write(fd, str, len) != len)
		ret = -EIO;
	free(str);
	return ret;
}

static int start_zsh_scriptfile(int fd, char *func_name, char *tool_name)
{
	const char *part3 = " {\n\n\t_arguments -C -A \"*\" \\\n";
	const char *part2 = "\n\nfunction ";
	const char *part1 = "#compdef ";
	int len, ret = 0;
	char *str;

	len = asprintf(&str, "%s%s%s%s%s", part1, tool_name, part2, func_name, part3);
	if (len == -1)
		return -EIO;

	if (write(fd, str, len) != len)
		ret = -EIO;
	free(str);
	return ret;
}

static int write_bash_command_options(struct util_opt *opt_vec, int fd)
{
	const char *prefix = " --";
	char *str;
	int len;

	for (int i = 0; opt_vec[i].desc; i++) {
		if (opt_vec[i].option.name) {
			len = asprintf(&str, "%s%s", prefix, opt_vec[i].option.name);
			if (len == -1)
				return -EIO;
			if (write(fd, str, len) != len) {
				free(str);
				return -EIO;
			}
			free(str);
		}
	}
	if (write(fd, "\"\n\n", 3) != 3)
		return -EIO;
	return 0;
}

static int write_zsh_command_options(struct util_opt *opt_vec, int fd)
{
	const char *end = "\t\t\"*:files:_files\"\n}\n";
	const char *name, *desc;
	char *str;
	int len;

	for (int i = 0; opt_vec[i].desc; i++) {
		if (opt_vec[i].option.name) {
			name = opt_vec[i].option.name;
			desc = opt_vec[i].desc;
			len = asprintf(&str, "\t\t\"--%s[%s]\" \\\n", name, desc);
			if (len == -1)
				return -EIO;
			if (write(fd, str, len) != len) {
				free(str);
				return -EIO;
			}
			free(str);
		}
	}
	len = asprintf(&str, "%s", end);
	if (len == -1)
		return -EIO;
	if (write(fd, str, len) != len) {
		free(str);
		return -EIO;
	}
	free(str);
	return 0;
}

static int finish_bash_scriptfile(char *tool_name, int fd, char *func_name)
{
	int len, ret = 0;
	char *str;

	len = asprintf(&str, "%s%s %s\n", bash_script_part2, func_name, tool_name);
	if (len == -1)
		return -EIO;

	if (write(fd, str, len) != len)
		ret = -EIO;
	free(str);
	return ret;
}

/*
 * Adds tab completion in bash for a command.
 * Works by generating an autocompletion
 * script file  at '/usr/share/bash-completion/completions'.
 */
static void generate_bash_autocomp(struct util_opt *opt_vec, char *tool_name)
{
	char *func_name, *filename;
	int fd, ret = 0;

	func_name = generate_func_name(tool_name);
	if (!func_name) {
		ret = -ENOMEM;
		goto end;
	}
	filename = generate_bash_filename(tool_name);
	if (!filename) {
		ret = -ENOMEM;
		goto free_func;
	}
	fd = init_scriptfile(filename);
	if (fd < 0) {
		ret = fd;
		goto free_file;
	}
	ret = start_bash_scriptfile(fd, func_name);
	if (ret < 0)
		goto close;
	ret = write_bash_command_options(opt_vec, fd);
	if (ret < 0)
		goto close;
	ret = finish_bash_scriptfile(tool_name, fd, func_name);
close:
	close(fd);
	if (ret)
		remove(filename);
free_file:
	free(filename);
free_func:
	free(func_name);
end:
	printf("  AUTOCOMP\t%s/%s.bash\n", tool_name, tool_name);
	if (ret)
		printf("%s.bash: error - %s\n", tool_name, strerror(abs(ret)));
}

/*
 * Adds tab completion in zsh for a command.
 * Works by generating an autocompletion
 * script file  at '/usr/share/zsh/site-functions'.
 */
static void generate_zsh_autocomp(struct util_opt *opt_vec, char *tool_name)
{
	char *func_name;
	int fd, ret = 0;

	func_name = generate_func_name(tool_name);
	if (!func_name) {
		ret = -ENOMEM;
		goto end;
	}
	fd = init_scriptfile(func_name);
	if (fd < 0) {
		ret = fd;
		goto free_func;
	}
	ret = start_zsh_scriptfile(fd, func_name, tool_name);
	if (ret < 0)
		goto close;
	ret = write_zsh_command_options(opt_vec, fd);
close:
	close(fd);
	if (ret)
		remove(func_name);
free_func:
	free(func_name);
end:
	printf("  AUTOCOMP\t%s/_%s\n", tool_name, tool_name);
	if (ret)
		printf("_%s: error - %s\n", tool_name, strerror(abs(ret)));
}

void generate_autocomp(struct util_opt *opt_vec, char *tool_name)
{
	generate_bash_autocomp(opt_vec, tool_name);
	generate_zsh_autocomp(opt_vec, tool_name);
}
