#include <limits.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <fcntl.h>
#include <ctype.h>
#include <sys/stat.h>

#include "config.h"
#include "command.h"
#include "general.h"
#include "variables.h"
#include "dospath.h"
#include "dosmalloc.h"

#define ARRAY_INIT_LEN 32

struct dynamic_array
{
  char *start;
  char *ptr;
  char *end;
};

struct dos_var_info
{
  const char *name;
  void (*init_func)(const char *);
  char found;
};

char path_separator = ';';
int path_expand = 0;
int test_finds_exe = 0;

static void path_separator_init (const char *);
static void path_expand_init (const char *);
static void test_finds_exe_init (const char *);
static int make_bash_path (const char *in, struct dynamic_array *buffer,
                           char stop_char, char **in_end);
static int make_bash_path_var (const char *in, struct dynamic_array *buffer);
static int make_dos_path (const char *in, struct dynamic_array *buffer,
                          char stop_char, char **in_end);
static int make_dos_path_var (const char *in, struct dynamic_array *buffer);
static int make_export_path_var (const char *in, struct dynamic_array *buffer);
static void restore_cwd (void);

static void array_resize(struct dynamic_array *array);
static const char *array_puts(struct dynamic_array *array, const char *s);
static void array_putc(struct dynamic_array *array, const char ch);
static void array_init(struct dynamic_array *array, const char *s, size_t size);
static void array_done(struct dynamic_array *array);
static int array_len(struct dynamic_array *array);

static const char * path_sep_vars[] =
{
/*  "CDPATH", */
  "COMSPEC",
  "HOME",
  "MAILPATH",
  "OLDPWD",
  "PATH",
  "PWD",
  "TMPDIR",
};

static char path_var_found[] =
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};

static struct dos_var_info dos_var_init[] =
{
  { "PATH_EXPAND", &path_expand_init, 0},
  { "TEST_FINDS_EXE", &test_finds_exe_init, 0},
};

static char path_buffer[256];

static char *init_cwd;

/* Called at startup. Certain variables containing a list of path like PATH and
   are used by Bash must be in a form that Bash understands.
   When PATH_SEPARATOR=:, the list is separated by ':' and uses the special
   '/dev/x/' form understood by DJGPP. When PATH_SEPARATOR=;, the list is
   separated by ';' as normal but backslashes are converted to slashes.  */
int try_init_path_var(const char *name, char **val)
{
  int i;
  struct dynamic_array buffer;

  for (i = 0; i < sizeof (path_sep_vars) / sizeof (path_sep_vars[0]); i++)
  {
    if (!path_var_found[i] && strcmp (name, path_sep_vars[i]) == 0)
    {
      path_var_found[i] = 1;
      array_init(&buffer, 0, ARRAY_INIT_LEN);
      make_bash_path_var(*val, &buffer);
      *val = buffer.start;
      return 1;
    }
  }
  for (i = 0; i < sizeof (dos_var_init) / sizeof (dos_var_init[0]); i++)
  {
    if (!dos_var_init[i].found && strcmp (name, dos_var_init[i].name) == 0)
    {
      dos_var_init[i].found = 1;
      if ((*val)[0] != '\0')
        (dos_var_init[i].init_func)(*val);
      break;
    }
  }
  return 0;
}

void init_path_separator(void)
{
  char *path_sep = getenv("PATH_SEPARATOR");
  if (path_sep)
    path_separator_init(path_sep);
}

/* Put a path variable to be exported Bash into a form that native DOS programs
   can understand. Do that by changing paths containing '/dev/x/foo' path to
   canonical form and change slashes to backslashes. */
char *
export_path_var(const char *name, const char *val)
{
  int out_len;
  struct dynamic_array buffer;

  array_init(&buffer, 0, ARRAY_INIT_LEN);
  array_puts(&buffer, name);
  array_putc(&buffer, '=');

  out_len = make_export_path_var(val, &buffer);
  array_putc(&buffer, '\0');

  return buffer.start;
}

