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

#include <limits.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <fcntl.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 char *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;
  char *var;
  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 ()
{
  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, name_len;
  char *export_val;
  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 = getcwd (NULL, FILENAME_MAX + 1);
  atexit (restore_cwd);
}

char *dospath_getcwd (char *buf, size_t size)
{
  char ch;
  if (path_separator == ';')
    return getcwd(buf, size);

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

  return buf;
}

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

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

  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)
{
  int i, dot, sep;
  char *ptr, *ext, *file;

  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

  *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 *eop = (char *)in + 2;
          char ch;
          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;
  char *in_start = in_ptr;

  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;
  char *in_start = in_ptr;

  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;
  char *in_start = in_ptr;

  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 char *
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;
}
