#include <errno.h>
#include <signal.h>
#include <stdarg.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <term.h>
#include <termios.h>
#include <unistd.h>
#include <sys/select.h>
#include <sys/types.h>

#include "common.h"
#include "config.h"

#define C(c) #c
#define S(c) C(c)

/* ncurses doesn't define those in term.h, where they're used */
#ifndef OK
#define OK (0)
#endif
#ifndef ERR
#define ERR (-1)
#endif

#define maplines (lines - 2)

enum {
	Blocks = 1,
	Standout = 2,
	Important = 4
};

struct cell;
struct tile {
	char c;
	char flags;
	char *name;
	char *description;
	char *afterinteract;
	Item *(*interact)(struct cell *);
};

struct cell {
	struct tile *tile;
	size_t nitems;
	Item **items;
};

struct room {
	size_t x, y;
	size_t w, h;
};

struct rect {
	struct rect *next, *next2;
	struct rect *p;
	size_t x1, y1;
	size_t x2, y2;
	size_t d;
	union {
		void *p;
		int i;
	} data;
};

static struct termios tsave;
static struct termios tsacc;
static Item *curentry;
static int termset = ERR;
static char bufout[256];
static char bufout2[256];

size_t ox, oy;
size_t px, py;

#define MAPHEIGHT (50)
#define MAPWIDTH (160)
struct cell map[MAPHEIGHT][MAPWIDTH];

enum {
	DungeonScreen,
	MenuScreen
} screen;

Item *interactitem(struct cell *);
Item *interactmenu(struct cell *);

struct tile tile_void = { ' ', Blocks, "Void", "The void. The thing which is everywhere where nothing is.", NULL, NULL };
struct tile tile_floor = { '.', 0, "Floor", "An ordinary stone floor.", NULL, NULL };
struct tile tile_corridor = { '#', 0, "Different Floor", "This floor looks different than the other one.", NULL, NULL };
struct tile tile_verticalwall = { '|', Blocks, "Wall", "Wall.", NULL, NULL };
struct tile tile_horizontalwall = { '-', Blocks, "Wall", "Wall.", NULL, NULL };
struct tile tile_door = { '/', 0, "Door", "A door.", NULL, NULL };
struct tile tile_bookshelf = { 'E', Important, "Bookshelf", "A bookshelf.", "A loading bar?! In a book?!", interactmenu };
struct tile tile_book = { '?', Important, "%s", "A book: '%s'.", "A loading bar?! In a book?!", interactitem };
struct tile tile_portal = { '0', Important, "%s", "A portal: '%s'.", "You are getting transported through time and space.", interactitem };
struct tile tile_portalmachine = { 'O', Important, "Portal Machine", "A portal machine.", "You are getting transported through time and space.", interactmenu };
struct tile tile_heapofstuff = { '%', Important, "Heap", "A heap of stuff.", "The thing you touches glows strangely...", interactmenu };
struct tile tile_elevator = { 'L', Important, "Elevator", "An elevator.", "You hear elevator music...", interactmenu };
struct tile tile_stairsdown = { '>', Important, "'%s'", "A staircase leading down: '%s'.", "Too many stairs...", interactitem };

struct tile tile_stairsup = { '<', Standout | Important, "'%s'", "A staircase leading up: '%s'.", "Too many stairs...", interactitem };
struct tile tile_backportal = { '0', Standout | Important, "'%s'", "A portal leading back to wherever you came from: '%s'.", "You are getting transported through time and space.", interactitem };

void drawscreen(void);

int
mygetchar_(void)
{
	int r;
	fd_set fdset;

	FD_ZERO(&fdset);
	FD_SET(0, &fdset);

	if ((r = select(1, &fdset, NULL, NULL, NULL)) == -1) {
		if (errno == EINTR)
			return -1;
		return -2;
	}

	return getchar();
}

volatile sig_atomic_t sigwinch;

int
mygetchar(void)
{
	int r;

	while ((r = mygetchar_()) == -1) {
		if (sigwinch) {
			sigwinch = 0;

			if (termset == OK)
				del_curterm(cur_term);
			termset = setupterm(NULL, 1, NULL);

			drawscreen();
		}
	}

	if (r == -2)
		die("mygetchar: %s", strerror(errno));

	return r;
}

/*
	FNV-1a ( http://www.isthe.com/chongo/tech/comp/fnv/ )
	FNV was published into the public domain ( https://creativecommons.org/publicdomain/zero/1.0/ )
	by Landon Curt Noll: http://www.isthe.com/chongo/tech/comp/fnv/#public_domain
*/
uint32_t
fnv1a(int n,...)
{
	int i;
	char *s;
	va_list l;
	uint32_t h;

	h = 0x811c9dc5;

	va_start(l, n);
	for (i = 0; i < n; i++) {
		for (s = va_arg(l, char*); *s; s++) {
			h ^= *s;
			h *= 0x01000193;
		}
	}
	va_end(l);

	return h;
}

