// 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

wapp_intern inline CMDResult execute_command(Str8RO *cmd, CMDOutHandling out_handling, Str8 *out_buf);
wapp_intern inline CMDError get_command_output(FILE *fp, CMDOutHandling out_handling, Str8 *out_buf);

CMDResult wapp_shell_commander_execute(CMDOutHandling out_handling, Str8 *out_buf, const Str8List *cmd) {
  if (!cmd) {
    return CMD_NO_EXIT(SHELL_ERR_INVALID_ARGS);
  }

  Allocator arena = wapp_mem_arena_allocator_init(KB(500));

  Str8 *cmd_str = wapp_str8_join(&arena, cmd, &wapp_str8_lit_ro(" "));
  if (!cmd_str) {
    wapp_mem_arena_allocator_destroy(&arena);
    return CMD_NO_EXIT(SHELL_ERR_ALLOCATION_FAIL);
  }

  // Redirect output
  cmd_str = wapp_str8_alloc_concat(&arena, cmd_str, &wapp_str8_lit_ro(" 2>&1"));

  CMDResult output = execute_command(cmd_str, out_handling, out_buf);

  wapp_mem_arena_allocator_destroy(&arena);

  return output;
}

wapp_intern inline CMDResult execute_command(Str8RO *cmd, CMDOutHandling out_handling, Str8 *out_buf) {
  char cmd_buf[CMD_BUF_LEN] = {0};
  wapp_str8_copy_to_cstr(cmd_buf, cmd, CMD_BUF_LEN);

  FILE *fp = wapp_shell_utils_popen(cmd_buf, "r");
  if (!fp) {
    return CMD_NO_EXIT(SHELL_ERR_PROC_START_FAIL);
  }

  CMDResult output;

  CMDError err = get_command_output(fp, out_handling, out_buf);
  if (err > SHELL_ERR_NO_ERROR) {
    output = CMD_NO_EXIT(err);
    goto EXECUTE_COMMAND_CLOSE;
  }

  i32 st = EXIT_SUCCESS;
  err = get_output_status(fp, &st);
  if (err > SHELL_ERR_NO_ERROR) {
    output = CMD_NO_EXIT(err);
    goto EXECUTE_COMMAND_CLOSE;
  }

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

  output = (CMDResult){
      .exited = true,
      .exit_code = st,
      .error = SHELL_ERR_NO_ERROR,
  };

EXECUTE_COMMAND_CLOSE:
  if (fp) {
    wapp_shell_utils_pclose(fp);
  }
  return output;
}

wapp_intern inline CMDError get_command_output(FILE *fp, CMDOutHandling out_handling, Str8 *out_buf) {
  Str8 out = wapp_str8_buf(OUT_BUF_LEN);

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

    wapp_str8_concat_capped(out_buf, &out);
  } else if (out_handling == SHELL_OUTPUT_PRINT) {
    printf(WAPP_STR8_SPEC, wapp_str8_varg(out));
  }

  return SHELL_ERR_NO_ERROR;
}
