#include "amiga.h"
#include "signals.h"
#include "fifofd.h"
#include "timers.h"
#include "amigados.h"
#include <exec/execbase.h>
#include <dos/var.h>
#include <workbench/startup.h>
#include <proto/timer.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <sys/time.h>
#include <pwd.h>

struct Process *_us;
struct timeinfo *_odd_timer;
ULONG _odd_sig;
long _startup_time;
long _stack_size;
struct Device *TimerBase;

static char *empty_env = 0;	/* A default empty environment */
char **environ;			/* Unix style environment variable list */
char *_system_name;

extern struct ExecBase *SysBase;
extern struct passwd _amiga_user;
extern main(int argc, char **argv, char **envp);

static void nomem(void)
{
  _fail("No memory");
}

static void *_xmalloc(unsigned n)
{
    void *p = malloc(n);

    if (!p) nomem();

    return p;
}

static void *_xrealloc(void *p, unsigned n)
{
    void *p2 = realloc(p, n);

    if (!p2) nomem();

    return p2;
}

static char *safe_copystr(char *str)
{
    char *new;

    if (!str) str = "";
    new = malloc(strlen(str) + 1);
    if (!new) return 0;
    return strcpy(new, str);
}

static char *copystr(char *str)
{
    char *new = safe_copystr(str);

    if (!new) nomem();

    return new;
}

void make_environ(void)
/* Effect: Builds a UNIX style environ variable from the AmigaDOS environment.
*/
{
    int env_count = 0;
    long env_len = 0;
    struct LocalVar *scan_env;
    char **new_environ, *env_text;

    for (scan_env = (struct LocalVar *)_us->pr_LocalVars.mlh_Head;
	 scan_env->lv_Node.ln_Succ;
	 scan_env = (struct LocalVar *)scan_env->lv_Node.ln_Succ)
	if (scan_env->lv_Node.ln_Type == LV_VAR &&
	    !(scan_env->lv_Flags & (GVF_GLOBAL_ONLY | GVF_BINARY_VAR)))
	{
	    /* We only handle local text variables */
	    env_count++;
	    env_len += 2 + strlen(scan_env->lv_Node.ln_Name) + scan_env->lv_Len;
	}

    new_environ = environ = (char **)_xmalloc(sizeof(char *) * (1 + env_count) +
					      env_len);
    env_text = (char *)(environ + (1 + env_count));
    if (!environ) environ = &empty_env;
    else
    {
	for (scan_env = (struct LocalVar *)_us->pr_LocalVars.mlh_Head;
	     scan_env->lv_Node.ln_Succ;
	     scan_env = (struct LocalVar *)scan_env->lv_Node.ln_Succ)
	    if (scan_env->lv_Node.ln_Type == LV_VAR &&
		!(scan_env->lv_Flags & (GVF_GLOBAL_ONLY | GVF_BINARY_VAR)))
	    {
		/* We only handle local text variables */
		char *env_name = scan_env->lv_Node.ln_Name;
		int env_len = scan_env->lv_Len;

		*new_environ++ = env_text;
		while (*env_name) *env_text++ = *env_name++;
		*env_text++ = '=';
		env_name = scan_env->lv_Value;
		while (env_len--) *env_text++ = *env_name++;
		*env_text++ = '\0';
	    }
	*new_environ = 0;
    }
}

/* _main routine.
   Hides the differences between wb & cli.
   Provides a unix-like environment (including coomand-line parsing &
   wildcard expansion)
*/

#define DEFPATLEN 256
#define NAMELEN 1024

struct args
{
    int size;
    int argc;
    char **argv;
};

static void make_argv(struct args *args, int argc)
{
    args->size = argc;
    args->argc = 0;
    args->argv = _xmalloc(sizeof(char *) * argc);
}

static int safe_add_arg(struct args *args, char *argument, int copy)
{
    char *arg_copy;

    if (copy) arg_copy = safe_copystr(argument);
    else arg_copy = argument;
    if (!arg_copy) return 0;

    if (args->argc >= args->size)
    {
	/* Make argv bigger */
	if (args->size * 2 < args->size + 16) args->size += 16;
	else args->size *= 2;
	args->argv = realloc(args->argv, sizeof(char *) * args->size);
	if (!args->argv) return 0;
    }
    args->argv[args->argc++] = arg_copy;
    return 1;
}

static void add_arg(struct args *args, char *argument, int copy)
{
    if (!safe_add_arg(args, argument, copy)) nomem();
}

static void concat_args(struct args *args, struct args *add)
{
    if (args->argc + add->argc > args->size)
    {
	args->size = (args->argc + add->argc) * 2;
	args->argv = _xrealloc(args->argv, sizeof(char *) * args->size);
    }
    memcpy(args->argv + args->argc, add->argv, add->argc * sizeof(char *));
    args->argc += add->argc;
    free(add->argv);
}

