#include "process.h"

#if !defined(_WIN32) || defined(__CYGWIN__)
#  include "status.h"

#  include <assert.h>
#  include <fcntl.h>
#  include <signal.h>
#  include <sys/wait.h>

static char *kit_process_argv_null_[] = { "", NULL };
static char *kit_process_env_null_[]  = { NULL };

static char *kit_process_str_(kit_str_t s) {
  //  FIXME
  //

  return BS(s);
}

static char **kit_init_argv_(kit_process_args_t args, u32 flags) {
  //  TODO
  //

  if ((flags & KIT_PROCESS_NO_ARGUMENTS) != 0)
    return kit_process_argv_null_;

  return NULL;
}

static char **kit_init_envp_(kit_process_env_t env, u32 flags) {
  //  TODO
  //

  if ((flags & KIT_PROCESS_NO_ENVIRONMENT) != 0)
    return kit_process_env_null_;

  return NULL;
}

s32 kit_process_init(kit_process_t *p, kit_process_info_t info) {
  assert(p != NULL);
  assert(info.working_directory.size == 0 ||
         info.working_directory.values != NULL);

  if (p == NULL || (info.working_directory.size != 0 &&
                    info.working_directory.values == NULL))
    return KIT_ERROR_INVALID_ARGUMENT;

  memset(p, 0, sizeof *p);

  p->_stdin  = -1;
  p->_stdout = -1;
  p->_stderr = -1;

  signal(SIGCHLD, SIG_IGN);
  signal(SIGALRM, SIG_IGN); // pipes

  i32 pipe_in[2];
  i32 pipe_out[2];
  i32 pipe_err[2];

  if ((info.flags & KIT_PROCESS_NO_PIPES) == 0) {
    if (pipe(pipe_in) == -1) {
      assert(0);
      return KIT_ERROR_PIPE_FAILED;
    }

    if (pipe(pipe_out) == -1) {
      assert(0);
      close(pipe_in[0]);
      close(pipe_in[1]);
      return KIT_ERROR_PIPE_FAILED;
    }

    if (pipe(pipe_err) == -1) {
      assert(0);
      close(pipe_in[0]);
      close(pipe_in[1]);
      close(pipe_out[0]);
      close(pipe_out[1]);
      return KIT_ERROR_PIPE_FAILED;
    }

    //  non-blocking writing for stdout, stderr
    if (fcntl(pipe_out[1], F_SETFL, O_NONBLOCK) == -1 ||
        fcntl(pipe_err[1], F_SETFL, O_NONBLOCK) == -1) {
      assert(0);
      close(pipe_in[0]);
      close(pipe_in[1]);
      close(pipe_out[0]);
      close(pipe_out[1]);
      close(pipe_err[0]);
      close(pipe_err[1]);
      return KIT_ERROR_PIPE_FAILED;
    }
  }

  pid_t id = fork();

  switch (id) {
    case -1: return KIT_ERROR_FORK_FAILED;

    case 0:
      //  Child process
      //

      p->status            = KIT_OK;
      p->current_is_forked = 1;

      if ((info.flags & KIT_PROCESS_NO_PIPES) == 0) {
        //  Redirect IO
        if (dup2(pipe_in[0], STDIN_FILENO) == -1 ||
            dup2(pipe_out[1], STDOUT_FILENO) == -1 ||
            dup2(pipe_err[1], STDERR_FILENO) == -1) {
          assert(0);
          close(pipe_in[0]);
          close(pipe_in[1]);
          close(pipe_out[0]);
          close(pipe_out[1]);
          close(pipe_err[0]);
          close(pipe_err[1]);
          return KIT_ERROR_DUP2_FAILED;
        }

        //  Close pipes
        close(pipe_in[0]);
        close(pipe_in[1]);
        close(pipe_out[0]);
        close(pipe_out[1]);
        close(pipe_err[0]);
        close(pipe_err[1]);
      }

      //  Change working directory
      if (info.working_directory.size != 0 &&
          chdir(kit_process_str_(info.working_directory)) == -1) {
        assert(0);
        return KIT_ERROR_CHDIR_FAILED;
      }

      if ((info.flags & KIT_PROCESS_FORK) == 0) {
        execve(kit_process_str_(info.file_name),
               kit_init_argv_(info.command_line, info.flags),
               kit_init_envp_(info.environment, info.flags));
        // Doesn't return on success

        return KIT_ERROR_EXECVE_FAILED;
      }

      return KIT_OK;

    default:
      //  Parent process
      //

      p->status            = KIT_OK;
      p->current_is_forked = 0;
      p->_ready            = 1;
      p->_running          = 1;
      p->_id               = id;

      if ((info.flags & KIT_PROCESS_NO_PIPES) == 0) {
        p->_stdin  = pipe_in[1];
        p->_stdout = pipe_out[0];
        p->_stderr = pipe_err[0];

        //  Close unused pipes
        close(pipe_in[0]);
        close(pipe_out[1]);
        close(pipe_err[1]);
      }
  }

  return KIT_OK;
}

