/*
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */

/*
 * Purpose: Run DOS commands specified by dosemu2 cmdline.
 * Also make-style environment passing with different substitution rules.
 *
 * Author: Stas Sergeev.
 * Reused some code snippets from unix.c of dosemu1, written by
 * Clarence Dang.
 *
 */


#include "emu.h"
#include <errno.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "libbsd/string.h"
#include <unistd.h>

#include "init.h"
#include "int.h"
#include "builtins.h"
#include "utilities.h"
#include "../../dosext/mfs/lfn.h"
#include "redirect.h"
#include "msetenv.h"
#include "system.h"

static int usage(void);
static int do_execute_dos(int argc, char **argv);
static int do_execute_cmdline(int parent);
static int do_set_dosenv(int agrc, char **argv);
static void do_parse_vars(const char *str, char drv, int parent);

static char e_drv;
static int vars_parsed;
static uint8_t drive;

void system_com_reset(void)
{
    e_drv = 0;
    vars_parsed = 0;
    drive = 0xff;
}

int system_main(int argc, char **argv)
{
  int c;
  int is_e = 0, is_p = 0;
  const char *getopt_string = "ercsp";

  if (argc == 1 ||
      (argc == 2 && !strcmp (argv[1], "/?"))) {
    return usage();
  }

  GETOPT_RESET();		// glibc wants this to reser parser state
  while ((c = getopt(argc, argv, getopt_string)) != EOF) {
    /* Got a switch */
    switch (c) {
    case 'e':
      /* Execute the DOS command given in dosemu command line with -E or -K */
      is_e = 1;
      if (config.tty_stderr == 1)
        config.tty_stderr = 0;  // output of -E goes to stdout
      break;
    case 'r':
      /* Execute the DOS command given in the Linux environment variable */
      return do_execute_dos(argc-2, argv+2);
    case 's':
      /* SETENV from unix env */
      return do_set_dosenv(argc-2, argv+2);

    case 'p':
      is_p = 1;
      break;

    default:
      break;
    }
  }

  if (is_p && !is_e) {
    char *vars = misc_e6_options();
    if (vars)
      do_parse_vars(vars, e_drv, 1);
    return 0;
  }
  if (is_e)
    return do_execute_cmdline(is_p);

  return usage();
}


static int usage(void)
{
  com_printf ("Usage: SYSTEM [FLAG COMMAND]\n\n");
  com_printf ("SYSTEM -r ENVVAR\n");
  com_printf ("  Execute the DOS command given in the Linux environment variable \"ENVVAR\".\n\n");
  com_printf ("SYSTEM -e\n");
  com_printf ("  Execute the DOS command given in dosemu command line with -E or -K.\n\n");
  com_printf ("SYSTEM -s ENVVAR [DOSVAR]\n");
  com_printf ("  Set the DOS environment to the Linux environment variable \"ENVVAR\".\n\n");
  com_printf ("SYSTEM -p\n");
  com_printf ("  Set DOS environment variables passed via dosemu command line\n\n");
  com_printf ("SYSTEM\n");
  com_printf ("SYSTEM /?\n");
  com_printf ("  show this help screen\n");

  return 0;
}

static int setupDOSCommand(const char *dos_path, char *r_drv)
{
  char drvStr[2];

  if (drive == 0xff) {
    com_fprintf(com_stderr, "ERROR: Cannot find a drive\n");
    return (1);
  }
  if (dos_path && dos_path[0])
    msetenv("DOSEMU_SYS_DIR", dos_path);
  drvStr[0] = drive + 'A';
  drvStr[1] = '\0';
  msetenv("DOSEMU_SYS_DRV", drvStr);
  if (r_drv)
    *r_drv = drvStr[0];
  return (0);
}

static void setupDOSCommand2(const char *dos_path, char *r_drv)
{
  if (dos_path[1] == ':') {
    char drvStr[2];
    drvStr[0] = dos_path[0];
    drvStr[1] = '\0';
    msetenv("DOSEMU_SYS_DRV", drvStr);
    if (r_drv)
      *r_drv = drvStr[0];
    dos_path += 3;
  }
  if (dos_path[0])
    msetenv("DOSEMU_SYS_DIR", dos_path);
}

static int do_system(const char *cmd)
{
  dbug_printf ("About to Execute: %s\n", cmd);
  msetenv("DOSEMU_SYS_CMD", cmd);
  return (0);
}

static int do_execute_dos(int argc, char **argv)
{
  const char *cmd;

  if (!argc)
    return 1;
  cmd = getenv(argv[0]);
  if (!cmd)
    return (1);
  return do_system(cmd);
}

