/*
 * Copyright (c) 2006,2009 Charles S. Wilson
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a 
 * copy of this software and associated documentation files (the "Software"), 
 * to deal in the Software without restriction, including without limitation 
 * the rights to use, copy, modify, merge, publish, distribute, sublicense, 
 * and/or sell copies of the Software, and to permit persons to whom the 
 * Software is furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included 
 * in all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 
 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR 
 * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 
 * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 
 * OTHER DEALINGS IN THE SOFTWARE.
 */

#if HAVE_CONFIG_H
# include <config.h>
#endif

#if STDC_HEADERS
# include <stdlib.h>
# include <stdarg.h>
# include <string.h>
# include <float.h>
#endif

#include <stdio.h>
#include <errno.h>
#include <assert.h>
#include <ctype.h>

#if HAVE_SYS_TYPES_H
# include <sys/types.h>
#endif
#if HAVE_MALLOC_H
# include <malloc.h>
#endif
#if HAVE_SYS_STAT_H
# include <sys/stat.h>
#endif
#if HAVE_PWD_H
# include <pwd.h>
#endif
#if HAVE_UNISTD_H
# include <unistd.h>
#endif
#if HAVE_SYS_CYGWIN_H
# include <sys/cygwin.h>
#endif
#if HAVE_WINDOWS_H && HAVE_OPENCLIPBOARD
# define WIN32_LEAD_AND_MEAN
# define NOMINMAX
# include <windows.h>
#endif

#ifndef ORIGINAL_RUN
#include <ustr.h>
#endif
#include "util.h"

char *program_name = NULL;

static int gui_mode = 1; /* enabled */
static int tty_mode = 1; /* enabled */
static int verbose_level = 0;  /* only FATAL errors, by default */

int run2_gui_is_allowed(void)  { return gui_mode;    }
int run2_tty_is_allowed(void)  { return tty_mode;    }
int run2_get_verbose_level(void) { return verbose_level; }

void run2_set_gui_mode(int mode)    { gui_mode = mode;    }
void run2_set_tty_mode(int mode)    { tty_mode = mode;    }
void run2_set_verbose_level(int level){ verbose_level = level;}

const char *
run2_get_program_name (void)
{
  if (!program_name || !*program_name)
    return "run2";
  return program_name;
}

void
run2_set_program_name (const char *p)
{
  if (program_name)
    free (program_name);
  program_name = run2_strdup (run2_basename (p));
}

const char *
run2_basename (const char *name)
{
  const char *base;

#if defined(__CYGWIN__) || defined(__MINGW32__) || defined(_MSC_VER)
  /* Skip over the disk name in MSDOS pathnames. */
  if (isalpha ((unsigned char) name[0]) && name[1] == ':')
    name += 2;
#endif

  for (base = name; *name; name++)
    if ((*name == '/')
#if defined(__CYGWIN__) || defined(__MINGW32__) || defined(_MSC_VER)
       || (*name == '\\')
#endif
       )
      base = name + 1;
  return base;
}

int
run2_ends_with(const char* s1, const char* s2)
{
  int len1;
  int len2;
  int retval = 0;
  len1 = strlen(s1);
  len2 = strlen(s2);
  if (len1 - len2 >= 0)
    if (stricmp(&(s1[len1-len2]),s2) == 0)
      retval = 1;
  return retval;
}

void
run2_strip_exe(char* s)
{
  size_t slen = strlen(s);
  if ((slen > 4) && /* long enough to have .exe extension */
    /* second part not evaluated (short circuit) if exec_arg too short */
    (stricmp(&(s[slen-4]),".exe") == 0))
  s[slen-4] = '\0';
}

#ifndef ORIGINAL_RUN
/*
 * This function attempts to locate a file which may be in any of
 * several directories. Unlike the original pfopen, it does not
 * return a FILE pointer to the opened file, but rather returns
 * the fully-qualified filename of the first match found. Returns
 * NULL if not found.
 */
