#include "ui_internal.h"
#include "sys_log.h"
#include "platform.h"
#include <stdlib.h>
#include "imgload.h"
#include "localisation.h"
#include <math.h>

#define max_colors 64
#define max_ids 128

static int nexttoken(char* buf, int64_t len, int64_t* pptr)
{
	while(*pptr < len && isspace((unsigned char)buf[*pptr])) *pptr += 1;
	if(*pptr >= len) { yrLog(0, "UI parse: unexpected eof"); return -1; }
	//check for comments
	if(buf[*pptr] == '/' &&
	   *pptr + 1 < len &&
	   buf[*pptr+1] == '/')
	{
		while(*pptr < len && buf[*pptr]!='\n') *pptr += 1;
		return nexttoken(buf, len, pptr);
	}
	return 0;
}

static char* readtokenstring(char* buf, int64_t len, int64_t* pptr)
{
	int64_t end = *pptr + 1;
	while(end < len && buf[end]!='"') end += 1;
	if(end >= len) { yrLog(0, "UI parse: unexpected eof"); return NULL; }

	int64_t toklen = end - *pptr - 1;
	char* out = malloc(toklen + 1);
	if(!out) { yrLog(0, "UI parse: out of memory"); return NULL; }
	out[toklen] = 0;
	memcpy(out, buf + *pptr + 1, toklen);
	
	*pptr = end + 1;
	return out;
}

static int istokenchar(int c)
{
	return (c > 0 && c <= 255 && isalnum(c)) || c=='_' || c=='#' || c=='.' || c=='-';
}

static char* readtoken(char* buf, int64_t len, int64_t* pptr)
{
	if(*pptr >= len) { yrLog(0, "UI parse: unexpected eof"); return NULL; }
	if(buf[*pptr]=='"') return readtokenstring(buf, len, pptr);

	int64_t end = *pptr;
	while(end < len && istokenchar(buf[end])) end += 1;

	int64_t toklen = end - *pptr;
	char* out = malloc(toklen + 1);
	if(!out) { yrLog(0, "UI parse: out of memory"); return NULL; }
	out[toklen] = 0;
	memcpy(out, buf + *pptr, toklen);
	
	*pptr = end;
	return out;
}

static uint32_t strtocolor(const char* s, yrStringmap* cmap);
static int read_colors(char* buf, int64_t len, int64_t* pptr, yrStringmap* colormap)
{
	int err = 0;
	char* name = NULL;
	char* scolor = NULL;
	uint32_t color = color_undefined;
	//'color('
	err = nexttoken(buf, len, pptr);		if(err) return -1;
	if(strncmp(buf+*pptr, "color", 5) != 0)	return 0; //done
	*pptr += 5;
	err = nexttoken(buf, len, pptr);		if(err) return -1;
	if(buf[*pptr] != '(')					{ yrLog(0, "UI parse: syntax error, expected 'color('"); return -1; }
	*pptr += 1;

	//read name and value
	err = nexttoken(buf, len, pptr);		if(err) return -1;
	name = readtoken(buf, len, pptr);		if(!name) return -1;
	err = nexttoken(buf, len, pptr);		if(err) return -1;
	if(buf[*pptr] != ',')					{ yrLog(0, "UI parse: syntax error, expected ','"); return -1; }
	*pptr += 1;
	err = nexttoken(buf, len, pptr);		if(err) return -1;
	scolor = readtoken(buf, len, pptr);		if(!scolor) return -1;
	err = nexttoken(buf, len, pptr);		if(err) return -1;
	if(buf[*pptr] != ')')					{ yrLog(0, "UI parse: syntax error, expected ')'"); return -1; }
	*pptr += 1;

	//save value
	err = yrStringmap_insert(colormap, name, (void*)(uint64_t) strtocolor(scolor, NULL));
	if(err) return 0;

	//next
	return read_colors(buf, len, pptr, colormap);
}

static enum UiElementType read_elemtype(char* buf, int64_t len, int64_t* pptr)
{
	if(strncmp(buf+*pptr, "rect",     4)==0) { *pptr += 4; return etRect; }
	if(strncmp(buf+*pptr, "image",    5)==0) { *pptr += 5; return etImage; }
	if(strncmp(buf+*pptr, "text",     4)==0) { *pptr += 4; return etText; }
	if(strncmp(buf+*pptr, "newline",  7)==0) { *pptr += 7; return etNewline; }
	if(strncmp(buf+*pptr, "bglist",   6)==0) { *pptr += 6; return etBgList; }
	if(strncmp(buf+*pptr, "check",    5)==0) { *pptr += 5; return etCheck; }
	if(strncmp(buf+*pptr, "edit",     4)==0) { *pptr += 4; return etEdit; }
	if(strncmp(buf+*pptr, "progress", 8)==0) { *pptr += 8; return etProgress; }
	if(strncmp(buf+*pptr, "mirror",   6)==0) { *pptr += 6; return etMirror; }
	yrLog(0, "UI parse: expected element type (rect, image, text, newline, bglist, check, edit, progress or mirror) near '%.16s'", buf+*pptr);
	return et_end;
}

static yrUiElemRect			parse_rect(		char* buf, int64_t len, int64_t* pptr, yrStringmap* colormap, yrStringmap* idmap);
static yrUiElemImage		parse_image(	char* buf, int64_t len, int64_t* pptr, yrStringmap* colormap, yrStringmap* idmap);
static yrUiElemText			parse_text(		char* buf, int64_t len, int64_t* pptr, yrStringmap* colormap, yrStringmap* idmap);
static yrUiElemNewline		parse_newline(	char* buf, int64_t len, int64_t* pptr, yrStringmap* colormap, yrStringmap* idmap);
static yrUiElemBgList		parse_bglist(	char* buf, int64_t len, int64_t* pptr, yrStringmap* colormap, yrStringmap* idmap);
static yrUiElemCheck		parse_check(	char* buf, int64_t len, int64_t* pptr, yrStringmap* colormap, yrStringmap* idmap);
static yrUiElemEdit			parse_edit(		char* buf, int64_t len, int64_t* pptr, yrStringmap* colormap, yrStringmap* idmap);
static yrUiElemProgress		parse_progress(	char* buf, int64_t len, int64_t* pptr, yrStringmap* colormap, yrStringmap* idmap);
static yrUiElemMirror		parse_mirror(	char* buf, int64_t len, int64_t* pptr, yrStringmap* colormap, yrStringmap* idmap);