void expand_argv_words(WORD_LIST *list)
{
  struct dynamic_array buffer;

  if (!path_expand)
    return;

  list = list->next;

  while (list)
  {
    if (list->word->flags != W_QUOTED)
    {
      char *arg = list->word->word;

      if (strncmp(arg, "/dev/", 5) == 0 && arg[5]
          && (arg[6] == '/' || arg[6] == '\0'))
      {
        /* Setup a fake dynamic buffer. Set end to 0 to ensure
           no attempt is made to increase the size of the array.  */
        buffer.start = arg;
        buffer.ptr = arg;
        buffer.end = (char *)0 - 1;
        make_dos_path(arg, &buffer, '\0', NULL);
        array_putc(&buffer, '\0');
      }
    }
    list = list->next;
  }

  return;
}


void make_path_vars_bash(void)
{
  int i;
  struct dynamic_array buffer;
  SHELL_VAR *var;

  array_init(&buffer, 0, ARRAY_INIT_LEN);

  for (i = 0; i < sizeof(path_sep_vars) / sizeof(path_sep_vars[0]); i++)
  {
    var = find_variable((char *)path_sep_vars[i]);
    if (!var || !var->value)
      continue;
    buffer.ptr = buffer.start;
    make_bash_path_var(var->value, &buffer);
    free(var->value);
    var->value = malloc(array_len(&buffer) + 1);
    strcpy(var->value, buffer.start);
  }
  array_done(&buffer);
}

void make_path_vars_dos(void)
{
  int i;
  struct dynamic_array buffer;
  SHELL_VAR *var;

  array_init(&buffer, 0, ARRAY_INIT_LEN);

  for (i = 0; i < sizeof(path_sep_vars) / sizeof(path_sep_vars[0]); i++)
  {
    var = find_variable((char *)path_sep_vars[i]);
    if (!var || !var->value)
      continue;
    buffer.ptr = buffer.start;
    make_dos_path_var(var->value, &buffer);
    free(var->value);
    var->value = malloc(array_len(&buffer) + 1);
    strcpy(var->value, buffer.start);
  }
  array_done(&buffer);
}

char *make_posix_path_from_filename(const char *in)
{
  struct dynamic_array buffer;

  array_init(&buffer, 0, ARRAY_INIT_LEN);

  if (in[1] == ':')
  {
    array_puts(&buffer, "/dev/");
    array_putc(&buffer, in[0]);
    in += 2;
  }
  while (*in)
  {
     array_putc(&buffer, ((*in != '\\') ? *in : '/'));
     ++in;
  }

  array_putc(&buffer, '\0');
  return buffer.start;

}

void save_cwd(void)
{
  if (init_cwd == NULL)
    init_cwd = rpl_getcwd(NULL, FILENAME_MAX + 1);
  atexit (restore_cwd);
}

char *dospath_getcwd(char *buf, size_t size)
{
  char ch;

  if (buf == NULL)
  {
    buf = (char *)xmalloc(PATH_MAX + 1);
    buf[0] = '\0';
  }
  if (size == 0)
    size = PATH_MAX;

  if (path_separator == ';')
    return rpl_getcwd(buf, size);

  rpl_getcwd(buf + 4, size - 4);
  ch = *(buf + 4);
  strcpy(buf, "/dev/");
  buf[5] = ch;
  buf[6] = '/';

  return buf;
}

/* This shall replace lib/sh/getcwd and ensure
   that always DJGPP's own implementation is used.  */
char *rpl_getcwd(char *buf, size_t size)
{
  size_t buf_size = size ? size : PATH_MAX;

  return getcwd(buf, buf_size);
}

static const char *exec_extensions[] =
{"exe", "bat", "com", "btm"};

