#ifdef HAVE_CONFIG_H
# include "config.h"
#endif

#include "shell-builtin.h"

#include "xwrap.h"

#include <errno.h>
#include <fcntl.h>
#include <inttypes.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>

const RasShellBuiltin ras_shell_builtins[] = {
	RAS_SHELL_BUILTIN_ENTRY (cd),
	RAS_SHELL_BUILTIN_ENTRY (echo),
	RAS_SHELL_BUILTIN_ENTRY (exit),
	RAS_SHELL_BUILTIN_ENTRY (false),
	RAS_SHELL_BUILTIN_ENTRY (logout),
	RAS_SHELL_BUILTIN_ENTRY (printenv),
	RAS_SHELL_BUILTIN_ENTRY (pwd),
	RAS_SHELL_BUILTIN_ENTRY (set),
	RAS_SHELL_BUILTIN_ENTRY (setenv),
	RAS_SHELL_BUILTIN_ENTRY (shopt),
	RAS_SHELL_BUILTIN_ENTRY (tacat),
	RAS_SHELL_BUILTIN_ENTRY (tapipe),
	RAS_SHELL_BUILTIN_ENTRY (true),
	RAS_SHELL_BUILTIN_ENTRY (tty),
	RAS_SHELL_BUILTIN_ENTRY (type),
	RAS_SHELL_BUILTIN_ENTRY (umask),
	RAS_SHELL_BUILTIN_ENTRY (unset),
	RAS_SHELL_BUILTIN_ENTRY (unsetenv),
	RAS_SHELL_BUILTIN_ENTRY_NULL
};

int ras_shell_builtin_cd (int argc, char* argv[], RasShell* shell) {
	if (shell->attr_rshell) {
		fprintf (stderr, "%s: you are not permitted to run this command\n", argv[0]);
		return 1;
	}
	char* dest = argv[1];
	if (dest == NULL) {
		dest = getenv ("HOME");
		if (dest == NULL) {
			dest = ".";
		}
	}
	if (chdir (dest) < 0) {
		fprintf (stderr, "cd: %s: %s\n", dest, strerror (errno));
		return 1;
	}
	return 0;
}

int ras_shell_builtin_echo (int argc, char* argv[], RasShell* shell) {
	for (int i = 1; argv[i] != NULL; i++) {
		if (i > 1) {
			putchar (' ');
		}
		fputs (argv[i], stdout);
	}
	putchar ('\n');
	return 0;
}

int ras_shell_builtin_exit (int argc, char* argv[], RasShell* shell) {
	if (argc > 2) {
		fprintf (stderr, "%s: too many arguments\n", argv[0]);
		return 1;
	}
	if (argc < 2) {
		shell->req_exit_status = 0;
	} else if (sscanf (argv[1], "%d", &shell->req_exit_status) < 1) {
		fprintf (stderr, "%s: `%s\' is not a number\n", argv[0], argv[1]);
		return 1;
	}
	shell->req_exit = true;
	return 0;
}

int ras_shell_builtin_false (int argc, char* argv[], RasShell* shell) {
	return 1;
}

int ras_shell_builtin_logout (int argc, char* argv[], RasShell* shell) {
	if (!shell->attr_login) {
		fprintf (stderr, "%s: not a login shell\n", argv[0]);
		return 1;
	}
	return ras_shell_builtin_exit (argc, argv, shell);
}

int ras_shell_builtin_printenv (int argc, char* argv[], RasShell* shell) {
	if (argc > 2) {
		fprintf (stderr, "%s: too many arguments\n", argv[0]);
		return 1;
	}
	if (argc < 2) {
		extern char** environ;
		for (int i = 0; environ[i] != NULL; i++) {
			puts (environ[i]);
		}
	} else {
		char* res = getenv (argv[1]);
		if (res == NULL) {
			return 1;
		}
		puts (res);
	}
	return 0;
}

int ras_shell_builtin_pwd (int argc, char* argv[], RasShell* shell) {
	char* cwd = xgetcwd ();
	if (cwd == NULL) {
		fprintf (stderr, "%s: cannot get working directory: %s\n", argv[0], strerror (errno));
		return 1;
	}
	puts (cwd);
	free (cwd);
	return 0;
}

int ras_shell_builtin_set (int argc, char* argv[], RasShell* shell) {
	fputs ("This command has not been implemented. Try `setenv\' or `shopt\'.\n", stderr);
	return 1;
}

int ras_shell_builtin_setenv (int argc, char* argv[], RasShell* shell) {
	if (argc != 3) {
		fprintf (
			stderr, "%s: too %s arguments\n", argv[0], argc > 3 ? "many" : "few");
		return 1;
	}
	return - setenv (argv[1], argv[2], 1);
}

static inline const char* bstr (bool b) {
	return b ? "on" : "off";
}