int ui_parse(const char* f, yrUiScreen s)
{
	//dump file to buf
	int64_t ptr = 0;
	int64_t len = 0;
	char* buf = NULL;
	yrFile file = yrFile_open(f, yrF_read);
	if(!file) {
		yrLog(0, "Could not open ui definition file %s", f);
		return -1;
	}
	len = yrFile_seek(file, 0, yrF_seekend);
	yrFile_seek(file, 0, yrF_seekset);
	buf = malloc(len+1);
	if(!buf) {
		yrLog(0, "Out of memory for file read buffer during UI parse of file %s", f);
		yrFile_close(file);
		return -1;
	}
	buf[len] = 0;
	int64_t rw = yrFile_read(file, len, buf);
	yrFile_close(file);
	if(rw != len) {
		yrLog(0, "File read error during UI parse of file %s", f);
		free(buf);
		return -1;
	}

	//named colors
	int err = 0;
	yrStringmap* colormap = yrStringmap_create(max_colors);
	if(!colormap) goto onerror;
	err = read_colors(buf, len, &ptr, colormap);	if(err) goto onerror;

	//prepare idmap
	s->idmap = yrStringmap_create(max_ids);
	if(!s->idmap) goto onerror;

	//'screen() {'
	err = nexttoken(buf, len, &ptr);			if(err) goto onerror;
	if(strncmp(buf+ptr, "screen", 6) != 0)		{ yrLog(0, "UI parse: syntax error, expected 'screen() {' (%s)", f); goto onerror; }
	ptr += 6;
	err = nexttoken(buf, len, &ptr);			if(err) goto onerror;
	if(buf[ptr] != '(')							{ yrLog(0, "UI parse: syntax error, expected 'screen() {' (%s)", f); goto onerror; }
	ptr += 1;
	err = nexttoken(buf, len, &ptr);			if(err) goto onerror;
	if(buf[ptr] != ')')							{ yrLog(0, "UI parse: syntax error, expected 'screen() {' (%s)", f); goto onerror; }
	ptr += 1;
	err = nexttoken(buf, len, &ptr);			if(err) goto onerror;
	if(buf[ptr] != '{')							{ yrLog(0, "UI parse: syntax error, expected 'screen() {' (%s)", f); goto onerror; }
	ptr += 1;

	while((err = nexttoken(buf, len, &ptr))==0
		  && buf[ptr] != '}')
	{
		enum UiElementType t = read_elemtype(buf, len, &ptr);
		if(t == et_end) { err = 1; break; }
		yrUiElem e = NULL;
		switch(t) {
			case etRect:		e = (yrUiElem) parse_rect(buf, len, &ptr, colormap, s->idmap); break;
			case etImage:		e = (yrUiElem) parse_image(buf, len, &ptr, colormap, s->idmap); break;
			case etText:		e = (yrUiElem) parse_text(buf, len, &ptr, colormap, s->idmap); break;
			case etNewline:		e = (yrUiElem) parse_newline(buf, len, &ptr, colormap, s->idmap); break;
			case etBgList:		e = (yrUiElem) parse_bglist(buf, len, &ptr, colormap, s->idmap); break;
			case etCheck:		e = (yrUiElem) parse_check(buf, len, &ptr, colormap, s->idmap); break;
			case etEdit:		e = (yrUiElem) parse_edit(buf, len, &ptr, colormap, s->idmap); break;
			case etProgress:	e = (yrUiElem) parse_progress(buf, len, &ptr, colormap, s->idmap); break;
			case etMirror:		e = (yrUiElem) parse_mirror(buf, len, &ptr, colormap, s->idmap); break;
		}
		if(!e) { err = 1; break; }
		//stupid array realloc thing but there should only be a handful of root elements
		if(s->elems == NULL) {
			s->elems = malloc(sizeof(yrUiElem));
			s->elemcount = 1;
			s->elems[0] = e;
		} else {
			yrUiElem* newlist = realloc(s->elems, (s->elemcount + 1) * sizeof(yrUiElem));
			if(!newlist) {
				yrLog(0, "Out of memory");
				yrUiElem_destroy(e);
				err = 1;
				break;
			}
			s->elems = newlist;
			s->elems[s->elemcount] = e;
			s->elemcount += 1;
		}
	}
	if(err) goto onerror;

	//put all root elements next to eachother
	float xpos = 0.0f;
	for(size_t i = 0; i < s->elemcount; ++i) {
		s->elems[i]->x = xpos;
		xpos += s->elems[i]->w;
	}

	//cleanup
	yrStringmap_destroy(colormap, 0);
	free(buf);
	return 0;

onerror:
	if(s->idmap) yrStringmap_destroy(s->idmap, 0);
	s->idmap = NULL;
	if(colormap) yrStringmap_destroy(colormap, 0);
	free(buf);
	return -1;
}

static unsigned hexchartonum(int c)
{
	     if(c>='0' && c<='9') return c - '0';
	else if(c>='A' && c<='F') return c - 'A' + 10;
	else if(c>='a' && c<='f') return c - 'a' + 10;
	else { yrLog(1, "Invalid color value detected"); return 0;}
}

static int strtobool(const char* s)
{
	if(*s == 0) return 0;
	if(strcmp(s, "true") == 0) return 1;
	if(strcmp(s, "yes") == 0) return 1;
	if(strcmp(s, "t") == 0) return 1;
	if(strcmp(s, "y") == 0) return 1;
	return 0 != strtol(s, NULL, 10);
}

static enum UiTextAlign strtoalign(const char* s)
{
	if(*s == 0) return taLeft;
	if(strcmp(s, "l") == 0) return taLeft;
	if(strcmp(s, "r") == 0) return taRight;
	if(strcmp(s, "c") == 0) return taCenter;
	if(strcmp(s, "left") == 0) return taLeft;
	if(strcmp(s, "right") == 0) return taRight;
	if(strcmp(s, "center") == 0) return taCenter;
	yrLog(1, "Invalid text alignment value detected %s", s);
	return taLeft;
}