typedef enum { quote_none, quote_single, quote_double } quote_type;
typedef enum { extract_normal, extract_test, extract_pattern } extract_type;

static void extract(char *buf, char *start, char *end,
		    quote_type type, extract_type extract)
{
  char *res = buf;

  switch (type)
    {
    case quote_single:
      if (extract != extract_test)
	{
	  buf[end - start] = '\0';
	  memcpy(buf, start, end - start);
	}
      else strcpy(buf, "a");	/* Things in quotes are never patterns */
      break;

    case quote_none:
      while (start < end)
	{
	  if (start[0] == '\\' && start[1])
	    {
	      start += 2;
	      /* Wildcard are escaped */
	      if (extract == extract_test) *res++ = 'a';
	      else if (extract == extract_pattern)
		switch (start[-1])
		  {
		  case '?': case '#': case '(': case ')': case '|': case '[':
		  case ']': case '~': case '%': case '*': case '\'':
		    *res++ = '\'';
		  default:
		    *res++ = start[-1];
		    break;
		  }
	      else *res++ = start[-1];
	    }
	  else *res++ = *start++;
	}
      *res++ = '\0';
      break;

    case quote_double:
      while (start < end)
	{
	  if (start[0] == '*' && start[1])
	    {
	      start += 2;
	      switch (start[-1])
		{
		case 'n': *res++ = '\n'; break;
		case 'e': *res++ = '\x1b'; break;
		default: *res++ = start[-1]; break;
		}
	    }
	  else *res++ = *start++;
	}
      *res++ = '\0';
      break;
    }
}

