/*
 * File:        mred.c
 * Purpose:     GRacket main file, including a hodge-podge of global stuff
 * Author:      Matthew Flatt
 * Created:     1995
 * Copyright:   (c) 2004-2013 PLT Design Inc.
 * Copyright:   (c) 1995-2000, Matthew Flatt
 */

#ifdef WIN32
/* Hack: overwrite "y" with "n" in binary to disable checking for another
   instance of the same app. */
char *check_for_another = "yes, please check for another";
static int wx_in_terminal = 0;
# define MZ_DEFINE_UTF8_MAIN
# define PRE_FILTER_CMDLINE_ARGUMENTS
static void pre_filter_cmdline_arguments(int *argc, char ***argv);
# define WINMAIN_ALREADY
# undef wx_xt
#endif

struct Scheme_Env;
static char *get_gr_init_filename(struct Scheme_Env *env);

#ifdef wx_xt
# define PRE_FILTER_CMDLINE_ARGUMENTS
static void pre_filter_cmdline_arguments(int *argc, char ***argv);
#endif

#ifdef wx_mac
# define PRE_FILTER_CMDLINE_ARGUMENTS
static void pre_filter_cmdline_arguments(int *argc, char ***argv);
#endif

#define UNIX_INIT_FILENAME "~/.gracketrc"
#define WINDOWS_INIT_FILENAME "%%HOMEDIRVE%%\\%%HOMEPATH%%\\gracketrc.rktl"
#define MACOS9_INIT_FILENAME "PREFERENCES:gracketrc.rktl"
#define GET_INIT_FILENAME get_gr_init_filename
#if WIN32
# define NEED_CONSOLE_PRINTF
# define DEFER_EXPLICIT_EXIT
#else
# define PRINTF printf
#endif
#define PROGRAM "GRacket"
#define PROGRAM_LC "gracket"
#define INITIAL_BIN_TYPE "ri"

#define CMDLINE_STDIO_FLAG
#define INITIAL_NAMESPACE_MODULE "racket/gui/init"
#define GRAPHICAL_REPL

# include "../racket/main.c"

static char *get_gr_init_filename(Scheme_Env *env)
{
  char *s, *s2;
  int len, i;

  s = get_init_filename(env);
  if (s) {
    len = strlen(s);
    for (i = len - 8; i; i--) {
      if (!strncmp(s XFORM_OK_PLUS i, "racketrc", 8)) {
        s2 = (char *)malloc(len + 2);
        memcpy(s2, s, i);
        memcpy(s2 + i + 1, s + i, len - i + 1);
        s2[i] = 'g';
        s = s2;
        break;
      }
    }
  }

  return s;
}

/***********************************************************************/
/*                        Win32 handling                               */
/***********************************************************************/

#ifdef WIN32

/* ---------------------------------------- */
/*             stdio to console             */
/* ---------------------------------------- */

static void MrEdSchemeMessages(char *, ...);
static Scheme_Object *stdin_pipe;

static HANDLE console_out;
static HANDLE console_in;
static Scheme_Object *console_inport;
static HWND console_hwnd;
static int has_stdio, stdio_kills_prog;
static HANDLE waiting_sema;
static void *orig_signal_handle;
static void *orig_break_handle;

typedef HWND (WINAPI* gcw_proc)();

static void init_console_in()
{
  if (!console_in) {
    console_in = GetStdHandle(STD_INPUT_HANDLE);
    MZ_REGISTER_STATIC(console_inport);
    console_inport = scheme_make_fd_input_port((int)console_in, scheme_intern_symbol("stdin"), 0, 0);
  }
}

static BOOL WINAPI ConsoleHandler(DWORD op)
{
  if (stdio_kills_prog) {
    ReleaseSemaphore(waiting_sema, 1, NULL);
  } else {
    scheme_break_main_thread_at(orig_break_handle);
    scheme_signal_received_at(orig_signal_handle);
  }
  return TRUE;
}

static void WaitOnConsole()
{
  DWORD wrote;

  stdio_kills_prog = 1;
  if (console_hwnd) {
    AppendMenu(GetSystemMenu(console_hwnd, FALSE), 
	       MF_STRING,
	       SC_CLOSE,
	       "Close");
    /* Un-gray the close box: */
    RedrawWindow(console_hwnd, NULL, NULL, 
		 RDW_FRAME | RDW_INVALIDATE | RDW_UPDATENOW);
  }

  WriteConsole(console_out, "\n[Exited. Close box or Ctrl-C closes the console.]\n", 51, &wrote, NULL);

  WaitForSingleObject(waiting_sema, INFINITE);
}