/*
	An LCG using the constants from "Numerical Recipes".
*/
uint16_t
ranqd1(uint32_t *s)
{
	return (*s = 1664525 * (*s) + 1013904223) >> 16;
}

struct rect *
randomneighbor(struct rect *x, struct rect *rs, uint32_t *prng, int (*filter)(struct rect *, struct rect *))
{
	struct rect *r, *result;
	size_t n;

	n = 0;
	result = NULL;
	for (r = rs; r; r = r->next) {
		if (r == x)
			continue;
		if (r->y2 < x->y1 || r->y1 > x->y2 || r->x2 < x->x1 || r->x1 > x->x2)
			continue;
		if ((r->y2 == x->y1 || r->y1 == x->y2) && (r->x2 == x->x1 || r->x1 == x->x2))
			continue;
		if (!filter(x, r))
			continue;
		n++;
		if (ranqd1(prng) / (1. + UINT16_MAX) < 1. / n)
			result = r;
	}

	return result;
}

size_t
min(size_t a, size_t b)
{
	if (a < b)
		return a;
	return b;
}

size_t
max(size_t a, size_t b)
{
	if (a > b)
		return a;
	return b;
}

/*
	Creates an uneven grid by splitting the map recursively.
	Returns an array containing the cells (rects) of the grid.
*/
struct rect *
generaterects(size_t heightmin, size_t widthmin, uint32_t prng)
{
	struct rect *queuehead, *queuetail;
	struct rect *r, *t;
	struct rect *rects;
	size_t w, h;
	int vertical, spaceforvertical, spaceforhorizontal;

	r = malloc(sizeof(*r));
	memset(r, 0, sizeof(*r));
	r->x1 = r->y1 = 0;
	r->x2 = MAPWIDTH;
	r->y2 = MAPHEIGHT;
	r->d = 0;

	queuetail = r;
	queuetail->next = NULL;
	queuehead = r;

	rects = NULL;

	while (queuehead) {
		r = queuehead;
		if (queuetail == queuehead)
			queuetail = NULL;
		queuehead = queuehead->next;

		spaceforvertical = r->y2 - r->y1 >= heightmin * 2;
		spaceforhorizontal = r->x2 - r->x1 >= widthmin * 2;

		if (spaceforhorizontal && spaceforvertical) {
			vertical = ranqd1(&prng) & 1;
		} else if (spaceforhorizontal) {
			vertical = 0;
		} else if (spaceforvertical) {
			vertical = 1;
		} else {
			r->next = rects;
			rects = r;
			continue;
		}

		if (vertical) {
			w = r->x2 - r->x1;
			h = heightmin + ranqd1(&prng) % (1 + r->y2 - r->y1 - heightmin * 2);
		} else {
			w = widthmin + ranqd1(&prng) % (1 + r->x2 - r->x1 - widthmin * 2);
			h = r->y2 - r->y1;
		}

		t = malloc(sizeof(*t));
		memset(t, 0, sizeof(*t));
		t->x1 = r->x1;
		t->y1 = r->y1;
		t->x2 = r->x1 + w;
		t->y2 = r->y1 + h;
		t->d = r->d + 1;

		if (!queuetail) {
			queuehead = t;
			queuetail = t;
		} else {
			queuetail->next = t;
			queuetail = t;
		}

		t = malloc(sizeof(*t));
		memset(t, 0, sizeof(*t));
		if (vertical) {
			t->x1 = r->x1;
			t->y1 = r->y1 + h;
		} else {
			t->x1 = r->x1 + w;
			t->y1 = r->y1;
		}
		t->x2 = r->x2;
		t->y2 = r->y2;
		t->d = r->d + 1;

		queuetail->next = t;
		queuetail = t;

		free(r);
	}

	return rects;
}

void
connectpoints_horizontal(size_t y,
                       size_t ax, int ea, struct tile *at,
                       size_t bx, int eb, struct tile *bt,
                       struct tile *t)
{
	size_t i, s, e;
	ssize_t ii;

	if (ax < bx)
		ii = 1;
	else if (ax > bx)
		ii = -1;
	else
		ii = 0;

	s = ax;
	if (ea)
		s += ii;
	e = bx + ii;
	if (eb)
		e -= ii;

	for (i = s; i != e; i += ii)
		map[y][i].tile = t;

	if (e - ii == s) {
		if (at != t)
			map[y][s].tile = at;
		if (bt != t)
			map[y][s].tile = bt;
	} else {
		map[y][s].tile = at;
		map[y][e - ii].tile = bt;
	}
}

void
connectpoints_vertical(size_t x,
                       size_t ay, int ea, struct tile *at,
                       size_t by, int eb, struct tile *bt,
                       struct tile *t)
{
	size_t i, s, e;
	ssize_t ii;

	if (ay < by)
		ii = 1;
	else if (ay > by)
		ii = -1;
	else
		ii = 0;

