// console.cpp: the console buffer, its display, and command line control

#include "cube.h"
#include <ctype.h>

struct cline {
	char *cref;
	int outtime;
};
vector<cline> conlines;

const int ndraw = 5;
const int WORDWRAP = 80;
int conskip = 0;

bool saycommandon = false;
string commandbuf;

void
setconskip(int n)
{
	conskip += n;
	if (conskip < 0)
		conskip = 0;
};

COMMANDN(conskip, setconskip, ARG_1INT);

void
conline(const char *sf, bool highlight) // add a line to the console buffer
{
	cline cl;
	cl.cref = conlines.length() > 100
	              ? conlines.pop().cref
	              : newstringbuf(""); // constrain the buffer size
	cl.outtime = lastmillis;          // for how long to keep line on screen
	conlines.insert(0, cl);
	if (highlight) // show line in a different colour, for chat etc.
	{
		cl.cref[0] = '\f';
		cl.cref[1] = 0;
		strcat_s(cl.cref, sf);
	} else {
		strcpy_s(cl.cref, sf);
	};
	puts(cl.cref);
#ifndef _WIN32
	fflush(stdout);
#endif
};

void
conoutf(const char *s, ...)
{
	sprintf_sdv(sf, s);
	s = sf;
	int n = 0;
	while (strlen(s) > WORDWRAP) // cut strings to fit on screen
	{
		string t;
		strn0cpy(t, s, WORDWRAP + 1);
		conline(t, n++ != 0);
		s += WORDWRAP;
	};
	conline(s, n != 0);
};

void
renderconsole() // render buffer taking into account time & scrolling
{
	int nd = 0;
	char *refs[ndraw];
	loopv(conlines) if (conskip ? i >= conskip - 1 ||
	                                  i >= conlines.length() - ndraw
	                            : lastmillis - conlines[i].outtime < 20000)
	{
		refs[nd++] = conlines[i].cref;
		if (nd == ndraw)
			break;
	};
	loopj(nd)
	{
		draw_text(refs[j], FONTH / 3,
		    (FONTH / 4 * 5) * (nd - j - 1) + FONTH / 3, 2);
	};
};

// keymap is defined externally in keymap.cfg

struct keym {
	int code;
	char *name;
	char *action;
} keyms[256];
int numkm = 0;

void
keymap(char *code, char *key, char *action)
{
	keyms[numkm].code = atoi(code);
	keyms[numkm].name = newstring(key);
	keyms[numkm++].action = newstringbuf(action);
};

COMMAND(keymap, ARG_3STR);

void
bindkey(char *key, char *action)
{
	for (char *x = key; *x; x++)
		*x = toupper(*x);
	loopi(numkm) if (strcmp(keyms[i].name, key) == 0)
	{
		strcpy_s(keyms[i].action, action);
		return;
	};
	conoutf("unknown key \"%s\"", key);
};

COMMANDN(bind, bindkey, ARG_2STR);

void
saycommand(char *init) // turns input to the command line on or off
{
	SDL_EnableUNICODE(saycommandon = (init != NULL));
	if (!editmode)
		keyrepeat(saycommandon);
	if (!init)
		init = "";
	strcpy_s(commandbuf, init);
};

void
mapmsg(char *s)
{
	strn0cpy(hdr.maptitle, s, 128);
};

COMMAND(saycommand, ARG_VARI);
COMMAND(mapmsg, ARG_1STR);

#ifndef _WIN32
#include <SDL_syswm.h>
#include <X11/Xlib.h>
#endif

