// Copyright 2012 The Go Authors.  All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// These #ifdefs are being used as a substitute for
// build configuration, so that on any system, this
// tool can be built with the local equivalent of
//	cc *.c
//
#ifdef WIN32

// Portability layer implemented for Windows.
// See unix.c for doc comments about exported functions.

#include "a.h"
#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>

/*
 * Windows uses 16-bit rune strings in the APIs.
 * Define conversions between Rune* and UTF-8 char*.
 */

typedef unsigned char uchar;
typedef unsigned short Rune;  // same as Windows

// encoderune encodes the rune r into buf and returns
// the number of bytes used.
static int
encoderune(char *buf, Rune r)
{
	if(r < 0x80) {  // 7 bits
		buf[0] = r;
		return 1;
	}
	if(r < 0x800) {  // 5+6 bits
		buf[0] = 0xc0 | (r>>6);
		buf[1] = 0x80 | (r&0x3f);
		return 2;
	}
	buf[0] = 0xe0 | (r>>12);
	buf[1] = 0x80 | ((r>>6)&0x3f);
	buf[2] = 0x80 | (r&0x3f);
	return 3;
}

// decoderune decodes the rune encoding at sbuf into r
// and returns the number of bytes used.
static int
decoderune(Rune *r, char *sbuf)
{
	uchar *buf;

	buf = (uchar*)sbuf;
	if(buf[0] < 0x80) {
		*r = buf[0];
		return 1;
	}
	if((buf[0]&0xe0) == 0xc0 && (buf[1]&0xc0) == 0x80) {
		*r = (buf[0]&~0xc0)<<6 | (buf[1]&~0x80);
		if(*r < 0x80)
			goto err;
		return 2;
	}
	if((buf[0]&0xf0) == 0xe0 && (buf[1]&0xc0) == 0x80 && (buf[2]&0xc0) == 0x80) {
		*r = (buf[0]&~0xc0)<<12 | (buf[1]&~0x80)<<6 | (buf[2]&~0x80);
		if(*r < 0x800)
			goto err;
		return 3;
	}
err:
	*r = 0xfffd;
	return 1;
}

// toutf replaces b with the UTF-8 encoding of the rune string r.	
static void
toutf(Buf *b, Rune *r)
{
	int i, n;
	char buf[4];

	breset(b);
	for(i=0; r[i]; i++) {
		n = encoderune(buf, r[i]);
		bwrite(b, buf, n);
	}
}

// torune replaces *rp with a pointer to a newly allocated
// rune string equivalent of the UTF-8 string p.
static void
torune(Rune **rp, char *p)
{
	Rune *r, *w;

	r = xmalloc((strlen(p)+1) * sizeof r[0]);
	w = r;
	while(*p)
		p += decoderune(w++, p);
	*w = 0;
	*rp = r;
}

// errstr returns the most recent Windows error, in string form.
static char*
errstr(void)
{
	DWORD code;
	Rune *r;
	Buf b;

	binit(&b);
	code = GetLastError();
	r = nil;
	FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER|FORMAT_MESSAGE_FROM_SYSTEM,
		nil, code, 0, (Rune*)&r, 0, nil);
	toutf(&b, r);
	return bstr(&b);  // leak but we're dying anyway
}

void
xgetenv(Buf *b, char *name)
{
	Rune *buf;
	int n;
	Rune *r;

	breset(b);
	torune(&r, name);
	n = GetEnvironmentVariableW(r, NULL, 0);
	if(n > 0) {
		buf = xmalloc((n+1)*sizeof buf[0]);
		GetEnvironmentVariableW(r, buf, n+1);
		buf[n] = '\0';
		toutf(b, buf);
		xfree(buf);
	}
	xfree(r);
}

void
xsetenv(char *name, char *value)
{
	Rune *rname, *rvalue;

	torune(&rname, name);
	torune(&rvalue, value);
	SetEnvironmentVariableW(rname, rvalue);
	xfree(rname);
	xfree(rvalue);
}

char*
bprintf(Buf *b, char *fmt, ...)
{
	va_list arg;
	char buf[4096];
	
	breset(b);
	va_start(arg, fmt);
	vsnprintf(buf, sizeof buf, fmt, arg);
	va_end(arg);
	bwritestr(b, buf);
	return bstr(b);
}