	s = ay;
	if (ea)
		s += ii;
	e = by + ii;
	if (eb)
		e -= ii;

	for (i = s; i != e; i += ii)
		map[i][x].tile = t;

	if (e - ii == s) {
		if (at != t)
			map[s][x].tile = at;
		if (bt != t)
			map[s][x].tile = bt;
	} else {
		map[s][x].tile = at;
		map[e - ii][x].tile = bt;
	}
}

void
connectpoints(size_t ax, size_t ay, int ea, struct tile *at,
              size_t bx, size_t by, int eb, struct tile *bt,
              int vertical, struct tile *ct)
{
	if (!vertical) {
		connectpoints_horizontal(ay,
					 ax, ea, at,
					 bx, 0, ct,
					 ct);
		connectpoints_vertical(bx,
				       ay, 0, ct,
				       by, eb, bt,
				       ct);
	} else {
		connectpoints_vertical(ax,
				       ay, ea, at,
				       by, 0, ct,
				       ct);
		connectpoints_horizontal(by,
					 ax, 0, ct,
					 bx, eb, bt,
					 ct);
	}
}

void
nearestpoints(struct room *a, struct room *b, size_t *ax, size_t *ay, size_t *bx, size_t *by)
{
	if (a->y >= b->y && a->y < b->y + b->h) {
		*ay = *by = a->y;
	} else if (b->y >= a->y && b->y < a->y + a->h) {
		*ay = *by = b->y;
	} else if (a->y >= b->y) {
		*ay = a->y;
		*by = b->y + b->h - 1;
	} else if (b->y >= a->y) {
		*ay = a->y + a->h - 1;
		*by = b->y;
	}

	if (a->x >= b->x && a->x < b->x + b->w) {
		*ax = *bx = a->x;
	} else if (b->x >= a->x && b->x < a->x + a->w) {
		*ax = *bx = b->x;
	} else if (a->x >= b->x) {
		*ax = a->x;
		*bx = b->x + b->w - 1;
	} else if (b->x >= a->x) {
		*ax = a->x + a->w - 1;
		*bx = b->x;
	}
}

void
connectadjacentrooms(struct rect *a, struct room *ar, struct rect *b, struct room *br)
{
	size_t irx1, iry1, irx2, iry2;
	size_t rx1, ry1, rx2, ry2;
	size_t cx, cy;
	struct rect *r1, *r2;
	struct room *room1, *room2;
	int vertical;

	if (a->x2 == b->x1) {
		r1 = a;
		room1 = ar;
		r2 = b;
		room2 = br;
	} else if (b->x2 == a->x1) {
		r1 = b;
		room1 = br;
		r2 = a;
		room2 = ar;
	} else if (a->y2 == b->y1) {
		r1 = a;
		room1 = ar;
		r2 = b;
		room2 = br;
	} else if (b->y2 == a->y1) {
		r1 = b;
		room1 = br;
		room2 = ar;
		r2 = a;
	} else {
		return;
	}

	if (r1->y2 == r2->y1) {
		irx1 = max(r1->x1, r2->x1);
		irx2 = min(r1->x2, r2->x2);
		iry1 = r1->y2;
		iry2 = r1->y2 + 1;
	} else {
		iry1 = max(r1->y1, r2->y1);
		iry2 = min(r1->y2, r2->y2);
		irx1 = r1->x2;
		irx2 = r1->x2 + 1;
	}

	nearestpoints(room1, room2, &rx1, &ry1, &rx2, &ry2);

	if (r1->y2 == r2->y1) {
		/* both points are in the intersection */
		if (rx1 >= irx1 && rx1 < irx2 &&
		    rx2 >= irx1 && rx2 < irx2) {
			vertical = 1;
			cx = (rx2 + rx1) / 2;
			cy = (ry2 + ry1) / 2;
		} else
		/* none is in the intersection */
		if (!(rx1 >= irx1 && rx1 < irx2) &&
		    !(rx2 >= irx1 && rx2 < irx2)) {
			vertical = 0;
			cx = irx1;
			cy = r1->y2;
		} else if (rx1 >= irx1 && rx1 < irx2) {
			vertical = 1;
			cx = (rx2 + rx1) / 2;
			cy = r1->y2;
		} else if (rx2 >= irx1 && rx2 < irx2) {
			vertical = 1;
			cx = rx2;
			cy = r1->y2 - 1;
		}
	} else {
		/* both points are in the intersection */
		if (ry1 >= iry1 && ry1 < iry2 &&
		    ry2 >= iry1 && ry2 < iry2) {
			vertical = 0;
			cx = (rx2 + rx1) / 2;
			cy = (ry2 + ry1) / 2;
		} else
		/* none is in the intersection */
		if (!(ry1 >= iry1 && ry1 < iry2) &&
		    !(ry2 >= iry1 && ry2 < iry2)) {
			vertical = 1;
			cx = r1->x2;
			cy = iry1;
		} else if (ry1 >= iry1 && ry1 < iry2) {
			vertical = 0;
			cx = r1->x2;
			cy = (ry2 + ry1) / 2;
		} else if (ry2 >= iry1 && ry2 < iry2) {
			vertical = 0;
			cx = r1->x2 - 1;
			cy = ry2;
		}
	}

	if (rx1 == rx2) {
		connectpoints_vertical(rx1,
		                       ry1, 1, &tile_door,
		                       ry2, 1, &tile_door,
		                       &tile_corridor);
	} else if (ry1 == ry2) {
		connectpoints_horizontal(ry1,
		                         rx1, 1, &tile_door,
		                         rx2, 1, &tile_door,
		                         &tile_corridor);
	} else {
		connectpoints(rx1, ry1, 1, &tile_door,
			      cx, cy, 0, &tile_corridor,
			      vertical, &tile_corridor);
		connectpoints(cx, cy, 1, &tile_corridor,
			      rx2, ry2, 1, &tile_door,
			      !vertical, &tile_corridor);
	}
}