const char *
find_extension(const char *path)
{
  char *ptr, *ext, *file;
  int dot, i;

  dot = 0;
  ptr = (char *)path;
  ext = file = path_buffer;

  while (*ptr)
  {
    *ext = *ptr;
    if (*ext == '.')
      dot = 1;
    else if (*ext == '/')
    {
      dot = 0;
      file = ext;
      ++file;
    }
    ++ext;
    ++ptr;
  }

  if (dot)
    return NULL;

  *ext = '\0';
  if (access(path_buffer, F_OK) == 0 && !(access(path_buffer, D_OK) == 0))
    return path_buffer;

  *ext = '.';
  ++ext;

  for (i = 0; i < sizeof (exec_extensions) / sizeof (exec_extensions[0]); i++)
  {
    strcpy (ext, exec_extensions[i]);
    if (access(path_buffer, F_OK) == 0 && !(access(path_buffer, D_OK) == 0))
      return path_buffer;
  }
  return NULL;
}

const char *find_path_extension(const char *path, const char *file)
{
  char *ext, *file_ptr;
  int dot, i;

  ext = path_buffer;

  while (*path)
  {
    *ext = *((char *)(path));
    ++path;
    ++ext;
  }
  if (ext[-1] != '/')
  {
    *ext = '/';
    ++ext;
  }
  dot = 0;
  file_ptr = ext;

  while (*file)
  {
    *ext = *(char *)file;
    if (*ext == '.')
      dot = 1;
    ++file;
    ++ext;
  }
  if (dot)
    return NULL;

  *ext = '\0';
  if (access(path_buffer, F_OK) == 0 && !(access(path_buffer, D_OK) == 0))
    return file_ptr;

  *ext = '.';
  ++ext;

  for (i = 0; i < sizeof (exec_extensions) / sizeof (exec_extensions[0]); i++)
  {
    strcpy(ext, exec_extensions[i]);
    if (access(path_buffer, F_OK) == 0 && !(access(path_buffer, D_OK) == 0))
      return file_ptr;
  }
  return NULL;
}

const char *find_one_extension(const char *path, const char *extension)
{
  char *ptr, *ext, *file;
  int dot;

  dot = 0;
  ptr = (char *)path;
  ext = file = path_buffer;

  while (*ptr)
  {
    *ext = *ptr;
    if (*ext == '.')
      dot = 1;
    else if (*ext == '/')
    {
      dot = 0;
      file = ext;
      ++file;
    }
    ++ext;
    ++ptr;
  }

  if (dot)
    return NULL;

#if 0
  *ext = '\0';
  if (access(path_buffer, F_OK) == 0 && !(access(path_buffer, D_OK) == 0))
    return path_buffer;
#endif /* 0 */

  *ext = '.';
  ++ext;

  strcpy(ext, extension);

  if (access(path_buffer, F_OK) == 0 && !(access(path_buffer, D_OK) == 0))
    return path_buffer;

  return NULL;
}

char *
get_real_path(const char *path)
{
  _fixpath(path, path_buffer);
  return path_buffer;
}

char *
encode_drive_letter(char *buffer, const char letter)
{
  if (path_separator == ':')
  {
    strcpy(buffer, "/dev/");
    buffer[5] = letter;
    buffer[6] = '\0';
  }
  else
  {
    buffer[0] = letter;
    buffer[1] = ':';
    buffer[2] = '\0';
  }
  return buffer;
}

static void restore_cwd(void)
{
  if (init_cwd && init_cwd[0] != '\0')
    chdir(init_cwd);
}

static void
path_separator_init(const char *val)
{
  char ch = *val;
  if (ch == ':' || ch == ';')
    path_separator = ch;
}

static void
path_expand_init(const char *val)
{
  char ch = *val;
  if (tolower(ch) == 'y')
    path_expand = 1;
}

static void
test_finds_exe_init(const char *val)
{
  char ch = *val;
  if (tolower(ch) == 'y')
    test_finds_exe = 1;
  else
    test_finds_exe = 0;
}

