/*
 *  platform-specific interface
 */

#define Unknown win_Unknown
#define UNICODE
#include	<windows.h>
#include <winbase.h>
#include	<winsock.h>
#undef Unknown
#include	"dat.h"
#include	"fns.h"
#include	"error.h"
#include	"r16.h"

enum{
	Qdir,
	Qarchctl,
	Qcputype,
	Qregquery,
	Qhostmem
};

static
Dirtab archtab[]={
	".",		{Qdir, 0, QTDIR},	0,	0555,
	"archctl",	{Qarchctl, 0},	0,	0444,
	"cputype",	{Qcputype},	0,	0444,
	"regquery",	{Qregquery}, 0,	0666,
	"hostmem",	{Qhostmem},	0,	0444,
};

typedef struct Value Value;
struct Value {
	int	type;
	int	size;
	union {
		ulong	w;
		vlong	q;
		char	data[1];	/* utf-8 */
	};
};

typedef struct Regroot Regroot;
struct Regroot {
	char*	name;
	HKEY	root;
};

static Regroot roots[] = {
	{"HKEY_CLASSES_ROOT", HKEY_CLASSES_ROOT},
	{"HKEY_CURRENT_CONFIG", HKEY_CURRENT_CONFIG},
	{"HKEY_CURRENT_USER", HKEY_CURRENT_USER},
	{"HKEY_LOCAL_MACHINE", HKEY_LOCAL_MACHINE},
	{"HKEY_PERFORMANCE_DATA", HKEY_PERFORMANCE_DATA},
	{"HKEY_USERS", HKEY_USERS},
};

static struct {
	ulong	mhz;
	int ncpu;
	char	cpu[64];
} arch;

static	QLock	reglock;

static	Value*	getregistry(HKEY, Rune16*, Rune16*);
static int nprocs(void);

static void
archinit(void)
{
	Value *v;
	char *p;

	v = getregistry(HKEY_LOCAL_MACHINE, L"HARDWARE\\DESCRIPTION\\System\\CentralProcessor\\0", L"ProcessorNameString");
	if(v != nil){
		snprint(arch.cpu, sizeof(arch.cpu), "%s", v->data);
		if((p = strrchr(arch.cpu, ' ')) != nil)
			for(; p >= arch.cpu && *p == ' '; p--)
				*p = '\0';
		free(v);
	}else{
		v = getregistry(HKEY_LOCAL_MACHINE, L"HARDWARE\\DESCRIPTION\\System\\CentralProcessor\\0", L"VendorIdentifier");
		if(v != nil){
			snprint(arch.cpu, sizeof(arch.cpu), "%s", v->data);
			free(v);
		}else
			snprint(arch.cpu, sizeof(arch.cpu), "unknown");
	}
	v = getregistry(HKEY_LOCAL_MACHINE, L"HARDWARE\\DESCRIPTION\\System\\CentralProcessor\\0", L"~MHz");
	if(v != nil){
		arch.mhz = v->w;
		free(v);
	}
	arch.ncpu = nprocs();
}

static int
nprocs(void)
{
	int n;
	char *p;
	Rune16 *r;
	Value *v;
	n = 0;
	for(;;){
		p = smprint("HARDWARE\\DESCRIPTION\\System\\CentralProcessor\\%d", n);
		if(waserror()){
			free(p);
			nexterror();
		}
		r = widen(p);
		free(p);
		v = getregistry(HKEY_LOCAL_MACHINE, r, L"~MHz");
		free(r);
		if(v == nil)
			break;
		free(v);
		n++;
	}
	return n;
}

static Chan*
archattach(char* spec)
{
	return devattach('a', spec);
}

static Walkqid*
archwalk(Chan *c, Chan *nc, char **name, int nname)
{
	return devwalk(c, nc, name, nname, archtab, nelem(archtab), devgen);
}

static int
archstat(Chan* c, uchar *db, int n)
{
	return devstat(c, db, n, archtab, nelem(archtab), devgen);
}

static Chan*
archopen(Chan* c, int omode)
{
	return devopen(c, omode, archtab, nelem(archtab), devgen);
}

static void
archclose(Chan* c)
{
	if((ulong)c->qid.path == Qregquery && c->aux != nil)
		free(c->aux);
}