int
rectisfull(struct rect *x, struct rect *r)
{
	return !!r->data.i;
}

int
rectisempty(struct rect *x, struct rect *r)
{
	return !r->data.i;
}

int
rectisnotp(struct rect *x, struct rect *r)
{
	return r->data.p && x->p != r && r->p != x;
}

int
rectisrandom(struct rect *x, struct rect *r)
{
	return 1;
}

/*
	Basically https://www.roguebasin.com/index.php/Diffusion-limited_aggregation
	Returns the list of carved rooms.
*/
struct rect *
dla(struct rect *rects, size_t l, uint32_t prng) {
	size_t rl, i, n;
	struct rect *r, *t, *walk, *p;

	for (r = rects, rl = 0; r; r = r->next)
		rl++;

	if (l > rl)
		l = rl;

	/* get the rect which contains the map center */
	for (r = rects; r; r = r->next) {
		if (MAPHEIGHT / 2 >= r->y1 && MAPHEIGHT / 2 < r->y2 &&
		    MAPWIDTH / 2 >= r->x1 && MAPWIDTH / 2 < r->x2)
			break;
	}

	p = NULL;
	walk = NULL;
	i = 0;
	for (;;) {
		r->p = p;
		r->data.i = 1;
		r->next2 = walk;
		walk = r;

		if (i >= l - 1)
			break;

		t = NULL;
		for (r = rects, n = 0; r; r = r->next) {
			if (r->data.i)
				continue;
			n++;
			if (ranqd1(&prng) / (1. + UINT16_MAX) < 1. / n)
				t = r;
		}

		/* there is no free rect left */
		if (!t)
			break;

		/* do a random walk starting from t until the walk collides with a carved room (r) */
		while ((r = randomneighbor(t, rects, &prng, rectisrandom)) && !r->data.i)
			t = r;

		p = r;
		r = t;

		i++;
	}

	return walk;
}

void
rendermapchar(size_t i, size_t j) {
	if (map[i][j].tile->flags & Standout)
		putp(tiparm(enter_standout_mode));
	putchar(map[i][j].tile->c);
	if (map[i][j].tile->flags & Standout)
		putp(tiparm(exit_standout_mode));
}

void
rendermapline(size_t i)
{
	size_t j;

	for (j = ox; j < min(MAPWIDTH, ox + columns); j++)
		rendermapchar(i, j);
}

void
rendermap(void)
{
	size_t i;

	if (px < columns / 2 || MAPWIDTH <= columns)
		ox = 0;
	else if (px >= MAPWIDTH - columns / 2 - 1)
		ox = MAPWIDTH - columns;
	else
		ox = px - columns / 2;

	if (py < maplines / 2 || MAPHEIGHT <= maplines)
		oy = 0;
	else if (py >= MAPHEIGHT - maplines / 2 - 1)
		oy = MAPHEIGHT - maplines;
	else
		oy = py - maplines / 2;

	for (i = oy; i < min(MAPHEIGHT, oy + (lines - 2)); i++) {
		if (i != oy)
			putp(tiparm(cursor_down));
		rendermapline(i);
	}
}

size_t
placeitems_hash(Item *item, size_t *assocs, size_t k)
{
	Dir *dir;
	Item *citem;
	size_t i;

	dir = item->dat;
	for (i = 0; i < dir->nitems; i++) {
		citem = &dir->items[i];
		/* TODO Somewhere else */
		if (!citem->host || !citem->port || !citem->selector)
			continue;
		assocs[i] = fnv1a(6, item->host, item->port, item->selector, citem->host, citem->port, citem->selector) % k;
	}

	return k;
}

#define POSITIONS_LENGTH 5
enum {
	Portal,
	StaircaseDown,
	Bookshelf,
	OtherStuff,
	Back
};

enum {
	FillEntireCell = 1
};