static float srgblookup[256] = {
	0.0f,0.07739938080495357f,0.15479876160990713f,0.23219814241486067f,0.30959752321981426f,0.38699690402476783f,0.46439628482972134f,0.541795665634675f,
	0.6191950464396285f,0.6965944272445821f,0.7739938080495357f,0.853366619794286f,0.9375093676320961f,1.0263028397165581f,1.1198177195396248f,1.218123137576901f,
	1.3212867590962885f,1.4293748641716943f,1.5424524208285488f,1.6605831521115912f,1.7838295977526737f,1.9122531710226747f,2.0459142112731734f,2.1848720326076734f,
	2.3291849690663007f,2.4789104166606517f,2.6341048725548686f,2.7948239716545027f,2.9611225208346905f,3.1330545310135474f,3.310673247254118f,3.49403117705887f,
	3.683180117003599f,3.878171177842741f,4.079054808204956f,4.285880816986283f,4.49869839453794f,4.7175561327368225f,4.942502044018609f,5.173583579446362f,
	5.4108476458809065f,5.654340622313782f,5.904108375418154f,6.160196274368713f,6.4226492049772155f,6.691511583186724f,6.966827367964098f,7.2486400736273024f,
	7.536992781641244f,7.8319281519133765f,8.133488433617941f,8.44171547557569f,8.756650736213954f,9.078335293130188f,9.40680985228051f,9.742114756813358f,
	10.084289995566873f,10.433375211247563f,10.789409708306467f,11.152432460528233f,11.522482118347263f,11.899597015904424f,12.283815177856802f,12.675174325952444f,
	13.073711885381025f,13.479464990910962f,13.892470492822804f,14.312764962648023f,14.740384698722144f,15.175365731560206f,15.61774382906249f,16.06755450155776f,
	16.524833006690972f,16.989614354162086f,17.461933310322042f,17.941824402631948f,18.42932192399096f,18.924459936938156f,19.4272722777335f,19.937792560322514f,
	20.456054180189422f,20.982090318102774f,21.515933943757947f,22.05761781932019f,22.607174502872102f,23.16463635176896f,23.7300355259053f,24.303403990896f,
	24.884773521174928f,25.47417570301404f,26.071641937465838f,26.67720344323157f,27.290891259458242f,27.91273624846633f,28.54276909841093f,29.1810203258783f,
	29.827520278420263f,30.482299137028132f,31.14538691854848f,31.816813478042373f,32.496608511090095f,33.18480155604289f,33.88142199622359f,34.58649906207744f,
	35.300061833275215f,36.02213924076927f,36.75276006880473f,37.49195295688672f,38.23974640170518f,38.996168759018296f,39.761248245495985f,40.5350129405245f,
	41.317490787973306f,42.10870959792524f,42.90869704837114f,43.71748068686976f,44.535087932174186f,45.36154607582542f,46.19688228371436f,47.041123597612454f,
	47.89429693667286f,48.756429098901805f,49.62754676260183f,50.50767648778696f,51.396844717571256f,52.295077779530814f,53.20240188704022f,54.11884314058408f,
	55.04442752904431f,55.97918093096365f,56.92312911578617f,57.876297745075085f,58.838712373709036f,59.81039845105633f,60.791381322129105f,61.78168622871649f,
	62.78133831049805f,63.790362606138125f,64.80878405436083f,65.83662749500694f,66.87391767007254f,67.92067922472992f,68.97693670833178f,70.04271457539808f,
	71.11803718658665f,72.20292880964799f,73.29741362036395f,74.40151570347165f,75.51525905357188f,76.63866757602301f,77.771765087821f,78.91457531846467f,
	80.06712191080797f,81.22942842189842f,82.40151832380243f,83.58341500441826f,84.77514176827573f,85.97672183732425f,87.18817835170853f,88.40953437053255f,
	89.64081287261203f,90.8820367572156f,92.13322884479493f,93.39441187770407f,94.66560852090802f,95.94684136268116f,97.23813291529518f,98.53950561569751f,
	99.85098182617944f,101.17258383503484f,102.50433385720993f,103.84625403494286f,105.19836643839545f,106.56069306627505f,107.93325584644828f,109.31607663654619f,
	110.70917722456097f,112.1125793294343f,113.5263046016381f,114.95037462374698f,116.38481091100319f,117.8296349118738f,119.28486800860054f,120.75053151774243f,
	122.22664669071084f,123.71323471429794f,125.21031671119808f,126.71791374052195f,128.236046798305f,129.76473681800812f,131.30400467101296f,132.85387116711036f,
	134.41435705498233f,135.98548302267884f,137.56726969808733f,139.15973764939758f,140.76290738556003f,142.3767993567383f,144.00143395475695f,145.63683151354263f,
	147.28301230956092f,148.93999656224685f,150.6078044344309f,152.28645603275965f,153.97597140811115f,155.6763705560055f,157.38767341701103f,159.10989987714441f,
	160.84306976826744f,162.58720286847833f,164.34231890249845f,166.10843754205516f,167.88557840625936f,169.67376106197938f,171.47300502421032f,173.28332975643892f,
	175.10475467100494f,176.93729912945741f,178.78098244290814f,180.63582387238012f,182.50184262915258f,184.37905787510218f,186.26748872304017f,188.16715423704636f,
	190.0780734327988f,192.00026527789984f,193.93374869219957f,195.8785425481143f,197.83466567094302f,199.80213683917935f,201.7809747848207f,203.7711981936743f,
	205.77282570565916f,207.7858759151058f,209.8103673710523f,211.84631857753692f,213.8937479938887f,215.9526740350138f,218.02311507167963f,220.10508943079608f,
	222.19861539569328f,224.30371120639708f,226.4203950599014f,228.54868511043793f,230.68859946974314f,232.84015620732188f,235.00337335071012f,237.17826888573305f,
	239.36486075676143f,241.56316686696596f,243.77320507856797f,245.9949932130886f,248.2285490515952f,250.47389033494488f,252.7310347640266f,255.0f,
};