Ustr*
run2_pfopen (const Ustr *name, const Ustr *dirs)
{
    Ustr *tdirs = NULL;
    Ustr *tname = NULL;
    Ustr *returnVal = NULL;
    Ustr *tok = USTR_NULL;
    size_t off = 0;

    int foundit = 0;

    if (dirs == NULL || ustr_len (dirs) == 0)
        return NULL;

    tname = USTR_CHECK (ustr_dup (name));
    tdirs = USTR_CHECK (ustr_dup (dirs));
    while ((tok = ustr_split_cstr (tdirs, &off, SEP_CHARS, USTR_NULL, USTR_FLAG_SPLIT_DEF)) &&
           (foundit == 0))
    {
      foundit = run2_fileExists (&returnVal, tok, tname);
      ustr_free(tok); /* have to free to avoid mem leaks */
    }

    ustr_free(tdirs);
    ustr_free(tname);
    return returnVal;
}

int
run2_fileExists (Ustr **fullname, const Ustr* path, const Ustr* name)
{
  int retval = 0;
  FILE* file;
  size_t len;
  Ustr* worku = NULL;
  char* workc = NULL;
  if (path != NULL)
    {
       worku = ustr_dup (path);
       len = ustr_len (worku);
       workc = ustr_wstr (worku);
       if (len && workc[len-1] != '/' && workc[len-1] != '\\')
       {
          USTR_ADD_OSTR (&worku, PATH_SEP_CHAR_STR);
       }
    }
  else
    worku = ustr_dup_empty ();

  ustr_add (&worku, name);

  workc = (char *) cygwin_create_path (CCP_WIN_A_TO_POSIX | CCP_RELATIVE, ustr_cstr (worku));
  if (!workc)
    {
      errorMsg ("Unable to convert \"%s\" to posix format: %s",
                ustr_cstr (worku), strerror (errno));
    }
  else
    {
      debugMsg (3, "(%s) looking for...\t%s", __func__, workc);

      file = fopen (workc, "rb");
      if (file != NULL)
        {
          fclose(file);
          if (fullname != NULL)
            *fullname = USTR_CHECK (ustr_dup_cstr (workc));
          retval = TRUE;
        }
      free (workc);
    }
  ustr_free (worku);
  return retval;
}


Ustr *
ustr_check (Ustr * s, const char * fn, int ln)
{
  if (!s)
    {
      run2_error (EXIT_FAILURE, ENOMEM,
                  "Unable to allocate memory for ustr: %s:%d",
                  fn, ln);
      abort ();
    }
  if ((ustr_ro (s) == USTR_FALSE) &&
      (ustr_enomem (s) == USTR_TRUE))
    {
      run2_error (EXIT_FAILURE, ENOMEM,
                  "Unable to allocate memory for ustr: %s:%d",
                  fn, ln);
      abort ();
    }
  return s;
}
#else /* ORIGINAL_RUN */
/*
 * This function attempts to locate a file which may be in any of
 * several directories. Unlike the original pfopen, it does not
 * return a FILE pointer to the opened file, but rather returns
 * the fully-qualified filename of the first match found. Returns
 * NULL if not found.
 */
char*
run2_pfopen (const char *name, const char *dirs)
{
    char *tdirs = NULL;
    char *tname = NULL;
    char *returnVal = NULL;
    char *tok = NULL;
    size_t off = 0;

    int foundit = 0;

    if (dirs == NULL || *dirs == '\0')
        return NULL;

    tname = run2_strdup (name);
    tdirs = run2_strdup (dirs);
    for (tok = strtok (tdirs, SEP_CHARS);
         (foundit == 0) && (tok != NULL);
	 tok = strtok (NULL, SEP_CHARS))
    {
      foundit = run2_fileExists (&returnVal, tok, tname);
    }

    free(tdirs);
    free(tname);
    return returnVal;
}

int
run2_fileExists (char **fullname, const char* path, const char* name)
{
  int retval = 0;
  FILE* file;
  size_t len;
  char* worku = NULL;
  char* workc = NULL;
  if (path != NULL)
    {
       worku = run2_strdup (path);
       len = strlen (worku);
       if (len && worku[len-1] != '/' && worku[len-1] != '\\')
       {
	  workc = run2_extend_str(worku, PATH_SEP_CHAR_STR, 1);
	  worku = workc;
       }
    }
  else
    worku = run2_strdup("");

  workc = run2_extend_str(worku, name, 1);
  worku = workc;

#ifdef __CYGWIN__
  workc = (char *) cygwin_create_path (CCP_WIN_A_TO_POSIX | CCP_RELATIVE, worku);
  if (!workc)
    {
      errorMsg ("Unable to convert \"%s\" to posix format: %s",
                worku, strerror (errno));
    }
  else
    {
#endif
      debugMsg (3, "(%s) looking for...\t%s", __func__, workc);

      file = fopen (workc, "rb");
      if (file != NULL)
        {
          fclose(file);
          if (fullname != NULL)
            *fullname = run2_strdup (workc);
          retval = TRUE;
        }
#ifdef __CYGWIN__
      /* on non-cygwin, worku and workc point to the same location */
      free (workc);
    }
#endif
  free (worku);
  return retval;
}
#endif /* ORIGINAL_RUN */