#define length(a) (sizeof(a) / sizeof(a[0]))
static struct dungeontype {
	char *name;
	char flags;
	size_t heightmin;
	size_t heightmax;
	size_t widthmin;
	size_t widthmax;
	size_t margin;
	size_t wiggle;
} dungeontypes[] = {
	{ "rogueish", 0, 2, 5, 3, 7, 2, 0 },
	{ "rogueish-wide", 0, 2, 5, 3, 7, 3, 1 },
	{ "compact", FillEntireCell, 2, 2, 3, 3, 1, 0 },
};

void
generatemap(Item *item, Item *pitem)
{
	Dir *dir;
	Item *citem;
	size_t l, i, j, k, ir, n, m, x, y;
	struct rect *rects, *walk, *tr, *cr;
	struct room *rooms, *room;
	size_t *cassocs;
	int changedlevel, gonedown;
	char buffer[10];
	uint32_t prng;
	struct {
		size_t x, y;
	} positions[POSITIONS_LENGTH];
	size_t cellwidth, cellheight;
	struct dungeontype *type;

	type = &dungeontypes[fnv1a(3, item->host, item->port, "dungeontype") % length(dungeontypes)];

	cellheight = type->heightmin + 2 * type->margin + type->wiggle;
	cellwidth = type->widthmin + 2 * type->margin + type->wiggle;

	rects = generaterects(cellheight, cellwidth, fnv1a(4, item->host, item->port, item->selector, "gridseed"));

	dir = item->dat;
	for (j = l = 0; j < dir->nitems; j++) {
		if (dir->items[j].type != 0 &&
		    dir->items[j].type != 'i' &&
		    dir->items[j].type != '3')
			l++;
	}

	k = 1 + l / 10;
	walk = dla(rects, k, fnv1a(4, item->host, item->port, item->selector, "randomwalkseed"));
	for (cr = walk, k = 0; cr; cr = cr->next2, k++);

	for (cr = rects; cr; cr = cr->next)
		cr->data.p = NULL;

	rooms = calloc(k, sizeof(*rooms));
	for (cr = walk, i = 0; cr; cr = cr->next2, i++)
		cr->data.p = &rooms[i];

	prng = fnv1a(4, item->host, item->port, item->selector, "roomsseed");
	for (cr = walk; cr; cr = cr->next2) {
		room = cr->data.p;

		if (type->flags & FillEntireCell) {
			room->w = cr->x2 - cr->x1 - 2 * type->margin;
			room->x = cr->x1 + type->margin;
			room->h = cr->y2 - cr->y1 - 2 * type->margin;
			room->y = cr->y1 + type->margin;
		} else {
			room->w = type->widthmin + ranqd1(&prng) % (1 + min(cr->x2 - cr->x1 - type->widthmin - 2 * type->margin, type->widthmax - type->widthmin));
			room->x = cr->x1 + type->margin + ranqd1(&prng) % (1 + cr->x2 - cr->x1 - room->w - 2 * type->margin);
			room->h = type->heightmin + ranqd1(&prng) % (1 + min(cr->y2 - cr->y1 - type->heightmin - 2 * type->margin, type->heightmax - type->heightmin));
			room->y = cr->y1 + type->margin + ranqd1(&prng) % (1 + cr->y2 - cr->y1 - room->h - 2 * type->margin);
		}
	}

	for (i = 0; i < MAPHEIGHT; i++) {
		for (j = 0; j < MAPWIDTH; j++) {
			map[i][j].tile = &tile_void;
			free(map[i][j].items);
			map[i][j].items = NULL;
			map[i][j].nitems = 0;
		}
	}

	for (cr = walk; cr; cr = cr->next2) {
		room = cr->data.p;

		for (x = room->x - 1; x < room->x + room->w + 1; x++)
			map[room->y-1][x].tile = &tile_horizontalwall;
		for (y = room->y; y < room->y + room->h; y++) {
			map[y][room->x - 1].tile = &tile_verticalwall;
			for (x = room->x; x < room->x + room->w; x++)
				map[y][x].tile = &tile_floor;
			map[y][room->x + room->w].tile = &tile_verticalwall;
		}
		for (x = room->x - 1; x < room->x + room->w + 1; x++)
			map[room->y + room->h][x].tile = &tile_horizontalwall;
	}

	for (cr = walk; cr; cr = cr->next2) {
		if (cr->p)
			connectadjacentrooms(cr, cr->data.p,
			                     cr->p, cr->p->data.p);

		/* Add some loop possibility */
		if (tr = randomneighbor(cr, rects, &prng, rectisnotp))
			connectadjacentrooms(cr, cr->data.p,
			                     tr, tr->data.p);
	}

	cassocs = calloc(dir->nitems, sizeof(*cassocs));

	k = placeitems_hash(item, cassocs, k);

	changedlevel = item != pitem;
	gonedown = pitem == item->entry;

	/*
		Insert items
                The placement of items affects the initial placement of the
                player, because they could have gone back to this map, so they
                should appear at the elevator/portal/stair they used.
	*/

	/*
                The initial room is everytime the first one. Reason: The count
                of rooms is based on how many entries are in the gophermap and
                how many rooms can fit on the map. There will be at minimum 1.
                So when more entries get added there will be more rooms but the
                first one stays at the same position. I think about the
                retrying and clownflare things on bitreich.org, the selector
                doesn't change...
	*/
	ir = 0;

	for (i = 0; i < k; i++) {
		/* select random positions for different item types inside the current room */
		snprintf(buffer, sizeof(buffer), "%lu", i);
		prng = fnv1a(4, item->host, item->port, item->selector, buffer);
		for (j = 0, m = rooms[i].h * rooms[i].w; j < m; j++) {
			n = j;
			if (j >= POSITIONS_LENGTH)
				n *= ranqd1(&prng) / (double)UINT16_MAX;

			if (n < POSITIONS_LENGTH) {
				positions[n].x = rooms[i].x + j % rooms[i].w;
				positions[n].y = rooms[i].y + j / rooms[i].w;
			}
		}

		for (j = 0; j < dir->nitems; j++) {
			if (cassocs[j] != i)
				continue;

			citem = &dir->items[j];
			switch (citem->type) {
			case '0':
				x = positions[Bookshelf].x;
				y = positions[Bookshelf].y;
				if (map[y][x].nitems)
					map[y][x].tile = &tile_bookshelf;
				else
					map[y][x].tile = &tile_book;
				break;
			case '1':
				if (strcmp(citem->host, item->host) || strcmp(citem->port, item->port)) {
					x = positions[Portal].x;
					y = positions[Portal].y;
					if (map[y][x].nitems)
						map[y][x].tile = &tile_portalmachine;
					else
						map[y][x].tile = &tile_portal;
				} else {
					x = positions[StaircaseDown].x;
					y = positions[StaircaseDown].y;
					if (map[y][x].nitems)
						map[y][x].tile = &tile_elevator;
					else
						map[y][x].tile = &tile_stairsdown;
				}
				break;
			case 0:
			case 'i':
			case '3':
				continue;
				break;
			default:
				x = positions[OtherStuff].x;
				y = positions[OtherStuff].y;
				map[y][x].tile = &tile_heapofstuff;
				break;
			}

			map[y][x].nitems++;
			map[y][x].items = realloc(map[y][x].items, map[y][x].nitems * sizeof(*map[y][x].items));
			map[y][x].items[map[y][x].nitems-1] = citem;

			if (changedlevel && citem == pitem) {
				px = x;
				py = y;
			}
		}

		if (i == ir && item->entry != item) {
			y = positions[Back].y;
			x = positions[Back].x;
			if (strcmp(item->entry->host, item->host) || strcmp(item->entry->port, item->port))
				map[y][x].tile = &tile_backportal;
			else
				map[y][x].tile = &tile_stairsup;
			map[y][x].nitems++;
			map[y][x].items = realloc(map[y][x].items, map[y][x].nitems * sizeof(*map[y][x].items));
			map[y][x].items[map[y][x].nitems-1] = item->entry;
		}

		if (changedlevel && i == ir && (gonedown || !pitem)) {
			px = positions[Back].x;
			py = positions[Back].y;
		}
	}

	free(cassocs);
	free(rooms);

	for (cr = rects; cr;) {
		tr = cr;
		cr = cr->next;
		free(tr);
	}
}