static long
archread(Chan* c, void* a, long n, vlong offset)
{
	char *p;
	Value *v;
	int i, l;
	MEMORYSTATUS mem;

	switch((ulong)c->qid.path){
	case Qdir:
		return devdirread(c, a, n, archtab, nelem(archtab), devgen);
	case Qarchctl:
	case Qcputype:
		l = 0;
		if((ulong)c->qid.path == Qcputype)
			l = 4;
		p = smalloc(READSTR);
		if(waserror()){
			free(p);
			nexterror();
		}
		snprint(p, READSTR, "cpu %q %lud %d\n", arch.cpu, arch.mhz, arch.ncpu);
		n = readstr(offset, a, n, p+l);
		poperror();
		free(p);
		break;
	case Qregquery:
		v = c->aux;
		if(v == nil)
			return 0;
		p = smalloc(READSTR);
		if(waserror()){
			free(p);
			nexterror();
		}
		switch(v->type){
		case REG_NONE:
			n = readstr(offset, a, n, "nil");
			break;
		case REG_DWORD:
			snprint(p, READSTR, "int %ld", v->w);
			n = readstr(offset, a, n, p);
			break;
#ifdef REG_QWORD
		case REG_QWORD:
			snprint(p, READSTR, "int %lld", v->q);
			n = readstr(offset, a, n, p);
			break;
#endif
		case REG_SZ:
		case REG_EXPAND_SZ:
			if(v->data[0])
				snprint(p, READSTR, "str %q", v->data);
			n = readstr(offset, a, n, p);
			break;
		case REG_MULTI_SZ:
			l = snprint(p, READSTR, "str");
			for(i=0;;){
				l += snprint(p+l, READSTR-l, " %q", v->data+i);
				while(v->data[i++] != 0){
					/* skip */
				}
				if(v->data[i] == 0)
					break;	/* final terminator */
			}
			n = readstr(offset, a, n, p);
			break;
		case REG_BINARY:
			l = n;
			n = readstr(offset, a, l, "bin");
			if(n >= 3){
				offset -= 3;
				if(offset+l > v->size)
					l = v->size - offset;
				memmove((char*)a+n, v->data+offset, l);
				n += l;
			}
			break;
		default:
			error("unknown registry type");
			n=0;
			break;
		}
		poperror();
		free(p);
		c->aux = nil;
		free(v);
		break;
	case Qhostmem:
		mem.dwLength = sizeof(mem);
		GlobalMemoryStatus(&mem);	/* GlobalMemoryStatusEx isn't on NT */
		p = smalloc(READSTR);
		if(waserror()){
			free(p);
			nexterror();
		}
		snprint(p, READSTR, "load %ld\nphys %lud %lud\nvirt %lud %lud\nswap %lud %lud\n",
			mem.dwMemoryLoad,
			mem.dwAvailPhys, mem.dwTotalPhys, mem.dwAvailVirtual, mem.dwTotalVirtual,
			mem.dwAvailPageFile, mem.dwTotalPageFile);
		n = readstr(offset, a, n, p);
		poperror();
		free(p);
		break;
	default:
		n=0;
		break;
	}
	return n;
}

static long
archwrite(Chan* c, void* a, long n, vlong offset)
{
	Value *v;
	int i;
	Cmdbuf *cb;
	Rune16 *key, *item;

	if((ulong)c->qid.path != Qregquery)
		error(Eperm);
	USED(offset);
	if(c->aux != nil){
		free(c->aux);
		c->aux = nil;
	}
	cb = parsecmd(a, n);
	if(waserror()){
		free(cb);
		nexterror();
	}
	if(cb->nf < 3)
		error(Ebadctl);
	for(i=0; i<nelem(roots); i++)
		if(strcmp(cb->f[0], roots[i].name) == 0)
			break;
	if(i >= nelem(roots))
		errorf("unknown root: %s", cb->f[0]);
	key = widen(cb->f[1]);
	if(waserror()){
		free(key);
		nexterror();
	}
	item = widen(cb->f[2]);
	if(waserror()){
		free(item);
		nexterror();
	}
	v = getregistry(roots[i].root, key, item);
	if(v == nil)
		error(up->env->errstr);
	c->aux = v;
	poperror();
	free(item);
	poperror();
	free(key);
	poperror();
	free(cb);
	return n;
}

Dev archdevtab = {
	'a',
	"arch",

	archinit,
	archattach,
	archwalk,
	archstat,
	archopen,
	devcreate,
	archclose,
	archread,
	devbread,
	archwrite,
	devbwrite,
	devremove,
	devwstat,
};

static void
regerr(int rc)
{
	Rune16 err[64];
	char emsg[sizeof(err)*UTFmax+1];

	FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM,
		0, rc, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
		err, sizeof(err), 0);
	runes16toutf(emsg, err, nelem(err));
	error(emsg);
}

static Value*
getregistry(HKEY root, Rune16 *keyname, Rune16 *name)
{
	long res;
	HKEY key;
	DWORD dtype, n;
	int i, l, nb;
	void* vp;
	char *p;
	Value *val;
	Rune16 *rb;

	qlock(&reglock);
	if(waserror()){
		qunlock(&reglock);
		return nil;
	}
	res = RegOpenKey(root, keyname, &key);
	if(res != ERROR_SUCCESS)
		regerr(res);
	if(waserror()){
		RegCloseKey(key);
		nexterror();
	}
	n = 0;
	res = RegQueryValueEx(key, name, NULL, &dtype, NULL, &n);
	if(res != ERROR_SUCCESS)
		regerr(res);
	nb = n;
	if(dtype == REG_SZ || dtype == REG_EXPAND_SZ || dtype == REG_MULTI_SZ){
		nb = n*UTFmax + 1;
		rb = smalloc((n+2)*sizeof(Rune16));
		memset(rb, 0, (n+2)*sizeof(Rune16));
	}else
		rb = nil;
	if(waserror()){
		free(rb);
		nexterror();
	}
	val = smalloc(sizeof(Value)+nb);
	if(waserror()){
		free(val);
		nexterror();
	}
	val->type = dtype;
	val->size = n;
	switch(dtype){
	case REG_DWORD:
		vp = &val->w;
		break;
#ifdef REG_QWORD
	case REG_QWORD:
		vp = &val->q;
		break;
#endif
	case REG_SZ:
	case REG_EXPAND_SZ:
	case REG_MULTI_SZ:
		vp = rb;
		break;
	case REG_BINARY:
	case REG_NONE:
		vp = val->data;
		break;
	default:
		errorf("unsupported registry type: %d", dtype);
		return nil;	/* for compiler */
	}
	res = RegQueryValueEx(key, name, NULL, NULL, vp, &n);
	if(res != ERROR_SUCCESS)
		regerr(res);
	poperror();
	if(rb != nil){
		if(dtype == REG_MULTI_SZ){
			p = val->data;
			for(i=0;;){
				l = runes16len(rb+i);
				runes16toutf(p, rb+i, l);
				i += l+1;
				if(rb[i] == 0)
					break;
				p += strlen(p)+1;
			}
		}else
			runes16toutf(val->data, rb, n);
		free(rb);
	}
	poperror();
	poperror();
	RegCloseKey(key);
	poperror();
	qunlock(&reglock);
	return val;
}