static uint32_t strtocolor(const char* s, yrStringmap* cmap)
{
	if(*s == 0) return 0xFF000000ul;
	if(*s == '#') {
		s += 1;
		uint32_t out = 0;
		size_t len = strlen(s);
		if(len == 3 || len == 4) {
			float r,g,b,a;
			r = srgblookup[hexchartonum(s[0]) * 0x11];
			g = srgblookup[hexchartonum(s[1]) * 0x11];
			b = srgblookup[hexchartonum(s[2]) * 0x11];
			if(len == 4) {
				a = (hexchartonum(s[3]) * 0x11)/255.0f;
			} else {
				a = 1.0;
			}
			r *= a;
			g *= a;
			b *= a;
			a *= 255.0f;

			out |= ((uint8_t)r) << 0;
			out |= ((uint8_t)g) << 8;
			out |= ((uint8_t)b) << 16;
			out |= ((uint8_t)a) << 24;
		}
		else if(len == 6 || len == 8) {
			float r,g,b,a;
			r = srgblookup[(hexchartonum(s[0]) << 4) | hexchartonum(s[1])];
			g = srgblookup[(hexchartonum(s[2]) << 4) | hexchartonum(s[3])];
			b = srgblookup[(hexchartonum(s[4]) << 4) | hexchartonum(s[5])];
			if(len == 8) {
				a = ((hexchartonum(s[6]) << 4) | hexchartonum(s[7]))/255.0f;
			} else {
				a = 1.0;
			}
			r *= a;
			g *= a;
			b *= a;
			a *= 255.0f;

			out |= ((uint8_t)r) << 0;
			out |= ((uint8_t)g) << 8;
			out |= ((uint8_t)b) << 16;
			out |= ((uint8_t)a) << 24;
		}
		else yrLog(1, "Invalid color value detected");
		return out;
	}
	else if(cmap) {
		uint32_t c = (uint32_t)(uint64_t) yrStringmap_lookup(cmap, s);
		if(c == 0) yrLog(1, "Unknown color name detected");
		return c;
	}
	yrLog(1, "Invalid color value detected");
	return 0xFF000000ul;
}

static int default_attrib(yrUiElem e, char* k, char* v, yrStringmap* cmap, yrStringmap* idmap)
{
			if(strcmp(k, "w")==0)			e->w = strtof(v, NULL);
	else	if(strcmp(k, "h")==0)			e->h = strtof(v, NULL);
	else	if(strcmp(k, "bg")==0)			e->bg = strtocolor(v, cmap);
	else	if(strcmp(k, "bg_hover")==0)	e->bg_hover = strtocolor(v, cmap);
	else	if(strcmp(k, "bg_disabled")==0)	e->bg_disabled = strtocolor(v, cmap);
	else	if(strcmp(k, "bg_down")==0)		e->bg_down = strtocolor(v, cmap);
	else	if(strcmp(k, "fg")==0)			e->fg = strtocolor(v, cmap);
	else	if(strcmp(k, "fg_hover")==0)	e->fg_hover = strtocolor(v, cmap);
	else	if(strcmp(k, "fg_disabled")==0)	e->fg_disabled = strtocolor(v, cmap);
	else	if(strcmp(k, "fg_down")==0)		e->fg_down = strtocolor(v, cmap);
	else	if(strcmp(k, "id")==0)			{
		int err = yrStringmap_insert(idmap, v, e);
		if(err) yrLog(0, "UI parse: dropped id '%s', idmap is full", v);
	}
	else { yrLog(0, "UI parse: unknown attribute %s", k); return -1; }
	return 0;
}

static void yrUiElem_init(yrUiElem e, enum UiElementType t)
{
	switch(t) {
	case etRect:
		((yrUiElemRect)e)->type = etRect;
		((yrUiElemRect)e)->hpad = 0.0f;
		((yrUiElemRect)e)->vpad = 0.0f;
		((yrUiElemRect)e)->childcount = 0;
		((yrUiElemRect)e)->children = NULL;
		break;
	case etImage:
		((yrUiElemImage)e)->type = etImage;
		((yrUiElemImage)e)->tex = 0;
		((yrUiElemImage)e)->handle_tex = 0;
		break;
	case etText:
		((yrUiElemText)e)->type = etText;
		((yrUiElemText)e)->size = 1.0f;
		((yrUiElemText)e)->text = NULL;
		((yrUiElemText)e)->align = taLeft;
		((yrUiElemText)e)->singleline = 0;
		((yrUiElemText)e)->autowidth = 0;
		((yrUiElemText)e)->bitmap = 0;
		((yrUiElemText)e)->handle_bitmap = 0;
		break;
	case etNewline:
		((yrUiElemNewline)e)->type = etNewline;
		break;
	case etBgList: {
		yrUiElemBgList bgl = (yrUiElemBgList)e;
		memset(bgl, 0, sizeof(*bgl));
		bgl->type = etBgList;
		bgl->selected = BG_MAX_TEX;
		bgl->scale = 1.0f;
		bgl->ar = 1.0f;
		bgl->fg2 = 0xffffff00ul;
		break;}
	case etCheck:
		((yrUiElemCheck)e)->type = etCheck;
		break;
	case etEdit: {
		yrUiElemEdit i = (yrUiElemEdit)e;
		memset(i, 0, sizeof(*i));
		i->type = etEdit;
		i->align = taLeft;
		i->size = 1.0f;
		i->border = 1.0f;
		i->scale = 1.0f;
		i->ar = 1.0f;
		i->input_enabled = 0;
		i->capacity = 128;
		break;}
	case etProgress:
		((yrUiElemProgress)e)->type = etProgress;
		((yrUiElemProgress)e)->bitmap = 0;
		((yrUiElemProgress)e)->handle_bitmap = 0;
		((yrUiElemProgress)e)->bitmap_h = 0.0f;
		((yrUiElemProgress)e)->progress = 0.0f;
		break;
	case etMirror:
		((yrUiElemMirror)e)->type = etMirror;
		((yrUiElemMirror)e)->scale = 1.0f;
		((yrUiElemMirror)e)->ar = 1.0f;
		break;
	}

	e->x = 0.0f;
	e->y = 0.0f;
	e->w = 0.0f;
	e->h = 0.0f;
	e->bg = 0x00000000ul;
	e->bg_hover = color_undefined;
	e->bg_disabled = color_undefined;
	e->bg_down = color_undefined;
	e->fg = color_undefined;
	e->fg_hover = color_undefined;
	e->fg_disabled = color_undefined;
	e->fg_down = color_undefined;
	e->flags = UIELEM_ENABLED;
}

static void yrUiElem_initfinish(yrUiElem e)
{
	if(e->bg_hover == color_undefined) e->bg_hover = e->bg;
	if(e->bg_disabled == color_undefined) e->bg_disabled = e->bg;
	if(e->bg_down  == color_undefined) e->bg_down  = e->bg_hover;
	if(e->fg_hover == color_undefined) e->fg_hover = e->fg;
	if(e->fg_disabled == color_undefined) e->fg_disabled = e->fg;
	if(e->fg_down  == color_undefined) e->fg_down  = e->fg_hover;
	if(e->type == etEdit) {
		yrUiElemEdit i = (yrUiElemEdit)e;
		if(!i->capacity) yrLog(0, "0 capacity in edit element is not allowed");
		else {
			i->buffer = malloc(i->capacity+1);
			if(!i->buffer) yrLog(0, "Out of memory");
			else i->buffer[0] = 0;
		}
	}
}