void
uisetup(void)
{
	tcgetattr(0, &tsave);
	tsacc = tsave;
	tsacc.c_lflag &= ~(ECHO|ICANON);
	tsacc.c_cc[VMIN] = 1;
	tsacc.c_cc[VTIME] = 0;
	tcsetattr(0, TCSANOW, &tsacc);

	if (termset != OK)
		/* setupterm call exits on error */
		termset = setupterm(NULL, 1, NULL);
	putp(tiparm(clear_screen));
	fflush(stdout);
}

void
uicleanup(void)
{
	tcsetattr(0, TCSANOW, &tsave);

	if (termset != OK)
		return;

	putp(tiparm(change_scroll_region, 0, lines-1));
	putp(tiparm(clear_screen));
	fflush(stdout);
}

char *
uiprompt(char *fmt, ...)
{
	va_list ap;
	char *input = NULL;
	size_t n;
	ssize_t r;

	putp(tiparm(save_cursor));

	putp(tiparm(cursor_address, lines-1, 0));
	putp(tiparm(clr_eol));

	va_start(ap, fmt);
	vsnprintf(bufout, sizeof(bufout), fmt, ap);
	va_end(ap);

	n = mbsprint(bufout, columns);

	putp(tiparm(clr_eol));

	putp(tiparm(cursor_address, lines-1, n));

	tsacc.c_lflag |= (ECHO|ICANON);
	tcsetattr(0, TCSANOW, &tsacc);
	fflush(stdout);

	n = 0;
	r = getline(&input, &n, stdin);

	tsacc.c_lflag &= ~(ECHO|ICANON);
	tcsetattr(0, TCSANOW, &tsacc);
	putp(tiparm(restore_cursor));
	fflush(stdout);

	if (r == -1 || feof(stdin)) {
		clearerr(stdin);
		clear(&input);
	} else if (input[r - 1] == '\n') {
		input[--r] = '\0';
	}

	return input;
}