void
bwritef(Buf *b, char *fmt, ...)
{
	va_list arg;
	char buf[4096];
	
	// no reset
	va_start(arg, fmt);
	vsnprintf(buf, sizeof buf, fmt, arg);
	va_end(arg);
	bwritestr(b, buf);
}

// bpathf is like bprintf but replaces / with \ in the result,
// to make it a canonical windows file path.
char*
bpathf(Buf *b, char *fmt, ...)
{
	int i;
	va_list arg;
	char buf[4096];
	
	breset(b);
	va_start(arg, fmt);
	vsnprintf(buf, sizeof buf, fmt, arg);
	va_end(arg);
	bwritestr(b, buf);

	for(i=0; i<b->len; i++)
		if(b->p[i] == '/')
			b->p[i] = '\\';

	return bstr(b);
}


static void
breadfrom(Buf *b, HANDLE h)
{
	DWORD n;

	for(;;) {
		if(b->len > 1<<22)
			fatal("unlikely file size in readfrom");
		bgrow(b, 4096);
		n = 0;
		if(!ReadFile(h, b->p+b->len, 4096, &n, nil)) {
			// Happens for pipe reads.
			break;
		}
		if(n == 0)
			break;
		b->len += n;
	}
}

void
run(Buf *b, char *dir, int mode, char *cmd, ...)
{
	va_list arg;
	Vec argv;
	char *p;
	
	vinit(&argv);
	vadd(&argv, cmd);
	va_start(arg, cmd);
	while((p = va_arg(arg, char*)) != nil)
		vadd(&argv, p);
	va_end(arg);
	
	runv(b, dir, mode, &argv);
	
	vfree(&argv);
}

static void genrun(Buf*, char*, int, Vec*, int);

void
runv(Buf *b, char *dir, int mode, Vec *argv)
{
	genrun(b, dir, mode, argv, 1);
}

void
bgrunv(char *dir, int mode, Vec *argv)
{
	genrun(nil, dir, mode, argv, 0);
}

#define MAXBG 4 /* maximum number of jobs to run at once */

static struct {
	PROCESS_INFORMATION pi;
	int mode;
	char *cmd;
} bg[MAXBG];

static int nbg;

static void bgwait1(void);

static void
genrun(Buf *b, char *dir, int mode, Vec *argv, int wait)
{
	int i, j, nslash;
	Buf cmd;
	char *q;
	Rune *rcmd, *rexe, *rdir;
	STARTUPINFOW si;
	PROCESS_INFORMATION pi;
	HANDLE p[2];

	while(nbg >= nelem(bg))
		bgwait1();

	binit(&cmd);

	for(i=0; i<argv->len; i++) {
		if(i > 0)
			bwritestr(&cmd, " ");
		q = argv->p[i];
		if(contains(q, " ") || contains(q, "\t") || contains(q, "\"") || contains(q, "\\\\") || hassuffix(q, "\\")) {
			bwritestr(&cmd, "\"");
			nslash = 0;
			for(; *q; q++) {
				if(*q == '\\') {
					nslash++;
					continue;
				}
				if(*q == '"') {
					for(j=0; j<2*nslash+1; j++)
						bwritestr(&cmd, "\\");
					nslash = 0;
				}
				for(j=0; j<nslash; j++)
					bwritestr(&cmd, "\\");
				nslash = 0;
				bwrite(&cmd, q, 1);
			}
			for(j=0; j<2*nslash; j++)
				bwritestr(&cmd, "\\");
			bwritestr(&cmd, "\"");
		} else {
			bwritestr(&cmd, q);
		}
	}
	if(vflag > 1)
		xprintf("%s\n", bstr(&cmd));

	torune(&rcmd, bstr(&cmd));
	rexe = nil;
	rdir = nil;
	if(dir != nil)
		torune(&rdir, dir);

	memset(&si, 0, sizeof si);
	si.cb = sizeof si;
	si.dwFlags = STARTF_USESTDHANDLES;
	si.hStdInput = INVALID_HANDLE_VALUE;
	if(b == nil) {
		si.hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE);
		si.hStdError = GetStdHandle(STD_ERROR_HANDLE);
	} else {
		SECURITY_ATTRIBUTES seci;

		memset(&seci, 0, sizeof seci);
		seci.nLength = sizeof seci;
		seci.bInheritHandle = 1;
		breset(b);
		if(!CreatePipe(&p[0], &p[1], &seci, 0))
			fatal("CreatePipe: %s", errstr());
		si.hStdOutput = p[1];
		si.hStdError = p[1];
	}

	if(!CreateProcessW(rexe, rcmd, nil, nil, TRUE, 0, nil, rdir, &si, &pi)) {
		if(mode!=CheckExit)
			return;
		fatal("%s: %s", argv->p[0], errstr());
	}
	if(rexe != nil)
		xfree(rexe);
	xfree(rcmd);
	if(rdir != nil)
		xfree(rdir);
	if(b != nil) {
		CloseHandle(p[1]);
		breadfrom(b, p[0]);
		CloseHandle(p[0]);
	}

	if(nbg < 0)
		fatal("bad bookkeeping");
	bg[nbg].pi = pi;
	bg[nbg].mode = mode;
	bg[nbg].cmd = btake(&cmd);
	nbg++;

	if(wait)
		bgwait();

	bfree(&cmd);
}