static int yrUiElemRect_appendchildren(yrUiElemRect r, unsigned count, yrUiElem* list)
{
	if(count == 0) return 0;
	unsigned newcount = r->childcount + count;
	yrUiElem* newlist = (yrUiElem*) malloc(newcount * sizeof(yrUiElem));
	if(!newlist) { yrLog(0, "UI rect children parse: out of memory"); return -1; }
	memcpy(newlist, r->children, r->childcount * sizeof(yrUiElem));
	memcpy(newlist + r->childcount, list, count * sizeof(yrUiElem));
	free(r->children);
	r->children = newlist;
	r->childcount = newcount;
	return 0;
}

static yrUiElemRect parse_rect(char* buf, int64_t len, int64_t* pptr, yrStringmap* cmap, yrStringmap* idmap)
{
	int err = 0;
	char* key = NULL;
	char* val = NULL;
	//alloc init
	yrUiElemRect out = (yrUiElemRect) malloc(sizeof(struct _yr_ui_elem_rect));
	if(!out) { yrLog(0, "UI rect parse: out of memory"); return NULL; }
	yrUiElem_init((yrUiElem)out, etRect);

	//opening parens
	err = nexttoken(buf, len, pptr);	if(err) goto onerror;
	if(buf[*pptr] != '(')				{ yrLog(0, "UI rect parse: expected '(' near %.16s", buf + *pptr); goto onerror; }
	*pptr += 1;
	
	//parse attributes
	while((err = nexttoken(buf, len, pptr))==0
		  && buf[*pptr] != ')')
	{
		//read key and value
		key = readtoken(buf, len, pptr);		if(!key) { err = 1; break; }
		err = nexttoken(buf, len, pptr);		if(err) break;
		if(buf[*pptr] != '=')					{ yrLog(0, "UI rect parse: expected '=' near %.16s", buf + *pptr); goto onerror; }
		*pptr += 1;
		err = nexttoken(buf, len, pptr);		if(err) break;
		val = readtoken(buf, len, pptr);		if(!val) { err = 1; break; }
		//skip comma if any
		err = nexttoken(buf, len, pptr);		if(err) break;
		if(buf[*pptr] == ',') *pptr += 1;

		//apply
				if(strcmp(key, "hpad")==0)			out->hpad = strtof(val, NULL);
		else	if(strcmp(key, "vpad")==0)			out->vpad = strtof(val, NULL);
		else	err = default_attrib((yrUiElem) out, key, val, cmap, idmap);
		if(err) break;
		free(key); key = NULL;
		free(val); val = NULL;
	}
	if(err) goto onerror;
	yrUiElem_initfinish((yrUiElem)out);

	//closing parens and opening brace
	if(buf[*pptr] != ')')				{ yrLog(0, "UI rect parse: expected ')' near %.16s", buf + *pptr); goto onerror; }
	*pptr += 1;
	err = nexttoken(buf, len, pptr);	if(err) goto onerror;	
	if(buf[*pptr] == '{')
	{
		*pptr += 1;
	
		//parse children
		#define cbufsize 32
		yrUiElem cbuf[cbufsize];
		unsigned ccount = 0;

		while((err = nexttoken(buf, len, pptr))==0
			  && buf[*pptr] != '}')
		{
			enum UiElementType t = read_elemtype(buf, len, pptr);
			if(t == et_end) { err = 1; break; }
			yrUiElem e = NULL;
			switch(t) {
				case etRect:		e = (yrUiElem)parse_rect(buf, len, pptr, cmap, idmap); break;
				case etImage:		e = (yrUiElem)parse_image(buf, len, pptr, cmap, idmap); break;
				case etText:		e = (yrUiElem)parse_text(buf, len, pptr, cmap, idmap); break;
				case etNewline:		e = (yrUiElem)parse_newline(buf, len, pptr, cmap, idmap); break;
				case etBgList:		e = (yrUiElem)parse_bglist(buf, len, pptr, cmap, idmap); break;
				case etCheck:		e = (yrUiElem)parse_check(buf, len, pptr, cmap, idmap); break;
				case etEdit:		e = (yrUiElem)parse_edit(buf, len, pptr, cmap, idmap); break;
				case etProgress:	e = (yrUiElem)parse_progress(buf, len, pptr, cmap, idmap); break;
				case etMirror:		e = (yrUiElem)parse_mirror(buf, len, pptr, cmap, idmap); break;
			}
			if(!e) { err = 1; break; }
			//append to children buffer
			cbuf[ccount] = e;
			ccount += 1;
			//flush children buffer
			if(ccount == cbufsize) {
				err = yrUiElemRect_appendchildren(out, ccount, cbuf);
				ccount = 0;
				if(err) break;
			}
		}

		if(!err) err = yrUiElemRect_appendchildren(out, ccount, cbuf);
		if(err) {
			for(unsigned i = 0; i < ccount; ++i) {
				yrUiElem_destroy(cbuf[i]);
			}
			goto onerror;
		}
		#undef cbufsize

		if(buf[*pptr] != '}')		{ yrLog(0, "UI rect parse: expected '}' near %.16s", buf + *pptr); goto onerror; }
		*pptr += 1;
	}

	//done
	return out;
onerror:
	free(key);
	free(val);
	if(out) for(unsigned i = 0; i < out->childcount; ++i) 
		yrUiElem_destroy(out->children[i]);
	free(out);
	return NULL;
}

GLuint loadimgtex(const char* path)
{
	yrFile f = NULL;
	size_t rw;
	size_t pnglen;
	void* pngdata = NULL;
	unsigned int width = 0;
	unsigned int height = 0;
	void* imgdata = 0;
	GLuint out = 0;

	//open and read file
	f = yrFile_open(path, yrF_read);				if(!f) { yrLog(0, "Could not open ui texture file for reading", path); goto onerror; }
	pnglen = yrFile_seek(f, 0, yrF_seekend);
	yrFile_seek(f, 0, yrF_seekset);
	pngdata = malloc(pnglen);						if(!pngdata) { yrLog(0, "Out of memory"); goto onerror; }
	rw = yrFile_read(f, (int64_t) pnglen, pngdata);	if(rw != pnglen) { yrLog(0, "Read error"); goto onerror; }
	yrFile_close(f);
	f = NULL;

	//decode png
	const char* errtxt = yrImgLoad_decode(pnglen, pngdata, &width, &height, &imgdata);
	free(pngdata);
	pngdata = NULL;
	if(errtxt) {
		yrLog(0, "Error while decoding ui texture: %s", errtxt);
		goto onerror;
	}

	//create texture & gen mip
	glGenTextures(1, &out);
	glBindTexture(GL_TEXTURE_2D, out);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
	glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, 16.0f);
	GLint l = (GLint) ceilf(log2f((float)((width>height)?width:height)));
	glTexStorage2D(GL_TEXTURE_2D, l, GL_SRGB8_ALPHA8, width, height);
	glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8_REV, imgdata);
	glGenerateMipmap(GL_TEXTURE_2D);
	glBindTexture(GL_TEXTURE_2D, 0);
	GLenum glerr = glGetError();
	if(glerr != GL_NO_ERROR) {
		yrLog(0, "OpenGL error during ui texture creation or loading: %x", glerr);
		goto onerror;
	}

	//done
	free(imgdata);
	return out;