void
displaybar(char *s) {
	size_t n;

	putp(tiparm(save_cursor));

	putp(tiparm(cursor_address, lines-2, 0));
	putp(tiparm(enter_standout_mode));

	n = mbsprint(s, columns);
	for (n = columns - n; n; n--)
		putchar(' ');

	putp(tiparm(exit_standout_mode));

	putp(tiparm(restore_cursor));
	fflush(stdout);
}

void
vdisplayinfoline(char *fmt, va_list ap)
{
	putp(tiparm(save_cursor));

	putp(tiparm(cursor_address, lines-1, 0));

	vsnprintf(bufout, sizeof(bufout), fmt, ap);

	mbsprint(bufout, columns);

	putp(tiparm(clr_eol));

	putp(tiparm(restore_cursor));
	fflush(stdout);
}

void
uistatus(char *fmt, ...)
{
	va_list ap;
	size_t n;

	putp(tiparm(save_cursor));

	putp(tiparm(cursor_address, lines-1, 0));

	va_start(ap, fmt);
	n = vsnprintf(bufout, sizeof(bufout), fmt, ap);
	va_end(ap);

	if (n < sizeof(bufout)-1) {
		snprintf(bufout+n, sizeof(bufout)-n,
		         " [Press a key to continue \xe2\x98\x83]");
	}

	mbsprint(bufout, columns);

	putp(tiparm(clr_eol));

	putp(tiparm(restore_cursor));
	fflush(stdout);

	mygetchar();
}

void
displayinfoline(char *fmt, ...)
{
	va_list ap;

	va_start(ap, fmt);
	vdisplayinfoline(fmt, ap);
	va_end(ap);
}

char *menutitle;
Item **menuitems;
size_t menunitems;
volatile size_t menuoffset;
size_t menuselected;

void
menudraw(void)
{
	size_t i, n;

	putp(tiparm(change_scroll_region, 1, lines-1));
	putp(tiparm(clear_screen));

	putp(tiparm(enter_standout_mode));
	puts(menutitle);
	putp(tiparm(exit_standout_mode));

	if (menuselected - menuoffset >= lines - 1)
		menuoffset = menuselected - (lines - 1) + 1;

	for (i = menuoffset, n = 0; i < menunitems && n < lines - 1; i++, n++) {
		if (i != menuoffset)
			putp(tiparm(cursor_down));
		if (i == menuselected)
			putp(tiparm(enter_standout_mode));
		mbsprint(menuitems[i]->username, columns);
		if (i == menuselected)
			putp(tiparm(exit_standout_mode));
		putp(tiparm(column_address, 0));
	}
	fflush(stdout);
}

Item *
showmenu(char *title, Item **item, size_t l)
{
	Item *selection;

	menutitle = title;
	menuitems = item;
	menunitems = l;
	menuselected = 0;
	menuoffset = 0;
	screen = MenuScreen;
	drawscreen();

	selection = NULL;
	for (;;) {
		switch (mygetchar()) {
		case 'j':
			if (menuselected + 1 < menunitems) {
				putp(tiparm(cursor_address, 1 + menuselected - menuoffset, 0));
				mbsprint(menuitems[menuselected]->username, columns);
				menuselected++;
				putp(tiparm(column_address, 0));
				if (menuselected - menuoffset >= lines - 1) {
					menuoffset++;
					putp(tiparm(scroll_forward));
				} else {
					putp(tiparm(cursor_down));
				}
				putp(tiparm(enter_standout_mode));
				mbsprint(menuitems[menuselected]->username, columns);
				putp(tiparm(exit_standout_mode));
			}
			break;
		case 'k':
			if (menuselected > 0) {
				putp(tiparm(cursor_address, 1 + menuselected - menuoffset, 0));
				mbsprint(menuitems[menuselected]->username, columns);
				menuselected--;
				putp(tiparm(column_address, 0));
				if (menuselected < menuoffset) {
					menuoffset = menuselected;
					putp(tiparm(scroll_reverse));
				} else {
					putp(tiparm(cursor_up));
				}
				putp(tiparm(enter_standout_mode));
				mbsprint(menuitems[menuselected]->username, columns);
				putp(tiparm(exit_standout_mode));
			}
			break;
		case ' ':
			selection = menuitems[menuselected];
		case 0x1b:
			goto endloop;
			break;
		}
		fflush(stdout);
	}

endloop:
	screen = DungeonScreen;
	drawscreen();

	return selection;
}