// closes the background job for bgwait1
static void
bgwaitclose(int i)
{
	if(i < 0 || i >= nbg)
		return;

	CloseHandle(bg[i].pi.hProcess);
	CloseHandle(bg[i].pi.hThread);
	
	bg[i] = bg[--nbg];
}

// bgwait1 waits for a single background job
static void
bgwait1(void)
{
	int i, mode;
	char *cmd;
	HANDLE bgh[MAXBG];
	DWORD code;

	if(nbg == 0)
		fatal("bgwait1: nothing left");

	for(i=0; i<nbg; i++)
		bgh[i] = bg[i].pi.hProcess;
	i = WaitForMultipleObjects(nbg, bgh, FALSE, INFINITE);
	if(i < 0 || i >= nbg)
		fatal("WaitForMultipleObjects: %s", errstr());

	cmd = bg[i].cmd;
	mode = bg[i].mode;
	if(!GetExitCodeProcess(bg[i].pi.hProcess, &code)) {
		bgwaitclose(i);
		fatal("GetExitCodeProcess: %s", errstr());
		return;
	}

	if(mode==CheckExit && code != 0) {
		bgwaitclose(i);
		fatal("FAILED: %s", cmd);
		return;
	}

	bgwaitclose(i);
}

void
bgwait(void)
{
	while(nbg > 0)
		bgwait1();
}

// rgetwd returns a rune string form of the current directory's path.
static Rune*
rgetwd(void)
{
	int n;
	Rune *r;

	n = GetCurrentDirectoryW(0, nil);
	r = xmalloc((n+1)*sizeof r[0]);
	GetCurrentDirectoryW(n+1, r);
	r[n] = '\0';
	return r;
}

void
xgetwd(Buf *b)
{
	Rune *r;

	r = rgetwd();
	breset(b);
	toutf(b, r);
	xfree(r);
}

void
xrealwd(Buf *b, char *path)
{
	Rune *old;
	Rune *rnew;

	old = rgetwd();
	torune(&rnew, path);
	if(!SetCurrentDirectoryW(rnew))
		fatal("chdir %s: %s", path, errstr());
	free(rnew);
	xgetwd(b);
	if(!SetCurrentDirectoryW(old)) {
		breset(b);
		toutf(b, old);
		fatal("chdir %s: %s", bstr(b), errstr());
	}
}

bool
isdir(char *p)
{
	DWORD attr;
	Rune *r;

	torune(&r, p);
	attr = GetFileAttributesW(r);
	xfree(r);
	return attr != INVALID_FILE_ATTRIBUTES && (attr & FILE_ATTRIBUTE_DIRECTORY);
}

bool
isfile(char *p)
{
	DWORD attr;
	Rune *r;

	torune(&r, p);
	attr = GetFileAttributesW(r);
	xfree(r);
	return attr != INVALID_FILE_ATTRIBUTES && !(attr & FILE_ATTRIBUTE_DIRECTORY);
}

Time
mtime(char *p)
{
	HANDLE h;
	WIN32_FIND_DATAW data;
	Rune *r;
	FILETIME *ft;

	torune(&r, p);
	h = FindFirstFileW(r, &data);
	xfree(r);
	if(h == INVALID_HANDLE_VALUE)
		return 0;
	FindClose(h);
	ft = &data.ftLastWriteTime;
	return (Time)ft->dwLowDateTime + ((Time)ft->dwHighDateTime<<32);
}

