// vim:fileencoding=utf-8:foldmethod=marker

#include "commander.h"
#include "commander_output.h"
#include "../utils/shell_utils.h"
#include "../../allocators/arena/mem_arena_allocator.h"
#include "../../../common/aliases/aliases.h"
#include "../../../common/misc/misc_utils.h"
#include "../../../base/dbl_list/dbl_list.h"
#include "../../../base/mem/allocator/mem_allocator.h"
#include "../../../base/strings/str8/str8.h"
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define CMD_BUF_LEN 8192
#define OUT_BUF_LEN 4096

wp_intern WpCmdResult executeCommand(WpStr8RO *cmd, WpCmdOutHandling out_handling, WpStr8 *out_buf);
wp_intern WpCmdError getCommandOutput(FILE *fp, WpCmdOutHandling out_handling, WpStr8 *out_buf);

WpCmdResult wpShellCommanderExecute(WpCmdOutHandling out_handling, WpStr8 *out_buf, const WpStr8List *cmd) {
	if (!cmd) {
		return wpCmdNoExit(WP_SHELL_ERR_INVALID_ARGS);
	}

	WpAllocator arena = wpMemArenaAllocatorInit(KiB(500));

	WpStr8 *cmd_str = wpStr8Join(&arena, cmd, &wpStr8LitRo(" "));
	if (!cmd_str) {
		wpMemArenaAllocatorDestroy(&arena);
		return wpCmdNoExit(WP_SHELL_ERR_ALLOCATION_FAIL);
	}

	// Redirect output
	cmd_str = wpStr8AllocConcat(&arena, cmd_str, &wpStr8LitRo(" 2>&1"));

	WpCmdResult output = executeCommand(cmd_str, out_handling, out_buf);

	wpMemArenaAllocatorDestroy(&arena);

	return output;
}

wp_intern WpCmdResult executeCommand(WpStr8RO *cmd, WpCmdOutHandling out_handling, WpStr8 *out_buf) {
	char cmd_buf[CMD_BUF_LEN] = {0};
	wpStr8CopyToCstr(cmd_buf, cmd, CMD_BUF_LEN);

	FILE *fp = wpShellUtilsPopen(cmd_buf, "r");
	if (!fp) {
		return wpCmdNoExit(WP_SHELL_ERR_PROC_START_FAIL);
	}

	WpCmdResult output;

	WpCmdError err = getCommandOutput(fp, out_handling, out_buf);
	if (err > WP_SHELL_ERR_NO_ERROR) {
		output = wpCmdNoExit(err);
		goto executeCommand_CLOSE;
	}

	i32 st = EXIT_SUCCESS;
	err = _getOutputStatus(fp, &st);
	if (err > WP_SHELL_ERR_NO_ERROR) {
		output = wpCmdNoExit(err);
		goto executeCommand_CLOSE;
	}

	// Process is already closed in _getOutputStatus
	fp = NULL;

	output = (WpCmdResult){
		.exited    = true,
		.exit_code = st,
		.error     = WP_SHELL_ERR_NO_ERROR,
	};

executeCommand_CLOSE:
	if (fp) {
		wpShellUtilsPclose(fp);
	}
	return output;
}

wp_intern WpCmdError getCommandOutput(FILE *fp, WpCmdOutHandling out_handling, WpStr8 *out_buf) {
	WpStr8 out = wpStr8Buf(OUT_BUF_LEN);

	out.size = fread((void *)out.buf, sizeof(c8), out.capacity, fp);
	if (out_handling == WP_SHELL_OUTPUT_CAPTURE && out_buf != NULL) {
		if (out.size >= out_buf->capacity) {
			return WP_SHELL_ERR_OUT_BUF_FULL;
		}

		wpStr8ConcatCapped(out_buf, &out);
	} else if (out_handling == WP_SHELL_OUTPUT_PRINT) {
		printf(WP_STR8_SPEC, wpStr8Varg(out));
	}

	return WP_SHELL_ERR_NO_ERROR;
}