Item *
interactitem(struct cell *c)
{
	displayinfoline(map[py][px].tile->afterinteract);

	return map[py][px].items[0];
}

Item *
interactmenu(struct cell *c)
{
	Item *selection;

	if (selection = showmenu(map[py][px].tile->name, map[py][px].items, map[py][px].nitems))
		displayinfoline(map[py][px].tile->afterinteract);

	return selection;
}

Item *
interact(Item *item)
{
	if (map[py][px].tile->interact)
		return map[py][px].tile->interact(&map[py][px]);

	return NULL;
}

void
describe(size_t x, size_t y, int verbose)
{
	char *name;

	if (map[y][x].nitems) {
		if (*map[y][x].items[0]->username) {
			name = map[y][x].items[0]->username;
		} else {
			itemuri(map[y][x].items[0], bufout2, sizeof(bufout2));
			name = bufout2;
		}
	} else {
		name = NULL;
	}
	if (map[y][x].tile->flags & Important || verbose)
		displayinfoline(map[y][x].tile->description, name);
	else
		displayinfoline("");
}

void
dungeondraw(void)
{
	putp(tiparm(change_scroll_region, 0, lines-3));
	putp(tiparm(clear_screen));

	rendermap();

	putp(tiparm(cursor_address, py - oy, px - ox));
	putchar('@');
	putp(tiparm(cursor_address, py - oy, px - ox));

	if (curentry->entry != curentry) {
		displaybar(curentry->username);
	} else {
		itemuri(curentry, bufout, sizeof(bufout));
		displaybar(bufout);
	}

	describe(px, py, 0);
}

void
move(ssize_t dx, ssize_t dy)
{
	ssize_t i;
	size_t x, y;
	size_t noy, nox;

	if ((ssize_t)py + dy >= MAPHEIGHT || (ssize_t)py + dy < 0)
		return;
	if ((ssize_t)px + dx >= MAPWIDTH || (ssize_t)px + dx < 0)
		return;

	x = px + dx;
	y = py + dy;

	if (map[y][x].tile->flags & Blocks)
		return;

	if (dx) {
		if (x < columns / 2 || MAPWIDTH <= columns)
			nox = 0;
		else if (x >= MAPWIDTH - columns / 2 - 1)
			nox = MAPWIDTH - columns;
		else
			nox = x - columns / 2;

		if (ox != nox) {
			putp(tiparm(cursor_address, 0, 0));
			rendermap();
		} else {
			putp(tiparm(cursor_address, py - oy, px - ox));
			rendermapchar(py, px);
		}
	} else if (dy) {
		putp(tiparm(cursor_address, py - oy, px - ox));
		rendermapchar(py, px);

		if (y < maplines / 2 || MAPHEIGHT <= maplines) {
			noy = 0;
		} else if (y >= MAPHEIGHT - maplines / 2 - 1) {
			noy = MAPHEIGHT - maplines;
		} else {
			noy = y - maplines / 2;
		}

		if (noy < oy) {
			putp(tiparm(cursor_address, 0, 0));
			for (i = (ssize_t)oy - 1; i >= (ssize_t)noy; i--) {
				putp(tiparm(scroll_reverse));
				rendermapline(i);
				putp(tiparm(column_address, 0));
			}
		} else if (noy > oy) {
			putp(tiparm(cursor_address, lines-3, 0));
			for (i = oy + 1; i <= noy; i++) {
				putp(tiparm(scroll_forward));
				rendermapline(i + maplines - 1);
				putp(tiparm(column_address, 0));
			}
		}
		oy = noy;
	}

	py = y;
	px = x;
	putp(tiparm(cursor_address, py - oy, px - ox));
	putchar('@');
	putp(tiparm(cursor_address, py - oy, px - ox));

	describe(px, py, 0);
}

void
drawscreen(void)
{
	switch (screen) {
	case DungeonScreen:
		dungeondraw();
		break;
	case MenuScreen:
		menudraw();
		break;
	}
	fflush(stdout);
}

void
uidisplay(Item *entry)
{
	if (!entry || !(entry->type == '1' || entry->type == '+' || entry->type == '7'))
		return;

	if (entry != curentry) {
		generatemap(entry, curentry);
		curentry = entry;
	}

	drawscreen();
}

Item *
uiselectitem(Item *entry)
{
	Item *e;

	if (!entry || !entry->dat)
		return NULL;

	for (;;) {
		switch (mygetchar()) {
		case 'h':
			move(-1, 0);
			break;
		case 'j':
			move(0, 1);
			break;
		case 'k':
			move(0, -1);
			break;
		case 'l':
			move(1, 0);
			break;
		case ' ':
			if (e = interact(entry))
				return e;
			break;
		case 'q':
		case 0x1b:
			return NULL;
		}
		fflush(stdout);
	}
}

void
uisigwinch(int signal)
{
	sigwinch = 1;
}
