/*
 * %CopyrightBegin%
 * 
 * Copyright Ericsson AB 1997-2011. All Rights Reserved.
 * 
 * The contents of this file are subject to the Erlang Public License,
 * Version 1.1, (the "License"); you may not use this file except in
 * compliance with the License. You should have received a copy of the
 * Erlang Public License along with this software. If not, it can be
 * retrieved online at http://www.erlang.org/.
 * 
 * Software distributed under the License is distributed on an "AS IS"
 * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
 * the License for the specific language governing rights and limitations
 * under the License.
 * 
 * %CopyrightEnd%
 */
/*
 * Extra support for running the emulator on Windows.
 * Most of this only used when beam is run as a separate process.
 */

#pragma comment(linker,"/manifestdependency:\"type='win32' "\
		"name='Microsoft.Windows.Common-Controls' "\
		"version='6.0.0.0' processorArchitecture='*' "\
		"publicKeyToken='6595b64144ccf1df' language='*'\"")

#include <windows.h>
#include <winuser.h>
#include <wincon.h>
#include <process.h>
#ifdef HAVE_CONFIG_H
#  include "config.h"
#endif
#include "sys.h"
#include "erl_driver.h"

extern int nohup;
extern int keep_window;
void error(char* format, ...);

/*
 * Local functions.
 */
#define  LOAD_BEAM_DYNAMICALLY 1
static int start(char* emu, char** argv);
static void start_winsock(void);
static char* last_error(void);
static char* last_wsa_error(void);
static char* win32_errorstr(int error);
static int has_console(void);
static char** fnuttify_argv(char **argv);
static void free_fnuttified(char **v);
static int windowed = 0;

#ifdef LOAD_BEAM_DYNAMICALLY
typedef int SysGetKeyFunction(int);
typedef void ErlStartFunction(int, char **);
typedef void SysPrimitiveInitFunction(HMODULE);
static SysGetKeyFunction *sys_get_key_p;
static ErlStartFunction *erl_start_p;
static SysPrimitiveInitFunction *sys_primitive_init_p;

static HMODULE load_win_beam_dll(char *name)
{
    HMODULE beam_module;
    beam_module=LoadLibrary(name);
    if (beam_module == INVALID_HANDLE_VALUE || beam_module == NULL) {
	error("Unable to load emulator DLL\n(%s)",name);
	return NULL;
    }
    sys_get_key_p = (SysGetKeyFunction *) 
	GetProcAddress(beam_module, "sys_get_key");
    erl_start_p = (ErlStartFunction *)
	GetProcAddress(beam_module, "erl_start");
    sys_primitive_init_p = (SysPrimitiveInitFunction *)
	GetProcAddress(beam_module, "sys_primitive_init");
    return beam_module;
}
#endif

#define DLL_ENV "ERL_EMULATOR_DLL"

static void
set_env(char *key, char *value)
{
    if (!SetEnvironmentVariable((LPCTSTR) key, (LPCTSTR) value))
	error("SetEnvironmentVariable(\"%s\", \"%s\") failed!", key, value);
}

static char *
get_env(char *key)
{
    DWORD size = 32;
    char *value = NULL;
    while (1) {
	DWORD nsz;
	if (value)
	    free(value);
	value = malloc(size);
	if (!value)
	    error("GetEnvironmentVariable(\"%s\") failed", key);
	SetLastError(0);
	nsz = GetEnvironmentVariable((LPCTSTR) key, (LPTSTR) value, size);
	if (nsz == 0 && GetLastError() == ERROR_ENVVAR_NOT_FOUND) {
	    free(value);
	    return NULL;
	}
	if (nsz <= size)
	    return value;
	size = nsz;
    }
}

free_env_val(char *value)
{
    if (value)
	free(value);
}


int
start_win_emulator(char* emu, char *start_prog, char** argv, int start_detached)
{
    int result;

    windowed = 1;
    if (start_detached) {
	char *buff;
	close(0);
	close(1);
	close(2);
	
	set_env("ERL_CONSOLE_MODE", "detached");
	set_env(DLL_ENV, emu);

	argv[0] = start_prog;
	argv = fnuttify_argv(argv);
	result = spawnv(_P_DETACH, start_prog, argv);
	free_fnuttified(argv);
    } else {
	int argc = 0;
#ifdef LOAD_BEAM_DYNAMICALLY
	HMODULE beam_module = load_win_beam_dll(emu);
#endif	
	set_env("ERL_CONSOLE_MODE", "window");
	while (argv[argc] != NULL) {
	    ++argc;
	}
#ifdef ARGS_HARDDEBUG
	{
	    char sbuf[2048] = "";
	    int i;
	    for (i = 0; i < argc; ++i) {
		strcat(sbuf,"|");
		strcat(sbuf, argv[i]);
		strcat(sbuf,"| ");
	    }
	    MessageBox(NULL, sbuf, "Werl", MB_OK|MB_ICONERROR);
	}
#endif
#ifdef LOAD_BEAM_DYNAMICALLY
	(*sys_primitive_init_p)(beam_module);
	(*erl_start_p)(argc,argv);
#else
	erl_start(argc, argv);
#endif
	result = 0;
    }
    if (result == -1) {
	error("Failed to execute %s: %s", emu, win32_errorstr(_doserrno));
    }
    return 0;
}