bool
isabs(char *p)
{
	// c:/ or c:\ at beginning
	if(('A' <= p[0] && p[0] <= 'Z') || ('a' <= p[0] && p[0] <= 'z'))
		return p[1] == ':' && (p[2] == '/' || p[2] == '\\');
	// / or \ at beginning
	return p[0] == '/' || p[0] == '\\';
}

void
readfile(Buf *b, char *file)
{
	HANDLE h;
	Rune *r;

	if(vflag > 2)
		xprintf("read %s\n", file);
	torune(&r, file);
	h = CreateFileW(r, GENERIC_READ, FILE_SHARE_READ|FILE_SHARE_WRITE, nil, OPEN_EXISTING, 0, 0);
	if(h == INVALID_HANDLE_VALUE)
		fatal("open %s: %s", file, errstr());
	breadfrom(b, h);
	CloseHandle(h);
}

void
writefile(Buf *b, char *file, int exec)
{
	HANDLE h;
	Rune *r;
	DWORD n;

	USED(exec);

	if(vflag > 2)
		xprintf("write %s\n", file);
	torune(&r, file);
	h = CreateFileW(r, GENERIC_WRITE, FILE_SHARE_READ|FILE_SHARE_WRITE, nil, CREATE_ALWAYS, 0, 0);
	if(h == INVALID_HANDLE_VALUE)
		fatal("create %s: %s", file, errstr());
	n = 0;
	if(!WriteFile(h, b->p, b->len, &n, 0))
		fatal("write %s: %s", file, errstr());
	CloseHandle(h);
}
	

void
xmkdir(char *p)
{
	Rune *r;

	torune(&r, p);
	if(!CreateDirectoryW(r, nil))
		fatal("mkdir %s: %s", p, errstr());
	xfree(r);
}

void
xmkdirall(char *p)
{
	int c;
	char *q, *q2;
	
	if(isdir(p))
		return;
	q = strrchr(p, '/');
	q2 = strrchr(p, '\\');
	if(q2 != nil && (q == nil || q < q2))
		q = q2;
	if(q != nil) {
		c = *q;
		*q = '\0';
		xmkdirall(p);
		*q = c;
	}
	xmkdir(p);
}

void
xremove(char *p)
{
	int attr;
	Rune *r;

	torune(&r, p);
	attr = GetFileAttributesW(r);
	if(attr >= 0) {
		if(attr & FILE_ATTRIBUTE_DIRECTORY)
			RemoveDirectoryW(r);
		else
			DeleteFileW(r);
	}
	xfree(r);
}

void
xreaddir(Vec *dst, char *dir)
{
	Rune *r;
	Buf b;
	HANDLE h;
	WIN32_FIND_DATAW data;
	char *p, *q;

	binit(&b);
	vreset(dst);

	bwritestr(&b, dir);
	bwritestr(&b, "\\*");
	torune(&r, bstr(&b));

	h = FindFirstFileW(r, &data);
	xfree(r);
	if(h == INVALID_HANDLE_VALUE)
		goto out;
	do{
		toutf(&b, data.cFileName);
		p = bstr(&b);
		q = xstrrchr(p, '\\');
		if(q != nil)
			p = q+1;
		if(!streq(p, ".") && !streq(p, ".."))
			vadd(dst, p);
	}while(FindNextFileW(h, &data));
	FindClose(h);

out:
	bfree(&b);
}

char*
xworkdir(void)
{
	Rune buf[1024];
	Rune tmp[MAX_PATH];
	Rune go[3] = {'g', 'o', '\0'};
	int n;
	Buf b;

	n = GetTempPathW(nelem(buf), buf);
	if(n <= 0)
		fatal("GetTempPath: %s", errstr());
	buf[n] = '\0';

	if(GetTempFileNameW(buf, go, 0, tmp) == 0)
		fatal("GetTempFileName: %s", errstr());
	DeleteFileW(tmp);
	if(!CreateDirectoryW(tmp, nil))
		fatal("create tempdir: %s", errstr());
	
	binit(&b);
	toutf(&b, tmp);
	return btake(&b);
}

void
xremoveall(char *p)
{
	int i;
	Buf b;
	Vec dir;
	Rune *r;

	binit(&b);
	vinit(&dir);

	torune(&r, p);
	if(isdir(p)) {
		xreaddir(&dir, p);
		for(i=0; i<dir.len; i++) {
			bprintf(&b, "%s/%s", p, dir.p[i]);
			xremoveall(bstr(&b));
		}
		RemoveDirectoryW(r);
	} else {
		DeleteFileW(r);
	}
	xfree(r);
	
	bfree(&b);
	vfree(&dir);	
}