static void MrEdSchemeMessages(char *msg, ...)
{
  GC_CAN_IGNORE va_list args;
  
  scheme_start_atomic();

  XFORM_HIDE_EXPR(va_start(args, msg));

  if (!console_out) {
    AllocConsole();
    console_out = GetStdHandle(STD_OUTPUT_HANDLE);

    if (!wx_in_terminal) {
      has_stdio = 1;
      waiting_sema = CreateSemaphore(NULL, 0, 1, NULL);
      orig_signal_handle = scheme_get_signal_handle();
      orig_break_handle = scheme_get_main_thread_break_handle();
      SetConsoleCtrlHandler(ConsoleHandler, TRUE);      

      {
	HMODULE hm;
	gcw_proc gcw;

	hm = LoadLibrary("kernel32.dll");
	if (hm)
	  gcw = (gcw_proc)GetProcAddress(hm, "GetConsoleWindow");
	else
	  gcw = NULL;
    
	if (gcw)
	  console_hwnd = gcw();
      }

      if (console_hwnd) {
	EnableMenuItem(GetSystemMenu(console_hwnd, FALSE), SC_CLOSE,
		       MF_BYCOMMAND | MF_GRAYED);
	RemoveMenu(GetSystemMenu(console_hwnd, FALSE), SC_CLOSE, MF_BYCOMMAND);
      }
    }
  }

  if (!msg) {
    char *s;
    intptr_t l, d;
    DWORD wrote;

    s = va_arg(args, char*);
    d = va_arg(args, intptr_t);
    l = va_arg(args, intptr_t);

    WriteConsole(console_out, s XFORM_OK_PLUS d, l, &wrote, NULL);
  } else {
    char *buffer;
    DWORD wrote;
    /* FIXME: multiplying by 5 and adding 80 works for
       all the cases where printf mode is currently used 
       for the function, but it's completely a hack. */
    buffer = (char *)malloc((5 * strlen(msg)) + 80);
    vsprintf(buffer, msg, args);
    WriteConsole(console_out, buffer, strlen(buffer), &wrote, NULL);
    free(buffer);
  }

  scheme_end_atomic_no_swap();

  XFORM_HIDE_EXPR(va_end(args));
}

static void MrEdSchemeMessagesOutput(char *s, intptr_t l)
{
  if (l)
    MrEdSchemeMessages(NULL, s, 0, l);
}

static Scheme_Object *console_reading;

static void add_console_reading()
{
  Scheme_Thread *thread;
  thread = scheme_get_current_thread();

  if (!console_reading) {
    MZ_REGISTER_STATIC(console_reading);
    console_reading = scheme_make_null();
  }

  console_reading = scheme_make_pair((Scheme_Object *)thread,
				     console_reading);
}

static void remove_console_reading()
{
  Scheme_Object *p, *prev = NULL;
  Scheme_Thread *thread;
  thread = scheme_get_current_thread();

  if (!console_reading)
    return;

  p = console_reading;
  while (SCHEME_PAIRP(p)) {
    if (SAME_OBJ(SCHEME_CAR(p), (Scheme_Object *)thread)) {
      if (prev)
	SCHEME_CDR(prev) = SCHEME_CDR(p);
      else
	console_reading = SCHEME_CDR(p);
      return;
    }
    prev = p;
    p = SCHEME_CDR(p);
  }
}

static void break_console_reading_threads()
{
  Scheme_Object *p;

  if (!console_reading)
    return;

  for (p = console_reading; SCHEME_PAIRP(p); p = SCHEME_CDR(p)) {
    scheme_break_thread((Scheme_Thread *)SCHEME_CAR(p));
  }
}

static intptr_t mrconsole_get_string(Scheme_Input_Port *ip,
				     char *buffer, intptr_t offset, intptr_t size,
				     int nonblock, Scheme_Object *unless)
{
  intptr_t result;
  Scheme_Object *pipe = (Scheme_Object *)ip->port_data;

  if (!pipe) return 0;
  MrEdSchemeMessages("");

  init_console_in();
  pipe = console_inport;

  add_console_reading();
  result = scheme_get_byte_string_unless("console get-string", pipe, 
					 buffer, offset, size, 
					 nonblock, 0, NULL,
					 unless);
  remove_console_reading();
  return result;
}

static Scheme_Object *mrconsole_progress_evt(Scheme_Input_Port *ip)
{
  Scheme_Object *pipe = (Scheme_Object *)ip->port_data;

  if (!pipe) return NULL;
  MrEdSchemeMessages("");

  init_console_in();
  pipe = console_inport;

  return scheme_progress_evt(pipe);
}