onerror:
	yrLog(0, "Failed to load ui texture %s.", path);
	if(out) glDeleteTextures(1, &out);
	free(imgdata);
	free(pngdata);
	if(f) yrFile_close(f);
	return 0;
}

static uint64_t getimghandle(GLuint img)
{
	uint64_t handle = glGetTextureHandleARB(img);
	glMakeTextureHandleResidentARB(handle);
	return handle;
}


static yrUiElemImage parse_image(char* buf, int64_t len, int64_t* pptr, yrStringmap* cmap, yrStringmap* idmap)
{
	int err = 0;
	char* key = NULL;
	char* val = NULL;
	//alloc init
	yrUiElemImage out = (yrUiElemImage) malloc(sizeof(struct _yr_ui_elem_image));
	if(!out) { yrLog(0, "UI image parse: out of memory"); return NULL; }
	yrUiElem_init((yrUiElem)out, etImage);

	//opening parens
	err = nexttoken(buf, len, pptr);		if(err) goto onerror;
	if(buf[*pptr] != '(')				{ yrLog(0, "UI image parse: expected '(' near %.16s", buf + *pptr); goto onerror; }
	*pptr += 1;
	
	//parse attributes
	while((err = nexttoken(buf, len, pptr))==0
		  && buf[*pptr] != ')')
	{
		//read key and value
		key = readtoken(buf, len, pptr);		if(!key) { err = 1; break; }
		err = nexttoken(buf, len, pptr);		if(err) break;
		if(buf[*pptr] != '=')					{ yrLog(0, "UI image parse: expected '=' near %.16s", buf + *pptr); goto onerror; }
		*pptr += 1;
		err = nexttoken(buf, len, pptr);		if(err) break;
		val = readtoken(buf, len, pptr);		if(!val) { err = 1; break; }
		//skip comma if any
		err = nexttoken(buf, len, pptr);		if(err) break;
		if(buf[*pptr] == ',') *pptr += 1;

		//apply
		if(strcmp(key, "src")==0) {
			out->tex = loadimgtex(val);
			if(!out->tex) { err = 1; break; }
			out->handle_tex = getimghandle(out->tex);
		}
		else {
			err = default_attrib((yrUiElem)out, key, val, cmap, idmap);
			if(err) break;
		}
		free(key); key = NULL;
		free(val); val = NULL;
	}
	if(err) goto onerror;
	yrUiElem_initfinish((yrUiElem)out);
	if(buf[*pptr] != ')')				{ yrLog(0, "UI image parse: expected ')' near %.16s", buf + *pptr); goto onerror; }
	*pptr += 1;

	//done
	return out;
onerror:
	free(key);
	free(val);
	if(out && out->tex)
		glDeleteTextures(1, &out->tex);
	free(out);
	return NULL;
}

static yrUiElemText parse_text(char* buf, int64_t len, int64_t* pptr, yrStringmap* cmap, yrStringmap* idmap)
{
	int err = 0;
	char* key = NULL;
	char* val = NULL;
	//alloc init
	yrUiElemText out = (yrUiElemText) malloc(sizeof(struct _yr_ui_elem_text));
	if(!out) { yrLog(0, "UI text parse: out of memory"); return NULL; }
	yrUiElem_init((yrUiElem)out, etText);

	//opening parens
	err = nexttoken(buf, len, pptr);	if(err) goto onerror;
	if(buf[*pptr] != '(')				{ yrLog(0, "UI text parse: expected '(' near %.16s", buf + *pptr); goto onerror; }
	*pptr += 1;
	
	//parse attributes
	while((err = nexttoken(buf, len, pptr))==0
		  && buf[*pptr] != ')')
	{
		//read key and value
		key = readtoken(buf, len, pptr);		if(!key) { err = 1; break; }
		err = nexttoken(buf, len, pptr);		if(err) break;
		if(buf[*pptr] != '=')					{ yrLog(0, "UI text parse: expected '=' near %.16s", buf + *pptr); goto onerror; }
		*pptr += 1;
		err = nexttoken(buf, len, pptr);		if(err) break;
		val = readtoken(buf, len, pptr);		if(!val) { err = 1; break; }
		//skip comma if any
		err = nexttoken(buf, len, pptr);		if(err) break;
		if(buf[*pptr] == ',') *pptr += 1;

		//apply
				if(strcmp(key, "size")==0)			out->size = strtof(val, NULL);
		else	if(strcmp(key, "align")==0)			out->align = strtoalign(val);
		else	if(strcmp(key, "txt")==0)			out->text = _strdup(yrLocalise_get_string(val));
		else	if(strcmp(key, "singleline")==0)	out->singleline = strtobool(val);
		else	if(strcmp(key, "autowidth")==0)		out->autowidth = strtobool(val);
		else	err = default_attrib((yrUiElem)out, key, val, cmap, idmap);
		if(err) break;
		free(key); key = NULL;
		free(val); val = NULL;
	}
	if(err) goto onerror;
	yrUiElem_initfinish((yrUiElem)out);
	if(buf[*pptr] != ')')				{ yrLog(0, "UI text parse: expected ')' near %.16s", buf + *pptr); goto onerror; }
	*pptr += 1;

	//require text
	if(out->text == NULL) {
		yrLog(0, "UI text parse: text element requires 'txt' attribute to be set");
		goto onerror;
	}

	//done
	return out;
onerror:
	free(key);
	free(val);
	if(out && out->text)
		free(out->text);
	free(out);
	return NULL;
}