void
fatal(char *msg, ...)
{
	static char buf1[1024];
	va_list arg;

	va_start(arg, msg);
	vsnprintf(buf1, sizeof buf1, msg, arg);
	va_end(arg);

	xprintf("go tool dist: %s\n", buf1);
	
	bgwait();
	ExitProcess(1);
}

// HEAP is the persistent handle to the default process heap.
static HANDLE HEAP = INVALID_HANDLE_VALUE;

void*
xmalloc(int n)
{
	void *p;

	if(HEAP == INVALID_HANDLE_VALUE)
		HEAP = GetProcessHeap();
	p = HeapAlloc(HEAP, 0, n);
	if(p == nil)
		fatal("out of memory allocating %d: %s", n, errstr());
	memset(p, 0, n);
	return p;
}

char*
xstrdup(char *p)
{
	char *q;

	q = xmalloc(strlen(p)+1);
	strcpy(q, p);
	return q;
}

void
xfree(void *p)
{
	if(HEAP == INVALID_HANDLE_VALUE)
		HEAP = GetProcessHeap();
	HeapFree(HEAP, 0, p);
}

void*
xrealloc(void *p, int n)
{
	if(p == nil)
		return xmalloc(n);
	if(HEAP == INVALID_HANDLE_VALUE)
		HEAP = GetProcessHeap();
	p = HeapReAlloc(HEAP, 0, p, n);
	if(p == nil)
		fatal("out of memory reallocating %d", n);
	return p;
}

bool
hassuffix(char *p, char *suffix)
{
	int np, ns;
	
	np = strlen(p);
	ns = strlen(suffix);
	return np >= ns && strcmp(p+np-ns, suffix) == 0;
}

bool
hasprefix(char *p, char *prefix)
{
	return strncmp(p, prefix, strlen(prefix)) == 0;
}

bool
contains(char *p, char *sep)
{
	return strstr(p, sep) != nil;
}

bool
streq(char *p, char *q)
{
	return strcmp(p, q) == 0;
}

char*
lastelem(char *p)
{
	char *out;

	out = p;
	for(; *p; p++)
		if(*p == '/' || *p == '\\')
			out = p+1;
	return out;
}

void
xmemmove(void *dst, void *src, int n)
{
	memmove(dst, src, n);
}

int
xmemcmp(void *a, void *b, int n)
{
	return memcmp(a, b, n);
}

int
xstrlen(char *p)
{
	return strlen(p);
}

void
xexit(int n)
{
	ExitProcess(n);
}

void
xatexit(void (*f)(void))
{
	atexit(f);
}

void
xprintf(char *fmt, ...)
{
	va_list arg;
	char *p;
	DWORD n, w;

	va_start(arg, fmt);
	n = vsnprintf(NULL, 0, fmt, arg);
	p = xmalloc(n+1);
	vsnprintf(p, n+1, fmt, arg);
	va_end(arg);
	w = 0;
	WriteFile(GetStdHandle(STD_OUTPUT_HANDLE), p, n, &w, 0);
	xfree(p);
}

int
main(int argc, char **argv)
{
	SYSTEM_INFO si;

	setvbuf(stdout, nil, _IOLBF, 0);
	setvbuf(stderr, nil, _IOLBF, 0);

	slash = "\\";
	gohostos = "windows";

	GetSystemInfo(&si);
	switch(si.wProcessorArchitecture) {
	case PROCESSOR_ARCHITECTURE_AMD64:
		gohostarch = "amd64";
		break;
	case PROCESSOR_ARCHITECTURE_INTEL:
		gohostarch = "386";
		break;
	default:
		fatal("unknown processor architecture");
	}

	init();

	xmain(argc, argv);
	return 0;
}

void
xqsort(void *data, int n, int elemsize, int (*cmp)(const void*, const void*))
{
	qsort(data, n, elemsize, cmp);
}

int
xstrcmp(char *a, char *b)
{
	return strcmp(a, b);
}

char*
xstrstr(char *a, char *b)
{
	return strstr(a, b);
}

char*
xstrrchr(char *p, int c)
{
	char *ep;
	
	ep = p+strlen(p);
	for(ep=p+strlen(p); ep >= p; ep--)
		if(*ep == c)
			return ep;
	return nil;
}

#endif // __WINDOWS__