static int mrconsole_peeked_read(Scheme_Input_Port *ip,
				 intptr_t amount,
				 Scheme_Object *unless,
				 Scheme_Object *target_ch)
{
  Scheme_Object *pipe = (Scheme_Object *)ip->port_data;

  if (!pipe) return 0;
  MrEdSchemeMessages("");

  init_console_in();
  pipe = console_inport;

  return scheme_peeked_read(pipe, amount, unless, target_ch);
}

static int mrconsole_char_ready(Scheme_Input_Port *ip)
{
  Scheme_Object *pipe = (Scheme_Object *)ip->port_data;

  if (!pipe) return 0;
  MrEdSchemeMessages("");

  init_console_in();
  pipe = console_inport;

  return scheme_char_ready(pipe);
}

static void mrconsole_close(Scheme_Input_Port *ip)
{
  Scheme_Object *pipe = (Scheme_Object *)ip->port_data;

  if (!pipe) return;

  init_console_in();
  pipe = console_inport;

  scheme_close_input_port(pipe);
}

static Scheme_Object *MrEdMakeStdIn(void)
{
  Scheme_Object *readp;
  Scheme_Input_Port *ip;

  if (scheme_get_place_id() == 0) {
    MZ_REGISTER_STATIC(stdin_pipe);

    scheme_pipe(&readp, &stdin_pipe);
  } else {
    /* for a non-main place, the port will be replaced anyway */
    readp = NULL;
  }

  ip = scheme_make_input_port(scheme_make_port_type("mred-console-input-port"),
			      readp,
			      scheme_intern_symbol("mred-console"),
			      mrconsole_get_string,
			      NULL,
			      mrconsole_progress_evt,
			      mrconsole_peeked_read,
			      mrconsole_char_ready,
			      mrconsole_close,
			      NULL,
			      0);

  return (Scheme_Object *)ip;
}

static intptr_t stdout_write(Scheme_Output_Port*op, const char *s, intptr_t d, intptr_t l, 
			 int rarely_block, int enable_break)
{
  if (l)
    MrEdSchemeMessages(NULL, s, d, l);
  return l;
}

static Scheme_Object *MrEdMakeStdOut(void)
{
  Scheme_Object *outtype;

  outtype = scheme_make_port_type("stdout");

  return (Scheme_Object *)scheme_make_output_port(outtype, NULL,
						  scheme_intern_symbol("mred-console"),
						  scheme_write_evt_via_write,
						  stdout_write,
						  NULL, NULL, NULL, NULL, NULL, 0);
}

static intptr_t stderr_write(Scheme_Output_Port*op, const char *s, intptr_t d, intptr_t l, 
			 int rarely_block, int enable_break)
{
  if (l)
    MrEdSchemeMessages(NULL, s, d, l);
  return l;
}

static Scheme_Object *MrEdMakeStdErr(void)
{
  Scheme_Object *errtype;

  errtype = scheme_make_port_type("stderr");

  return (Scheme_Object *)scheme_make_output_port(errtype, NULL,
						  scheme_intern_symbol("mred-console"),
						  scheme_write_evt_via_write,
						  stderr_write,
						  NULL, NULL, NULL, NULL, NULL, 0);
}

static void MrEdExit(int v)
{
  if (has_stdio) {
    WaitOnConsole();
  }

  scheme_immediate_exit(v);
}

# ifdef MZ_PRECISE_GC
START_XFORM_SKIP;
# endif

/* ---------------------------------------- */
/*        single-instance detection         */
/* ---------------------------------------- */

static char *CreateUniqueName()
{
  char desktop[MAX_PATH], session[32], *together;
  int dlen, slen;

  {
    // Name should be desktop unique, so add current desktop name
    HDESK hDesk;
    ULONG cchDesk = MAX_PATH - 1;

    hDesk = GetThreadDesktop(GetCurrentThreadId());
    
    if (!GetUserObjectInformation( hDesk, UOI_NAME, desktop, cchDesk, &cchDesk))
      desktop[0] = 0;
    else
      desktop[MAX_PATH - 1]  = 0;
  }

  {
    // Name should be session unique, so add current session id
    HANDLE hToken = NULL;
    // Try to open the token (fails on Win9x) and check necessary buffer size
    if (OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &hToken)) {
      DWORD cbBytes = 0;
      
      if(!GetTokenInformation( hToken, TokenStatistics, NULL, cbBytes, &cbBytes ) 
	 && GetLastError() == ERROR_INSUFFICIENT_BUFFER) {
	  PTOKEN_STATISTICS pTS;

	  pTS = (PTOKEN_STATISTICS)malloc(cbBytes);
	  
	  if(GetTokenInformation(hToken, TokenStatistics, (LPVOID)pTS, cbBytes, &cbBytes)) {
	    sprintf(session, "-%08x%08x-",
		    pTS->AuthenticationId.HighPart, 
		    pTS->AuthenticationId.LowPart);
	  } else
	    session[0] = 0;
	  free(pTS);
      } else {
	session[0] = 0;
      }
    } else
      session[0] = 0;
  }

  dlen = strlen(desktop);
  slen =  strlen(session);
  together = (char *)malloc(slen + dlen + 1);
  memcpy(together, desktop, dlen);
  memcpy(together + dlen, session, slen);
  together[dlen + slen] = 0;
  
  return together;
}