void _main(char *line)
/* Effect: Call unix_main with wildcards in argc & argv expanded (like unix)
     Also, do some early amiga initialisation for emacs.
*/
{
  struct args args, wildargs;
  char *pattern, *arg_start, *arg_end, *arg;
  quote_type arg_quoted;
  long patlen = DEFPATLEN;
  struct AnchorPath *anchor;
  struct timeval now;

  if (SysBase->LibNode.lib_Version < 37) XCEXIT(20);

  stdin->_file = 0;
  stdin->_flag = _IOREAD;
  stdout->_file = 1;
  stdout->_flag = _IOWRT;
  stderr->_file = 2;
  stderr->_flag = _IORW | _IONBF;

  _us = (struct Process *)FindTask(0);
  _odd_timer = _alloc_timer();
  if (!_odd_timer) _fail("Failed to create timer");
  _odd_sig = _timer_sig(_odd_timer);
  TimerBase = _odd_timer->io->tr_node.io_Device;
  GetSysTime(&now);
  _startup_time = now.tv_secs;

  /* These use _startup_time, so must be here */
  _init_fifo();
  _init_signals();

  if (_us->pr_CLI) _stack_size = ((struct CommandLineInterface *)BADDR(_us->pr_CLI))->cli_DefaultStack << 2;
  else _stack_size = _us->pr_StackSize;

  /* Make unix-like argc, argv (expand wildcards) */
  if (!line[0])			/* Workbench, create argc & argv from files passed */
    {
      extern struct WBStartup *WBenchMsg;
      int i;
      BPTR nilin = Open("NIL:",MODE_NEWFILE);
      BPTR nilout = Open("NIL:",MODE_NEWFILE);
      BPTR nilerr = Open("NIL:",MODE_NEWFILE);

      /* Initialise I/O. Nothing is available */
      if (!nilin || !nilout || !nilerr)
	{
	  if (nilin) Close(nilin);
	  if (nilout) Close(nilout);
	  if (nilerr) Close(nilerr);
	  nomem();
	}
      _init_unixio(nilin, TRUE, nilout, TRUE, nilerr, TRUE);

      /* Make argc, argv from Workbench parameters */
      make_argv(&args, WBenchMsg->sm_NumArgs);

      for (i = 0; i < WBenchMsg->sm_NumArgs; i++)
	{
	  char filename[256];

	  if (NameFromLock(WBenchMsg->sm_ArgList[i].wa_Lock, filename, 256))
	    {
	      AddPart(filename, WBenchMsg->sm_ArgList[i].wa_Name, 256);
	      add_arg(&args, filename, TRUE);
	    }
	  /* else A parameter was lost, cry, cry, cry */
	}
    }
  else				/* From CLI expand wildcards (with unix-like command line parsing) */
    {
      int close_error;
      BPTR in, out, error;

      /* Initialise I/O. Copy CLI info */
      in = Input();
      out = Output();
      close_error = FALSE;
      if ((error = _us->pr_CES) == 0)
	{
	  close_error = TRUE;
	  if ((error = Open("*", MODE_OLDFILE)) == 0)
	    if ((error = Open("NIL:", MODE_OLDFILE)) == 0) nomem();
	}
      _init_unixio(in, FALSE, out, FALSE, error, close_error);

      make_argv(&args, 1);

      anchor = _xmalloc(sizeof(struct AnchorPath) + NAMELEN);
      anchor->ap_Strlen = NAMELEN;
      pattern = _xmalloc(DEFPATLEN);
      while (1)
	{
	  long new_patlen;
	  int wild;

	  /* Skip white space */
	  while (isspace(*line)) line++;
	  if (!*line) break;	/* End of command line */

	  /* Extract next word */
	  /* Words in double quotes are handled AmigaDOS style
	     (+ filename expansion) */
	  if (*line == '"')
	    {
	      line++;
	      arg_start = line;
	      /* Find end of word */
	      while (*line && *line != '"')
		{
		  /* * is an escape character inside double quotes
		     (AmigaDOS compatibility) */
		  if ((*line == '*') && line[1]) line++;
		  line++;
		}
	      arg_end = line;
	      if (*line == '"') line++;
	      arg_quoted = quote_double;
	    }
	  /* Words in single quotes are handled unix style */
	  else if (*line == '\'')
	    {
	      line++;
	      arg_start = line;
	      /* Find end of word */
	      while (*line && *line != '\'') line++;
	      arg_end = line;
	      if (*line == '\'') line++;
	      arg_quoted = quote_single;
	    }
	  /* Unquoted words are handled unix style */
	  else			/* Plain word */
	    {
	      arg_start = line;
	      /* Find end of word */
	      while (*line && !isspace(*line))
		{
		  if (*line == '\\' && line[1]) line++;
		  line++;
		}
	      arg_end = line;
	      arg_quoted = quote_none;
	    }
	  arg = _xmalloc(arg_end - arg_start + 1);
	  if (args.argc == 0)	/* Command name is left untouched */
	    {
	      strncpy(arg, arg_start, arg_end - arg_start);
	      arg[arg_end - arg_start] = 0;
	      add_arg(&args, arg, FALSE);
	    }
	  else
	    {
	      new_patlen = (arg_end - arg_start) * 2 + 16;
	      if (new_patlen > patlen)
		{
		  free(pattern);
		  pattern = _xmalloc(new_patlen);
		  patlen = new_patlen;
		}
	      extract(arg, arg_start, arg_end, arg_quoted, extract_test);
	      wild = ParsePattern(arg, pattern, patlen);
	      if (wild < 0)
		{
		  *arg_end = 0;
		  _fail("Invalid wildcard %s", arg_start);
		}
	      if (!wild)
		{
		  extract(arg, arg_start, arg_end, arg_quoted, extract_normal);
		  add_arg(&args, arg, FALSE);
		}
	      else
		{
		  int none = TRUE;
		  long error;

		  anchor->ap_Flags = anchor->ap_Reserved = anchor->ap_BreakBits = 0;
		  extract(arg, arg_start, arg_end, arg_quoted, extract_pattern);
		  make_argv(&wildargs, 16);
		  error = MatchFirst(arg, anchor);
		  while (!error)
		    {
		      none = FALSE;
		      if (!safe_add_arg(&wildargs, anchor->ap_Buf, TRUE))
			{
			  error = ERROR_NO_FREE_STORE;
			  break;
			}
		      error = MatchNext(anchor);
		    }
		  MatchEnd(anchor);
		  if (error != ERROR_NO_MORE_ENTRIES)
		      _fail("Error expanding arguments");
		  if (none)
		    {
		      extract(arg, arg_start, arg_end, arg_quoted, extract_normal);
		      add_arg(&args, arg, FALSE);
		    }
		  else
		    {
		      tqsort(wildargs.argv, wildargs.argc);
		      concat_args(&args, &wildargs);
		      free(arg);
		    }
		}
	    }
	}
      free(pattern);
      free(anchor);
    }

  make_environ();

  if (!(_amiga_user.pw_name = getenv("USER"))) _amiga_user.pw_name = "user";
  if (!(_amiga_user.pw_gecos = getenv("USERNAME")))
    _amiga_user.pw_gecos = _amiga_user.pw_name;
  if (!(_amiga_user.pw_dir = getenv("HOME"))) _amiga_user.pw_dir = "s:";
  if (!(_amiga_user.pw_shell = getenv("SHELL"))) _amiga_user.pw_shell = "bin:sh";
  if (!(_system_name = getenv("HOSTNAME"))) _system_name = "amiga";
    
  main(args.argc, args.argv, environ);
  exit(0);
}