static yrUiElemNewline parse_newline(char* buf, int64_t len, int64_t* pptr, yrStringmap* cmap, yrStringmap* idmap)
{
	int err = 0;
	char* key = NULL;
	char* val = NULL;
	//alloc init
	yrUiElemNewline out = (yrUiElemNewline) malloc(sizeof(struct _yr_ui_elem_newline));
	if(!out) { yrLog(0, "UI newline parse: out of memory"); return NULL; }
	yrUiElem_init((yrUiElem)out, etNewline);

	//opening parens
	err = nexttoken(buf, len, pptr);		if(err) goto onerror;
	if(buf[*pptr] != '(')				{ yrLog(0, "UI newline parse: expected '(' near %.16s", buf + *pptr); goto onerror; }
	*pptr += 1;
	
	//parse attributes
	while((err = nexttoken(buf, len, pptr))==0
		  && buf[*pptr] != ')')
	{
		//read key and value
		key = readtoken(buf, len, pptr);		if(!key) { err = 1; break; }
		err = nexttoken(buf, len, pptr);		if(err) break;
		if(buf[*pptr] != '=')					{ yrLog(0, "UI newline parse: expected '=' near %.16s", buf + *pptr); goto onerror; }
		*pptr += 1;
		err = nexttoken(buf, len, pptr);		if(err) break;
		val = readtoken(buf, len, pptr);		if(!val) { err = 1; break; }
		//skip comma if any
		err = nexttoken(buf, len, pptr);		if(err) break;
		if(buf[*pptr] == ',') *pptr += 1;

		//apply
		err = default_attrib((yrUiElem)out, key, val, cmap, idmap);
		if(err) break;

		free(key); key = NULL;
		free(val); val = NULL;
	}
	if(err) goto onerror;
	yrUiElem_initfinish((yrUiElem)out);
	if(buf[*pptr] != ')')				{ yrLog(0, "UI newline parse: expected ')' near %.16s", buf + *pptr); goto onerror; }
	*pptr += 1;

	//done
	return out;
onerror:
	free(key);
	free(val);
	free(out);
	return NULL;
}

static yrUiElemMirror parse_mirror(char* buf, int64_t len, int64_t* pptr, yrStringmap* cmap, yrStringmap* idmap)
{
	int err = 0;
	char* key = NULL;
	char* val = NULL;
	//alloc init
	yrUiElemMirror out = (yrUiElemMirror) malloc(sizeof(struct _yr_ui_elem_mirror));
	if(!out) { yrLog(0, "UI mirror parse: out of memory"); return NULL; }
	yrUiElem_init((yrUiElem)out, etMirror);

	//opening parens
	err = nexttoken(buf, len, pptr);		if(err) goto onerror;
	if(buf[*pptr] != '(')				{ yrLog(0, "UI mirror parse: expected '(' near %.16s", buf + *pptr); goto onerror; }
	*pptr += 1;
	
	//parse attributes
	while((err = nexttoken(buf, len, pptr))==0
		  && buf[*pptr] != ')')
	{
		//read key and value
		key = readtoken(buf, len, pptr);		if(!key) { err = 1; break; }
		err = nexttoken(buf, len, pptr);		if(err) break;
		if(buf[*pptr] != '=')					{ yrLog(0, "UI mirror parse: expected '=' near %.16s", buf + *pptr); goto onerror; }
		*pptr += 1;
		err = nexttoken(buf, len, pptr);		if(err) break;
		val = readtoken(buf, len, pptr);		if(!val) { err = 1; break; }
		//skip comma if any
		err = nexttoken(buf, len, pptr);		if(err) break;
		if(buf[*pptr] == ',') *pptr += 1;

		//apply
		err = default_attrib((yrUiElem)out, key, val, cmap, idmap);
		if(err) break;

		free(key); key = NULL;
		free(val); val = NULL;
	}
	if(err) goto onerror;
	yrUiElem_initfinish((yrUiElem)out);
	if(buf[*pptr] != ')')				{ yrLog(0, "UI mirror parse: expected ')' near %.16s", buf + *pptr); goto onerror; }
	*pptr += 1;

	//done
	return out;
onerror:
	free(key);
	free(val);
	free(out);
	return NULL;
}

static yrUiElemCheck parse_check(char* buf, int64_t len, int64_t* pptr, yrStringmap* cmap, yrStringmap* idmap)
{
	int err = 0;
	char* key = NULL;
	char* val = NULL;
	//alloc init
	yrUiElemCheck out = (yrUiElemCheck) malloc(sizeof(struct _yr_ui_elem_check));
	if(!out) { yrLog(0, "UI check parse: out of memory"); return NULL; }
	yrUiElem_init((yrUiElem)out, etCheck);

	//opening parens
	err = nexttoken(buf, len, pptr);		if(err) goto onerror;
	if(buf[*pptr] != '(')				{ yrLog(0, "UI check parse: expected '(' near %.16s", buf + *pptr); goto onerror; }
	*pptr += 1;
	
	//parse attributes
	while((err = nexttoken(buf, len, pptr))==0
		  && buf[*pptr] != ')')
	{
		//read key and value
		key = readtoken(buf, len, pptr);		if(!key) { err = 1; break; }
		err = nexttoken(buf, len, pptr);		if(err) break;
		if(buf[*pptr] != '=')					{ yrLog(0, "UI check parse: expected '=' near %.16s", buf + *pptr); goto onerror; }
		*pptr += 1;
		err = nexttoken(buf, len, pptr);		if(err) break;
		val = readtoken(buf, len, pptr);		if(!val) { err = 1; break; }
		//skip comma if any
		err = nexttoken(buf, len, pptr);		if(err) break;
		if(buf[*pptr] == ',') *pptr += 1;

		//apply
		err = default_attrib((yrUiElem)out, key, val, cmap, idmap);
		if(err) break;

		free(key); key = NULL;
		free(val); val = NULL;
	}
	if(err) goto onerror;
	yrUiElem_initfinish((yrUiElem)out);
	if(buf[*pptr] != ')')				{ yrLog(0, "UI check parse: expected ')' near %.16s", buf + *pptr); goto onerror; }
	*pptr += 1;

	//done
	return out;
onerror:
	free(key);
	free(val);
	free(out);
	return NULL;
}