int ras_shell_builtin_shopt (int argc, char* argv[], RasShell* shell) {
	if (argc > 3) {
		fprintf (stderr, "%s: too many arguments\n", argv[0]);
		return 1;
	}

	bool printall = argc == 1;
	bool print = argc <= 2;
	bool value;
	int rval = 0;

	if (argc == 3) {
		if (strcmp (argv[2], "on") == 0) {
			value = true;
		} else if (strcmp (argv[2], "off") == 0) {
			value = false;
		} else {
			fprintf (stderr, "%s: invalid value `%s\'\n", argv[0], argv[2]);
			return 1;
		}
	}

	if (printall || strcmp (argv[1], "login_shell") == 0) {
		if (print) {
			printf ("login_shell     %s\n", bstr (shell->attr_login));
		} else {
			fputs ("login_shell attribute cannot be modified.\n", stderr);
			rval = 1;
		}
	}

	if (printall || strcmp (argv[1], "restricted") == 0) {
		if (print) {
			printf ("restricted      %s\n", bstr (shell->attr_rshell));
		} else {
			fputs ("restricted attribute cannot be modified.\n", stderr);
			rval = 1;
		}
	}

	if (printall || strcmp (argv[1], "debug") == 0) {
		if (print) {
			printf ("debug           %s\n", bstr (shell->attr_debug));
		} else {
			shell->attr_debug = value;
		}
	}

	if (printall || strcmp (argv[1], "is_term") == 0) {
		if (print) {
			printf ("is_term         %s\n", bstr (shell->attr_isterm));
		} else {
			fputs ("is_term attribute cannot be modified.\n", stderr);
			rval = 1;
		}
	}

	return rval;
}

int ras_shell_builtin_umask (int argc, char* argv[], RasShell* shell) {
	if (argc > 2) {
		fprintf (stderr, "%s: too many arguments\n", argv[0]);
		return 1;
	}

	if (argc < 2) {
		mode_t old_mode = umask (0777);
		umask (old_mode);
		printf ("%04o\n", old_mode);
	} else {
		int mode_int;
		if (sscanf (argv[1], "%o", &mode_int) < 0) {
			fprintf (stderr, "%s: `%s\' is not a valid octal number\n", argv[0], argv[1]);
			return 1;
		}
		mode_t new_mode = mode_int;
		umask (new_mode);
	}
	return 0;
}

int ras_shell_builtin_tacat (int argc, char* argv[], RasShell* shell) {
	int rval = 0;
	for (int i = 1; i < argc; i++) {
		char* f = xstrcat (shell->saved_tmpdir, "/", argv[i], NULL);
		int fd = open (f, O_RDONLY);

		if (fd < 0) {
			fprintf (stderr, "%s: fail to open `%s\': %s\n",
				argv[0], argv[i], strerror (errno));
			rval = 1;
			free (f);
			break;
		}

		char buf[8192];
		int readval;
		while ((readval = read (fd, &buf, 8192)) > 0) {
			write (STDOUT_FILENO, &buf, readval);
		}

		free (f);
	}
	return rval;
}

int ras_shell_builtin_tapipe (int argc, char* argv[], RasShell* shell) {
	ListNode* iter = list_node_front (shell->saved_list);
	for (; iter != NULL; iter = list_next (iter)) {
		RasShellSaved* saved = list_data (iter);
		printf ("Command %" PRIu64 " input == "
			"Command %" PRIu64 " output (%s)\n",
			saved->to, saved->from, saved->cmdorg);
	}
	return 0;
}

int ras_shell_builtin_true (int argc, char* argv[], RasShell* shell) {
	return 0;
}

int ras_shell_builtin_tty (int argc, char* argv[], RasShell* shell) {
	char* ttyn = ttyname (STDIN_FILENO);

	if (ttyn == NULL) {
		puts ("not a tty");
		return 1;
	}

	puts (ttyn);
	return 0;
}

int ras_shell_builtin_type (int argc, char* argv[], RasShell* shell) {
	if (argv[1] == NULL) {
		return 0;
	}

	for (int i = 0; ras_shell_builtins[i].cmd != NULL; i++) {
		if (strcmp (ras_shell_builtins[i].cmd, argv[1]) == 0) {
			printf ("%s is a shell builtin\n", argv[1]);
			return 0;
		}
	}

	printf (
		"%s is not understood by the shell ... Is it an external command?\n",
		argv[1]);
	return 1;
}

int ras_shell_builtin_unset (int argc, char* argv[], RasShell* shell) {
	fputs ("This command has not been implemented. Try `unsetenv\'.\n", stderr);
	return 1;
}

int ras_shell_builtin_unsetenv (int argc, char* argv[], RasShell* shell) {
	for (int i = 1; i < argc; i++) {
		if (unsetenv (argv[i]) < 0) {
			fprintf (stderr, "%s: %s: %s\n", argv[0], argv[i], strerror (errno));
			return 1;
		}
	}
	return 0;
}