void run2_message_(int level, const char* fmt, ...)
{
   va_list args;
   va_start(args, fmt);
   run2_vmessage_(level, fmt, args);
   va_end(args);
}

void run2_vmessage_(int level, const char* fmt, va_list args)
{
  char buf[10000];
  char titlebuf[200];
  int i,j;
  const char *pn = program_name;
  if (!pn || !*pn)
    pn = "run2";

  j = vsprintf(buf,fmt,args);
  buf[j] = '\0'; /* paranoia */
  if (ENABLE_TTY && (run2_tty_is_allowed()))
    {
      if (level >= RUN2_LOG_DEBUG)
        fprintf(stderr, "%s DEBUG: %s\n", pn, buf);
      else if (level < RUN2_LOG_FATAL)
        fprintf (stdout, buf);
      else
        switch (level)
          {
            case RUN2_LOG_FATAL: fprintf(stderr, "%s FATAL: %s\n", pn, buf); break;
            case RUN2_LOG_ERROR: fprintf(stderr, "%s Error: %s\n", pn, buf); break;
            case RUN2_LOG_WARN:  fprintf(stderr, "%s Warning: %s\n", pn, buf); break;
            case RUN2_LOG_INFO:  fprintf(stderr, "%s Info: %s\n", pn, buf); break;
            default: fprintf(stderr, "%s <unknown criticality>: %s\n", pn, buf); break;
          }
      return;
    }
  if (ENABLE_GUI && (run2_gui_is_allowed()))
    {
      i = 0;
      if (level >= RUN2_LOG_DEBUG)
        i = sprintf(titlebuf, "%s DEBUG", pn);
      else if (level < RUN2_LOG_FATAL)
        i = sprintf(titlebuf, "%s", pn);
      else
        switch (level)
          {
            case RUN2_LOG_FATAL: i = sprintf(titlebuf, "%s FATAL", pn); break;
            case RUN2_LOG_ERROR: i = sprintf(titlebuf, "%s Error", pn); break;
            case RUN2_LOG_WARN:  i = sprintf(titlebuf, "%s Warning", pn); break;
            case RUN2_LOG_INFO:  i = sprintf(titlebuf, "%s Info", pn); break;
            default: i = sprintf(titlebuf, "%s <unknown criticality>", pn); break;
          }
      titlebuf[i] = '\0'; /* paranoia */

      if (level >= RUN2_LOG_DEBUG)
        MessageBox(NULL, buf, titlebuf, MB_ICONINFORMATION);
      else if (level < RUN2_LOG_FATAL)
        MessageBox(NULL, buf, titlebuf, MB_ICONINFORMATION);
      else
        switch (level)
          {
            case RUN2_LOG_FATAL: MessageBox(NULL, buf, titlebuf, MB_ICONERROR); break;
            case RUN2_LOG_ERROR: MessageBox(NULL, buf, titlebuf, MB_ICONERROR); break;
            case RUN2_LOG_WARN:  MessageBox(NULL, buf, titlebuf, MB_ICONWARNING); break;
            case RUN2_LOG_INFO:  MessageBox(NULL, buf, titlebuf, MB_ICONINFORMATION); break;
            default: MessageBox(NULL, buf, titlebuf, MB_ICONERROR); break;
          }
      return;
    }
}

char**
run2_dupargv (char **vector)
{
  int argc;
  char **rval;

  if (vector == NULL)
    return NULL;

  for (argc = 0; vector[argc] != NULL; argc++)
    ;
  rval = (char **) run2_malloc ((argc + 1) * sizeof (char *));

  for (argc = 0; vector[argc] != NULL; argc++)
  {
    int len = strlen (vector[argc]);
    rval[argc] = run2_strdup (vector[argc]);
  }
  rval[argc] = NULL;
  return rval;
}

int
run2_countargv (char **vector)
{
  char **scan;
  int rc = 0;
  if (vector != NULL)
    for (scan = vector; *scan != NULL; scan++)
      rc++;
  return rc;
}