static int
make_bash_path(const char *in, struct dynamic_array *buffer, char stop_char, char **in_end)
{
  if (in == NULL)
    return 0;

  if (path_separator == ':' && *in && *in != '.' && in[1] == ':')
  {
    if (in[2] == '\\' || in[2] == '/')
    {
      array_puts(buffer, "/dev/");
      array_putc(buffer, *in);
      in += 2;
    }
    else
    {
      char ch;
      char *eop = (char *)in + 2;
      char fixbuf[PATH_MAX];

      while (*eop && *eop != stop_char)
        ++eop;
      ch = *eop;
      *eop = '\0';
      _fixpath(in, fixbuf);
      *eop = ch;
      ch = fixbuf[0];
      array_puts(buffer, "/dev/");
      array_putc(buffer, ch);
      in = eop;
    }
  }
  while (*in && *in != stop_char)
  {
    array_putc(buffer, (*in != '\\') ? *in : '/');
    ++in;
  }

  if (in_end)
    *in_end = (char *)in;

  return 0;
}

static int
make_bash_path_var(const char *in, struct dynamic_array *buffer)
{
  char *in_ptr = (char *)in;

  while (*in_ptr)
  {
    make_bash_path(in_ptr, buffer, ';', &in_ptr);
    if (*in_ptr == ';')
    {
      array_putc(buffer, path_separator);
      ++in_ptr;
    }
  }
  array_putc(buffer, '\0');
  return buffer->ptr - buffer->start;
}

static int
make_dos_path(const char *in, struct dynamic_array *buffer, char stop_char, char **in_end)
{
  if (in == NULL)
    return 0;

  if (strncmp(in, "/dev/", 5) == 0)
  {
    array_putc(buffer, in[5]);
    array_putc(buffer, ':');
    in += 6;
  }

  while (*in && *in != stop_char)
  {
    array_putc(buffer, (*in != '/') ? *in : '\\');
    ++in;
  }

  if (in_end)
    *in_end = (char *)in;

  return buffer->ptr - buffer->start;
}

static int
make_dos_path_var(const char *in, struct dynamic_array *buffer)
{
  char *in_ptr = (char *)in;

  while (*in_ptr)
  {
    make_dos_path(in_ptr, buffer, ':', &in_ptr);
    if (*in_ptr == ':')
    {
      array_putc(buffer, path_separator);
      ++in_ptr;
    }
  }
  array_putc(buffer, '\0');
  return buffer->ptr - buffer->start;
}

static int
make_export_path_var(const char *in, struct dynamic_array *buffer)
{
  char *in_ptr = (char *)in;

  while (*in_ptr)
  {
    make_dos_path(in_ptr, buffer, path_separator, &in_ptr);
    if (*in_ptr == path_separator)
    {
      array_putc(buffer, ';');
      ++in_ptr;
    }
  }
  array_putc(buffer, '\0');
  return buffer->ptr - buffer->start;
}

static void
array_resize(struct dynamic_array *array)
{
  char *xstart;
  size_t len;

  len = (array->end - array->start);
  len *= 2;

  xstart = realloc(array->start, len + 1);
  array->ptr = xstart + (array->ptr - array->start);
  array->start = xstart;
  array->end = xstart + len;
}

static const char *
array_puts(struct dynamic_array *array, const char *s)
{
  while (*s)
  {
    while (*s && (array->ptr < array->end))
    {
      *(array->ptr) = *s;
      ++(array->ptr);
      ++s;
    }
    if (*s)
      array_resize(array);
  }

  return array->ptr;
}

static void
array_putc(struct dynamic_array *array, const char ch)
{
  if (array->ptr >= array->end)
    array_resize(array);
  *(array->ptr) = ch;
  ++(array->ptr);
}

static void
array_init(struct dynamic_array *array, const char *s, size_t size)
{
  array->start = malloc(size + 1);
  array->ptr = array->start;
  array->end = array->start + size;
  if (s)
    array_puts(array, s);
}

static void
array_done(struct dynamic_array *array)
{
  free(array->start);
}

static int
array_len(struct dynamic_array *array)
{
  return array->ptr - array->start + 1;
}