static void _do_parse_vars(char *str, char drv, int parent)
{
  char *p, *p0, *p1, *sub;
#define MAX_RESOURCE_PATH_LENGTH 128
  char buf[MAX_RESOURCE_PATH_LENGTH];
  char buf2[MAX_RESOURCE_PATH_LENGTH];

  /* find env=val patterns in option and call msetenv() on them */
  while (1) {
    if (!(p = strchr(str, '=')))
      return;
    if (p == str || p[1] == 0)
      return;
    *p = 0;
    p0 = strrchr(str, ' ');
    if (!p0)
      p0 = str;
    else
      p0++;
    p++;
    p1 = strchr(p, ' ');
    if (p1)
      *p1 = 0;
    /* %D expands to drive letter */
    while ((sub = strstr(p, "%D"))) {
      if (!drv) {
        error("cannot expand %%D, no drive found\n");
        leavedos(32);
      }
      sub[0] = drv;
      sub[1] = ':';
    }
    /* %P means only at parent env */
    if (p0 && strncmp(p0, "%P", 2) == 0) {
      if (parent) {
        strlcpy(buf2, p0 + 2, sizeof(buf2));
        p0 = buf2;
      } else {
        p0 = NULL;
      }
    }
    /* %C means only at child env */
    if (p0 && strncmp(p0, "%C", 2) == 0) {
      if (!parent) {
        strlcpy(buf2, p0 + 2, sizeof(buf2));
        p0 = buf2;
      } else {
        p0 = NULL;
      }
    }
    if (p0) {
      /* %O expands to old value */
      if ((sub = strstr(p, "%O"))) {
        char *old = mgetenv(p0);
        int offs = sub - p;
        if (old)
          snprintf(buf, sizeof(buf), "%.*s%s%s", offs, p, old, p + offs + 2);
        else
          snprintf(buf, sizeof(buf), "%.*s%s", offs, p, p + offs + 2);
        p = buf;
      }

//      com_printf("setenv %s=%s\n", p0, p);
      if (parent)
        msetenv(p0, p);
      else
        msetenv_child(p0, p);
    }
    if (!p1)
      break;
    p1++;
    str = p1;
  }
}

static void do_parse_vars(const char *str, char drv, int parent)
{
  char *s = strdup(str);
  _do_parse_vars(s, drv, parent);
  free(s);
}

static int do_execute_cmdline(int parent)
{
  char *vars, drv = e_drv;
  int ret = 0, first = 0;

  /* Multiple invocations are possible due to freecom bug.
   * First in a command.com's context and second in a separate process.
   * If e_drv is not set then this is a first invocation. */
  if (!e_drv) {
    if (config.unix_path) {
      int err = setupDOSCommand(config.dos_path, &drv);
      if (err)
        return err;
    } else if (config.dos_path) {
      setupDOSCommand2(config.dos_path, &drv);
    }
    e_drv = drv;  // store for later -p
    first = 1;
  }
  vars = misc_e6_options();
  if (vars) {
    uint16_t ppsp = com_parent_psp_seg();
    /* if we have a parent then we are not command.com (hack) */
    if (ppsp && ppsp != com_psp_seg()) {
      if (parent && first) {
        do_parse_vars(vars, drv, 1);
        vars_parsed = 1;
      }
      if (!vars_parsed || first) {
        mresize_env(strlen(vars));
        do_parse_vars(vars, drv, 0);
      }
    } else if (first) {
      do_parse_vars(vars, drv, 0);
      vars_parsed = 1;
    }
  }
  if (config.dos_cmd)
    ret = do_system(config.dos_cmd);
  return ret;
}

static int do_set_dosenv(int argc, char **argv)
{
  char* data;
  char* varname;

  if (argc == 0) return usage();

  if (argc == 1) {
    varname = argv[0];
  } else {
    varname = argv[1];
  }

  data = getenv(argv[0]);
  if (data) {
    if (msetenv(varname, data))
      return 1;
  }
  return 0;
}

static void system_scrub(void)
{
#ifdef USE_DJDEV64
  if (config.elfload || config.elfload_type == 2) {
    free(config.dos_cmd);
    switch (config.elfload_type) {
    case 0:
      config.dos_cmd = strdup("elfload 0");
      break;
    case 1:
      config.dos_cmd = strdup("elfload2 0");
      break;
    case 2:
      if (config.elfload) {
        error("cannot load %s with loader type 2 (self-load)\n", config.elfload);
        leavedos(33);
      }
      config.dos_cmd = strdup("elfload 1");
      break;
    }
  }
#endif
  if (!config.unix_path)
    return;
  if (!config.unix_path[0]) {
    /* omitted unix path means current dir */
    free(config.unix_path);
    config.unix_path = strdup(".");
  }
  if (!config.dos_cmd) {
    char *p;
    if (!exists_dir(config.unix_path)) {
      /* hack to support full path in -K */
      if (!exists_file(config.unix_path))
        goto err;
      error("@Warning: -K <full_path> deprecated.\n"
            "\t<full_path> should now be passed directly, so remove -K\n");
      p = strrchr(config.unix_path, '/');
      if (!p)
        goto err;
      config.dos_cmd = p + 1;
      *p = 0;
    }
  }
  add_syscom_drive(config.unix_path, &drive);
  return;

err:
  free(config.unix_path);
  config.unix_path = NULL;
}

CONSTRUCTOR(static void init(void))
{
  register_config_scrub(system_scrub);
}