void
pasteconsole()
{
#ifdef _WIN32
	if (!IsClipboardFormatAvailable(CF_TEXT))
		return;
	if (!OpenClipboard(NULL))
		return;
	char *cb = (char *)GlobalLock(GetClipboardData(CF_TEXT));
	strcat_s(commandbuf, cb);
	GlobalUnlock(cb);
	CloseClipboard();
#else
	SDL_SysWMinfo wminfo;
	SDL_VERSION(&wminfo.version);
	wminfo.subsystem = SDL_SYSWM_X11;
	if (!SDL_GetWMInfo(&wminfo))
		return;
	int cbsize;
	char *cb = XFetchBytes(wminfo.info.x11.display, &cbsize);
	if (!cb || !cbsize)
		return;
	int commandlen = strlen(commandbuf);
	for (char *cbline = cb, *cbend;
	     commandlen + 1 < _MAXDEFSTR && cbline < &cb[cbsize];
	     cbline = cbend + 1) {
		cbend = (char *)memchr(cbline, '\0', &cb[cbsize] - cbline);
		if (!cbend)
			cbend = &cb[cbsize];
		if (commandlen + cbend - cbline + 1 > _MAXDEFSTR)
			cbend = cbline + _MAXDEFSTR - commandlen - 1;
		memcpy(&commandbuf[commandlen], cbline, cbend - cbline);
		commandlen += cbend - cbline;
		commandbuf[commandlen] = '\n';
		if (commandlen + 1 < _MAXDEFSTR && cbend < &cb[cbsize])
			++commandlen;
		commandbuf[commandlen] = '\0';
	};
	XFree(cb);
#endif
};

cvector vhistory;
int histpos = 0;

void
history(int n)
{
	static bool rec = false;
	if (!rec && n >= 0 && n < vhistory.length()) {
		rec = true;
		execute(vhistory[vhistory.length() - n - 1]);
		rec = false;
	};
};

COMMAND(history, ARG_1INT);

void
keypress(int code, bool isdown, int cooked)
{
	if (saycommandon) // keystrokes go to commandline
	{
		if (isdown) {
			switch (code) {
			case SDLK_RETURN:
				break;

			case SDLK_BACKSPACE:
			case SDLK_LEFT: {
				for (int i = 0; commandbuf[i]; i++)
					if (!commandbuf[i + 1])
						commandbuf[i] = 0;
				resetcomplete();
				break;
			};

			case SDLK_UP:
				if (histpos)
					strcpy_s(
					    commandbuf, vhistory[--histpos]);
				break;

			case SDLK_DOWN:
				if (histpos < vhistory.length())
					strcpy_s(
					    commandbuf, vhistory[histpos++]);
				break;

			case SDLK_TAB:
				complete(commandbuf);
				break;

			case SDLK_v:
				if (SDL_GetModState() &
				    (KMOD_LCTRL | KMOD_RCTRL)) {
					pasteconsole();
					return;
				};

			default:
				resetcomplete();
				if (cooked) {
					char add[] = {(char)cooked, 0};
					strcat_s(commandbuf, add);
				};
			};
		} else {
			if (code == SDLK_RETURN) {
				if (commandbuf[0]) {
					if (vhistory.empty() ||
					    strcmp(
					        vhistory.last(), commandbuf)) {
						vhistory.add(newstring(
						    commandbuf)); // cap this?
					};
					histpos = vhistory.length();
					if (commandbuf[0] == '/')
						execute(commandbuf, true);
					else
						toserver(commandbuf);
				};
				saycommand(NULL);
			} else if (code == SDLK_ESCAPE) {
				saycommand(NULL);
			};
		};
	} else if (!menukey(code, isdown)) // keystrokes go to menu
	{
		loopi(numkm) if (keyms[i].code ==
		                 code) // keystrokes go to game, lookup in
		                       // keymap and execute
		{
			string temp;
			strcpy_s(temp, keyms[i].action);
			execute(temp, isdown);
			return;
		};
	};
};

char *
getcurcommand()
{
	return saycommandon ? commandbuf : NULL;
};

void
writebinds(FILE *f)
{
	loopi(numkm)
	{
		if (*keyms[i].action)
			fprintf(f, "bind \"%s\" [%s]\n", keyms[i].name,
			    keyms[i].action);
	};
};