void kit_process_cleanup(kit_process_t *p) {
  assert(p != NULL);
  assert(p->_ready);

  if (p == NULL || !p->_ready)
    return;

  if (p->_stdin != -1)
    close(p->_stdin);
  if (p->_stdout != -1)
    close(p->_stdout);
  if (p->_stderr != -1)
    close(p->_stderr);

  memset(p, 0, sizeof *p);
}

i64 kit_process_write_stdin(kit_process_t *p, kit_str_t in_data) {
  assert(p != NULL && (in_data.size == 0 || in_data.values != NULL) &&
         p->_running);

  if (p == NULL || (in_data.size != 0 && in_data.values == NULL))
    return KIT_ERROR_INVALID_ARGUMENT;
  if (in_data.size == 0 || !p->_running || p->_stdin == -1)
    return 0;

  i64 n = write(p->_stdin, in_data.values, in_data.size);

  assert(n >= 0);
  if (n < 0)
    return 0;

  return n;
}

i64 kit_process_read_stdout(kit_process_t *p, kit_str_t out_data) {
  assert(p != NULL &&
         (out_data.size == 0 || out_data.values != NULL) &&
         p->_ready);

  if (p == NULL || (out_data.size != 0 && out_data.values == NULL))
    return KIT_ERROR_INVALID_ARGUMENT;
  if (out_data.size == 0 || !p->_ready || p->_stdout == -1)
    return 0;

  i64 n = read(p->_stdout, out_data.values, out_data.size);

  assert(n >= 0);
  if (n < 0)
    return 0;

  return n;
}

i64 kit_process_read_stderr(kit_process_t *p, kit_str_t out_data) {
  assert(p != NULL &&
         (out_data.size == 0 || out_data.values != NULL) &&
         p->_ready);

  if (p == NULL || (out_data.size != 0 && out_data.values == NULL))
    return KIT_ERROR_INVALID_ARGUMENT;
  if (out_data.size == 0 || !p->_ready || p->_stderr == -1)
    return 0;

  i64 n = read(p->_stderr, out_data.values, out_data.size);

  assert(n >= 0);
  if (n < 0)
    return 0;

  return n;
}

s32 kit_process_terminate(kit_process_t *p) {
  assert(p != NULL && p->_running);
  if (p == NULL || !p->_running)
    return KIT_ERROR_INVALID_ARGUMENT;

  if (kill(p->_id, SIGTERM) == -1)
    return KIT_ERROR_KILL_FAILED;

  return KIT_OK;
}

b8 kit_process_alive(kit_process_t *p) {
  assert(p != NULL);
  if (p == NULL || p->status != KIT_OK)
    return 0;

  if (!p->_running)
    return 0;

  int status;

  pid_t id = waitpid(p->_id, &status, WNOHANG);

  if (id == -1) {
    p->status = KIT_ERROR_WAITPID_FAILED;
    return 0;
  }

  if (id == 0)
    return 1;

  if (WIFEXITED(status)) {
    p->exit_code = WEXITSTATUS(status);
    p->_running  = 0;
    return 0;
  }

  return 1;
}

s32 kit_process_wait(kit_process_t *p) {
  assert(p != NULL);
  if (p == NULL)
    return KIT_ERROR_INVALID_ARGUMENT;

  if (p->status != KIT_OK)
    return p->status;
  if (!p->_running)
    return KIT_OK;

  for (;;) {
    int status;

    pid_t id = waitpid(p->_id, &status, 0);

    if (id == -1)
      return KIT_ERROR_WAITPID_FAILED;

    if (WIFEXITED(status)) {
      p->exit_code = WEXITSTATUS(status);
      p->_running  = 0;
      break;
    }
  }

  return KIT_OK;
}

#endif