#define GRACKET_GUID "B2261834-D535-44dd-8511-A26FC8F97DD0"

static int wm_is_gracket;

static BOOL CALLBACK CheckWindow(HWND wnd, LPARAM param)
{
  int i, len, gl;
  DWORD w;
  char **argv, *v;
  COPYDATASTRUCT cd;
  DWORD_PTR result;
  LRESULT ok;

  ok = SendMessageTimeout(wnd, wm_is_gracket,
			  0, 0, 
			  SMTO_BLOCK |
			  SMTO_ABORTIFHUNG,
			  200,
			  &result);

  printf("try %p result %d\n", wnd, result);

  if (ok == 0)
    return TRUE; /* ignore and continue */
  if ((intptr_t)result == 79) {
    /* found it */
  } else
    return TRUE; /* continue search */

  /* wnd is owned by another instance of this application */

  SetForegroundWindow(wnd);
  if (IsIconic(wnd)) 
    ShowWindow(wnd, SW_RESTORE);

  argv = (char **)param;
  
  len = gl = strlen(GRACKET_GUID);
  len += 4 + sizeof(DWORD);
  for (i = 1; argv[i]; i++) {
    len += sizeof(DWORD) + strlen(argv[i]);
  }
  w = i - 1;

  v = (char *)malloc(len);
  memcpy(v, GRACKET_GUID, gl);
  memcpy(v + gl, "OPEN", 4);
  memcpy(v + gl + 4, &w, sizeof(DWORD));
  len = gl + 4 + sizeof(DWORD);
  for (i = 1; argv[i]; i++) {
    w = strlen(argv[i]);
    memcpy(v + len, &w, sizeof(DWORD));
    len += sizeof(DWORD);
    memcpy(v + len, argv[i], w);
    len += w;
  }

  cd.dwData = 79;
  cd.cbData = len;
  cd.lpData = v;

  SendMessage(wnd, WM_COPYDATA, (WPARAM)wnd, (LPARAM)&cd);

  free(v);

  return FALSE;
}

static int CheckSingleInstance(char *normalized_path, char **argv)
{
  /* Check for an existing instance: */
  if (check_for_another[0] != 'n') {
    int alreadyrunning;
    HANDLE mutex;
    int j, l, i;
    char *a, *b;

    /* This mutex creation synchronizes multiple instances of
       the application that may have been started. */
    j = strlen(normalized_path);
    
    b = CreateUniqueName();
    l = strlen(b);
    a = (char *)malloc(j + l + 50);
    memcpy(a, normalized_path, j);
    for (i = 0; i < j; i++) {
      /* backslashes are not allowed in mutex names */
      if (a[i] == '\\') a[i] = '/';
    }
    memcpy(a + j, b, l);
    memcpy(a + j + l, "GRacket-" GRACKET_GUID, strlen(GRACKET_GUID) + 9);
    mutex = CreateMutex(NULL, FALSE, a);
    alreadyrunning = (GetLastError() == ERROR_ALREADY_EXISTS || 
		      GetLastError() == ERROR_ACCESS_DENIED);
    /* The call fails with ERROR_ACCESS_DENIED if the Mutex was 
       created in a different users session because of passing
       NULL for the SECURITY_ATTRIBUTES on Mutex creation. */
    wm_is_gracket = RegisterWindowMessage(a);
    free(a);

    if (alreadyrunning) {
      /* If another instance has been started, try to find it. */
      if (!EnumWindows((WNDENUMPROC)CheckWindow, (LPARAM)argv)) {
	return 1;
      }
    }
  }

  return 0;
}

static void pre_filter_cmdline_arguments(int *argc, char ***argv)
{
  scheme_register_process_global("PLT_WM_IS_GRACKET", (void *)(intptr_t)wm_is_gracket);
  scheme_register_process_global("PLT_GRACKET_GUID", GRACKET_GUID);
}