void
run2_freeargv (char **vector)
{
  char **scan;
  if (vector != NULL)
    {
      for (scan = vector; *scan != NULL; scan++)
        {
          free (*scan);
          *scan = NULL;
        }
      free (vector);
    }
}

static void
run2_verror (int status, int errnum, const char *message, va_list args)
{
  if (errnum)
  {
    /* grow the message to hold error info */
    char const *s = strerror (errnum);
    char *newmsg;
    if (!s) s = "Unknown system error";
    newmsg = run2_extend_str (message, ": ", 1);
    newmsg = run2_extend_str (newmsg, s, 1);
    verrorMsg (newmsg, args);
  }
  else
  {
    verrorMsg (message, args);
  }
  if (status)
    exit (status);
}

void
run2_error (int status, int errnum, const char *message, ...)
{
  va_list args;
  va_start (args, message);
  run2_verror (status, errnum, message, args);
  va_end (args);
}

void
run2_malloc_exit (void)
{
  run2_error (EXIT_FAILURE, 0, "Unable to allocate memory");
  abort ();
}

void*
run2_malloc (size_t sz)
{
  void *p = malloc (sz);
  if (!p && sz != 0)
    run2_malloc_exit ();
  return p;
}

void*
run2_realloc (void *p, size_t sz)
{
  p = realloc (p, sz);
  if (!p && sz != 0)
    run2_malloc_exit ();
  return p;
}

char*
run2_strdup (const char *s)
{
  size_t len = strlen (s);
  char * d = (char *) run2_malloc (len + 1);

  if (s)
    strncpy (d, s, len + 1);
  else
    d[0] = '\0';

  return d;
}

char*
run2_quote_strdup (const char *s, int quote)
{
  size_t len = strlen (s);
  if (quote && (len > strcspn (s, " \t\v\"\\")))
    {
      size_t i, j = 0;
      char * d = (char *) run2_malloc (2*len + 3);
      d[j++] = '"';
      for (i=0; i<len; i++)
	{
	  switch (s[i]) {
	  case '\\':
	    d[j++] = '\\';
	    d[j++] = '\\';
	    break;
	  case '"':
	    d[j++] = '\\';
	    d[j++] = '"';
	    break;
	  default:
	    d[j++] = s[i];
	  }
	}
      d[j++] = '"';
      d[j++] = '\0';
      return d;
    }
  return run2_strdup (s);
}

char *
run2_extend_str (const char *orig_value, const char *add, int to_end)
{
  char *new_value;
  if (orig_value && *orig_value)
    {
      int orig_value_len = strlen (orig_value);
      int add_len = strlen (add);
      new_value = (char *) run2_malloc (add_len + orig_value_len + 1);
      if (to_end)
        {
          strcpy (new_value, orig_value);
          strcpy (new_value + orig_value_len, add);
        }
      else
        {
          strcpy (new_value, add);
          strcpy (new_value + add_len, orig_value);
        }
    }
  else
    {
      new_value = run2_strdup (add);
    }
  return new_value;
}

int
run2_strtol(char *arg, long *value)
{
  char *endptr;
  int errno_save = errno;

  assert(arg!=NULL);
  errno = 0;
  *value = strtol(arg, &endptr, 0);
  if (errno != 0 || *endptr!='\0' || endptr==arg) {
    return 1;
  }
  errno = errno_save;
  return 0;
}

#if HAVE_PWD_H
char *
run2_get_homedir (const char *user)
{
  char *home;
  struct passwd *pwd;

  if (!user || !*user)
    {
      home = getenv("HOME");
      if (home && *home)
        return run2_strdup (home);
      errno = 0;
      pwd = getpwuid (getuid());
      if (pwd)
        return run2_strdup (pwd->pw_dir);
      errorMsg ("Could not determine home directory for current user");
      return NULL;
    }

  errno = 0;
  pwd = getpwnam (user);
  if (pwd)
    return run2_strdup (pwd->pw_dir);
  errorMsg ("Could not determine home directory for user %s", user);
  return NULL;
}
#else
char *
run2_get_homedir (const char *user)
{
  char *home;

  if (!user || !*user)
    {
      home = getenv("HOME");
      if (home && *home)
        return run2_strdup (home);
      errorMsg ("Could not determine home directory for current user");
      return NULL;
    }

  errorMsg ("Could not determine home directory for user %s", user);
  return NULL;
}
#endif