static yrUiElemEdit parse_edit(char* buf, int64_t len, int64_t* pptr, yrStringmap* cmap, yrStringmap* idmap)
{
	int err = 0;
	char* key = NULL;
	char* val = NULL;
	//alloc init
	yrUiElemEdit out = (yrUiElemEdit) malloc(sizeof(struct _yr_ui_elem_edit));
	if(!out) { yrLog(0, "UI edit parse: out of memory"); return NULL; }
	yrUiElem_init((yrUiElem)out, etEdit);

	//opening parens
	err = nexttoken(buf, len, pptr);		if(err) goto onerror;
	if(buf[*pptr] != '(')				{ yrLog(0, "UI edit parse: expected '(' near %.16s", buf + *pptr); goto onerror; }
	*pptr += 1;
	
	//parse attributes
	while((err = nexttoken(buf, len, pptr))==0
		  && buf[*pptr] != ')')
	{
		//read key and value
		key = readtoken(buf, len, pptr);		if(!key) { err = 1; break; }
		err = nexttoken(buf, len, pptr);		if(err) break;
		if(buf[*pptr] != '=')					{ yrLog(0, "UI edit parse: expected '=' near %.16s", buf + *pptr); goto onerror; }
		*pptr += 1;
		err = nexttoken(buf, len, pptr);		if(err) break;
		val = readtoken(buf, len, pptr);		if(!val) { err = 1; break; }
		//skip comma if any
		err = nexttoken(buf, len, pptr);		if(err) break;
		if(buf[*pptr] == ',') *pptr += 1;

		//apply
				if(strcmp(key, "size")==0)			out->size = strtof(val, NULL);
		else	if(strcmp(key, "align")==0)			out->align = strtoalign(val);
		else	if(strcmp(key, "capacity")==0)		out->capacity = strtoul(val, NULL, 10);
		else	if(strcmp(key, "border")==0)		out->border = strtof(val, NULL);
		else	err = default_attrib((yrUiElem)out, key, val, cmap, idmap);
		if(err) break;

		free(key); key = NULL;
		free(val); val = NULL;
	}
	if(err) goto onerror;
	yrUiElem_initfinish((yrUiElem)out);
	if(buf[*pptr] != ')')				{ yrLog(0, "UI edit parse: expected ')' near %.16s", buf + *pptr); goto onerror; }
	*pptr += 1;

	//done
	return out;
onerror:
	free(key);
	free(val);
	free(out);
	return NULL;
}

static yrUiElemProgress parse_progress(char* buf, int64_t len, int64_t* pptr, yrStringmap* cmap, yrStringmap* idmap)
{
	int err = 0;
	char* key = NULL;
	char* val = NULL;
	//alloc init
	yrUiElemProgress out = (yrUiElemProgress) malloc(sizeof(struct _yr_ui_elem_progress));
	if(!out) { yrLog(0, "UI progress parse: out of memory"); return NULL; }
	yrUiElem_init((yrUiElem)out, etProgress);

	//opening parens
	err = nexttoken(buf, len, pptr);		if(err) goto onerror;
	if(buf[*pptr] != '(')				{ yrLog(0, "UI progress parse: expected '(' near %.16s", buf + *pptr); goto onerror; }
	*pptr += 1;
	
	//parse attributes
	while((err = nexttoken(buf, len, pptr))==0
		  && buf[*pptr] != ')')
	{
		//read key and value
		key = readtoken(buf, len, pptr);		if(!key) { err = 1; break; }
		err = nexttoken(buf, len, pptr);		if(err) break;
		if(buf[*pptr] != '=')					{ yrLog(0, "UI progress parse: expected '=' near %.16s", buf + *pptr); goto onerror; }
		*pptr += 1;
		err = nexttoken(buf, len, pptr);		if(err) break;
		val = readtoken(buf, len, pptr);		if(!val) { err = 1; break; }
		//skip comma if any
		err = nexttoken(buf, len, pptr);		if(err) break;
		if(buf[*pptr] == ',') *pptr += 1;

		//apply
		err = default_attrib((yrUiElem)out, key, val, cmap, idmap);
		if(err) break;

		free(key); key = NULL;
		free(val); val = NULL;
	}
	if(err) goto onerror;
	yrUiElem_initfinish((yrUiElem)out);
	if(buf[*pptr] != ')')				{ yrLog(0, "UI progress parse: expected ')' near %.16s", buf + *pptr); goto onerror; }
	*pptr += 1;

	//done
	return out;
onerror:
	free(key);
	free(val);
	free(out);
	return NULL;
}

static yrUiElemBgList parse_bglist(char* buf, int64_t len, int64_t* pptr, yrStringmap* cmap, yrStringmap* idmap)
{
	int err = 0;
	char* key = NULL;
	char* val = NULL;
	//alloc init
	yrUiElemBgList out = (yrUiElemBgList) malloc(sizeof(struct _yr_ui_elem_bglist));
	if(!out) { yrLog(0, "UI bglist parse: out of memory"); return NULL; }
	yrUiElem_init((yrUiElem)out, etBgList);

	//opening parens
	err = nexttoken(buf, len, pptr);		if(err) goto onerror;
	if(buf[*pptr] != '(')				{ yrLog(0, "UI bglist parse: expected '(' near %.16s", buf + *pptr); goto onerror; }
	*pptr += 1;
	
	//parse attributes
	while((err = nexttoken(buf, len, pptr))==0
		  && buf[*pptr] != ')')
	{
		//read key and value
		key = readtoken(buf, len, pptr);		if(!key) { err = 1; break; }
		err = nexttoken(buf, len, pptr);		if(err) break;
		if(buf[*pptr] != '=')					{ yrLog(0, "UI bglist parse: expected '=' near %.16s", buf + *pptr); goto onerror; }
		*pptr += 1;
		err = nexttoken(buf, len, pptr);		if(err) break;
		val = readtoken(buf, len, pptr);		if(!val) { err = 1; break; }
		//skip comma if any
		err = nexttoken(buf, len, pptr);		if(err) break;
		if(buf[*pptr] == ',') *pptr += 1;

		//apply
				if(strcmp(key, "fg2")==0)			out->fg2 = strtocolor(val, cmap);
		else	err = default_attrib((yrUiElem)out, key, val, cmap, idmap);
		if(err) break;

		free(key); key = NULL;
		free(val); val = NULL;
	}
	if(err) goto onerror;
	yrUiElem_initfinish((yrUiElem)out);
	if(buf[*pptr] != ')')				{ yrLog(0, "UI bglist parse: expected ')' near %.16s", buf + *pptr); goto onerror; }
	*pptr += 1;

	//scroll arrow
	out->scroll_icon = loadimgtex("data/ui/scroll_up.png");
	out->handle_scroll_icon = getimghandle(out->scroll_icon);

	//done
	return out;
onerror:
	free(key);
	free(val);
	free(out);
	return NULL;
}