/* ---------------------------------------- */
/*           command-line parsing           */
/* ---------------------------------------- */

int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR ignored, int nCmdShow)
{
  int j, argc;
  char **argv, *normalized_path;

  load_delayed();

  {
    HANDLE h;
    h = GetStdHandle(STD_OUTPUT_HANDLE);
    if (h && (h != INVALID_HANDLE_VALUE)
        && (GetFileType(h) != FILE_TYPE_UNKNOWN)) {
      wx_in_terminal = 1;
    }
  }

  argv = cmdline_to_argv(&argc, &normalized_path);

  if (CheckSingleInstance(normalized_path, argv))
    return 0;

  if (!wx_in_terminal) {
    scheme_set_stdio_makers(MrEdMakeStdIn,
			    MrEdMakeStdOut,
			    MrEdMakeStdErr);
    scheme_set_console_output(MrEdSchemeMessagesOutput);
  }
  scheme_set_console_printf(MrEdSchemeMessages);
  scheme_set_exit(MrEdExit);

  j = MAIN(argc, argv);

  MrEdExit(j);
  /* shouldn't get here */

  return j;
}

# ifdef MZ_PRECISE_GC
END_XFORM_SKIP;
# endif

#endif

/***********************************************************************/
/*                      X11 flag handling                              */
/***********************************************************************/

#ifdef wx_xt

typedef struct {
  char *flag;
  int arg_count;
} X_flag_entry;

#define SINGLE_INSTANCE "-singleInstance"

X_flag_entry X_flags[] = {
  { "-display", 1 },
  { "-geometry", 1 },
  { "-bg", 1 },
  { "-background", 1 },
  { "-fg", 1 },
  { "-foreground", 1 },
  { "-fn", 1 },
  { "-font", 1 },
  { "-iconic", 0 },
  { "-name", 1 },
  { "-rv", 0 },
  { "-reverse", 0 },
  { "+rv", 0 },
  { "-selectionTimeout", 1 },
  { "-synchronous", 0 },
  { "-title", 1 },
  { "-xnllanguage", 1 },
  { "-xrm", 1 },
  { SINGLE_INSTANCE, 0},
  { NULL, 0 }
};

static int filter_x_readable(char **argv, int argc)
  XFORM_SKIP_PROC
{
  int pos = 1, i;

  while (pos < argc) {
    for (i = 0; X_flags[i].flag; i++) {
      if (!strcmp(X_flags[i].flag, argv[pos]))
	break;
    }

    if (!X_flags[i].flag)
      return pos;
    else {
      int newpos = pos + X_flags[i].arg_count + 1;
      if (newpos > argc) {
	printf("%s: X Window System flag \"%s\" expects %d arguments, %d provided\n",
	       argv[0], argv[pos], X_flags[i].arg_count, argc - pos - 1);
	exit(-1);
      }
      pos = newpos;
    }
  }

  return pos;
}

static void pre_filter_cmdline_arguments(int *argc, char ***argv)
  XFORM_SKIP_PROC
{
  int pos;
  char **naya;

  pos = filter_x_readable(*argv, *argc);
  if (pos > 1) {
    scheme_register_process_global("PLT_X11_ARGUMENT_COUNT", (void *)(intptr_t)pos);
    scheme_register_process_global("PLT_X11_ARGUMENTS", *argv);
    naya = malloc((*argc - (pos - 1)) * sizeof(char *));
    memcpy(naya, *argv + (pos - 1), (*argc - (pos - 1)) * sizeof(char *));
    naya[0] = (*argv)[0];
    *argv = naya;
    *argc -= (pos - 1);
  }
}

#endif

/***********************************************************************/
/*                   Mac OS X flag handling                            */
/***********************************************************************/

#ifdef wx_mac

static void pre_filter_cmdline_arguments(int *argc, char ***argv)
  XFORM_SKIP_PROC
{
  if ((*argc > 1) && !strncmp((*argv)[1], "-psn_", 5)) {
    /* Finder adds "-psn_" when you double-click on the application.
       Drop it. */
    char **new_argv;
    new_argv = (char **)malloc(((*argc) - 1) * sizeof(char *));
    new_argv[0] = (*argv)[0];
    memcpy(new_argv + 1, (*argv) + 2, ((*argc) - 2) * sizeof(char *));
    (*argc)--;
    *argv = new_argv;
  }
  scheme_register_process_global("PLT_IS_FOREGROUND_APP", (void *)(intptr_t)0x1);
}

#endif