void __cdecl 
do_keep_window(void)
{
    printf("\nPress any key to close window.\n");
#ifdef LOAD_BEAM_DYNAMICALLY
    (*sys_get_key_p)(0);
#else 
    sys_get_key(0);
#endif 
}

int
start_emulator(char* emu, char *start_prog, char** argv, int start_detached)
{
    int result;
    static char console_mode[] = "tty:ccc";
    char* fd_type;
    char* title;

#ifdef HARDDEBUG
    fprintf(stderr,"emu = %s, start_prog = %s\n",emu, start_prog);
#endif

    fd_type = strchr(console_mode, ':');
    fd_type++;
    _flushall();
    
    /*
     * If no console, we will spawn the emulator detached.
     */

    if (start_detached) {
	char *buff;
	close(0);
	close(1);
	close(2);
	set_env("ERL_CONSOLE_MODE", "detached");
	set_env(DLL_ENV, emu);

	argv[0] = start_prog;
	argv = fnuttify_argv(argv);
#ifdef ARGS_HARDDEBUG
	{
	    char buffer[2048];
	    int i;
	    sprintf(buffer,"Start detached [%s]\n",start_prog);
	    for(i=0;argv[i] != NULL;++i) {
		strcat(buffer,"|");
		strcat(buffer,argv[i]);
		strcat(buffer,"|\n");
	    }
	    MessageBox(NULL, buffer,"Start detached",MB_OK);
	}
#endif	    
	result = spawnv(_P_DETACH, start_prog, argv);
	free_fnuttified(argv);
	if (result == -1) {
#ifdef ARGS_HARDDEBUG
	    MessageBox(NULL, "_spawnv failed","Start detached",MB_OK);
#endif
	    return 1;
	}
	SetPriorityClass((HANDLE) result, GetPriorityClass(GetCurrentProcess()));
    } else {
	int argc = 0;
#ifdef LOAD_BEAM_DYNAMICALLY
	HMODULE beam_module = load_win_beam_dll(emu);
#endif	

	/*
	 * Start the emulator.
	 */

	title = get_env("ERL_WINDOW_TITLE");
	if (title) {
	    SetConsoleTitle(title);
	}
	free_env_val(title);
	
	set_env("ERL_CONSOLE_MODE", console_mode);
	while (argv[argc] != NULL) {
	    ++argc;
	}
	if (keep_window) {
	    atexit(do_keep_window);
	}
#ifdef ARGS_HARDDEBUG
	{
	    char sbuf[2048] = "";
	    int i;
	    for (i = 0; i < argc; ++i) {
		strcat(sbuf,"|");
		strcat(sbuf, argv[i]);
		strcat(sbuf,"|\n");
	    }
	    MessageBox(NULL, sbuf, "erl", MB_OK);
	}
#endif
#ifdef LOAD_BEAM_DYNAMICALLY
	(*sys_primitive_init_p)(beam_module);
	(*erl_start_p)(argc,argv);
#else
	erl_start(argc, argv);
#endif
    }
    return 0;
}

void
error(char* format, ...)
{
    char sbuf[2048];
    va_list ap;

    va_start(ap, format);
    vsprintf(sbuf, format, ap);
    va_end(ap);

    if (!windowed && has_console()) {
	fprintf(stderr, "%s\n", sbuf);
    } else {
	MessageBox(NULL, sbuf, "Werl", MB_OK|MB_ICONERROR);
    }
    exit(1);
}

static char*
last_error(void)
{
    return win32_errorstr(GetLastError());
}

/*
 * Returns a human-readable description of the last error.
 * The returned pointer will be valid only as long as last-error()
 * isn't called again.
 */

static char*
win32_errorstr(int error)
{
    static LPTSTR lpBufPtr = NULL;

    if (lpBufPtr)
	LocalFree(lpBufPtr);
    FormatMessage(
		  FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
		  NULL,
		  error,
		  MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
		  (LPTSTR) &lpBufPtr,
		  0,
		  NULL);
    SetLastError(error);
    return lpBufPtr;
}

static int
has_console(void)
{
    HANDLE handle = CreateFile("CONOUT$", GENERIC_WRITE, FILE_SHARE_WRITE,
			       NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);

    if (handle != INVALID_HANDLE_VALUE) {
        CloseHandle(handle);
	return 1;
    } else {
        return 0;
    }
}

static char** fnuttify_argv(char **argv)
{
    char **v;
    char *p;
    char *q;
    int c;
    int i;
    int n;
    int m;

    for (c = 0; argv[c]; ++c)
	;

    v = malloc(sizeof(char *) * (c+1));
    v[c] = NULL;
    for (i = 0; i < c; ++i) {
	p = argv[i];
	n = m = 0;
	while (*p) {
	    if (*p == ' ') {
		m = 2;
	    } else if (*p == '"') {
		m = 2;
		++n;
	    }
	    ++p;
	}
	v[i] = malloc((p - argv[i]) + 1 + n + m);
	p = argv[i];
	q = v[i];
	if (n || m) {
	    if (m) {
		*q++ = '"';
	    }
	    while (*p) {
		if (*p == '"') {
		    *q++ = '\\';
		}
		*q++ = *p++;
	    }
	    if (m) {
		*q++ = '"';
	    }
	    *q = '\0';
	} else {
	    strcpy(q,p);
	}
    }
    return v;
}

static void free_fnuttified(char **v)
{
    char **t = v;

    while(*t) {
	free(*t);
	++t;
    }
    free(v);
}