#include <stdio.h>
#include <assert.h>
#include <ctype.h>
#include <stdlib.h>
#include <string.h>
#include "gpklib.h"

#define D if(0)

static const char hex[] = "0123456789abcdef";

struct specials_s { unsigned e; int d;};
struct specials_s _specials[] = {
	{' ', '_'}, {'u', '_'}, {'p', '.'},
	{'m', ','}, {'s', ';'}, {'z', '='},
	{'t', '\t'}, {'Z', '\033'},
	{'M', '&'}, {'l', '/'}, {'K', '\\'},
	{'k', '\b'}, {'R', '\r'}, {'L', '\n'},
	{'q', '"'}, {'Q', '?'}, {'U', '\''},
	{'S', ' '}, {'P', '#'},
	{0,-1}
	};

LOCAL char allowed[] = "qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM"
			"1234567890_!@$^&*()+={}[]|:'\"?/>.<,-" ;

C LOCAL int NCHAR = 256;

#define U(x)	(0377 & (unsigned)(x))

LOCAL void encode_init_spc(int *spc)
{
	int i;
	for(i=0; i<NCHAR; i++)
		spc[i] = -1;
	for(i=0; _specials[i].d >= 0; i++)
		spc[U( _specials[i].d)] = U( _specials[i].e );
}

LOCAL void encode_init_alwd(BOOL *alwd)
{
	int i;
	for(i=0; i<NCHAR; i++)
		alwd[i] = FALSE;
	for(i=0; allowed[i]; i++)
		alwd[U(allowed[i])] = TRUE;
}


char *encode_av(const char *v)
{
	LOCAL BOOL initialized = FALSE;
	LOCAL int spc[NCHAR];
	LOCAL BOOL alwd[NCHAR];

	if(!v || !*v) {
		return strdup("%mt");
	}
	if(!initialized) {
		encode_init_spc(spc);
		encode_init_alwd(alwd);
		initialized = TRUE;
	}
	char *rv = (char *)malloc(3*strlen(v)+1);
	assert(rv);
	char *p;
	for(p=rv; *v; v++,p++) {
		D printf("Encode %d(%c) ", U(*v), *v);
		if(alwd[U(*v)])	{
			*p = *v;
			D printf(" into %c\n", *p);
		}
		else	{
			*p++ = '%';
			C unsigned code = U(*v);
			*p++ = hex[code/16];
			*p = hex[code%16];
			D printf(" into %%%c%c\n", p[-1], *p);
		}
	}
	*p = '\0';
	return rv;
}


LOCAL void decode_init(int *spc)
{
	int i;
	for(i=0; i<NCHAR; i++) {
		spc[i] = -1;
	}
	for(i=0; _specials[i].d >= 0; i++)
		spc[_specials[i].e] = _specials[i].d;
}


char *decode_av(const char *s)
{
	static BOOL initialized = FALSE;
	static int spc[NCHAR];
	assert(s);
	if(!initialized) {
		decode_init(spc);
		initialized = TRUE;
		}
	D printf("decode: s=%s\n", s);
	if(s[0]=='%' && s[1]=='m' && s[2]=='t' && s[3]=='\0')
		return strdup("");

	char *rv = (char *)malloc(strlen(s)+1);
	assert(rv);
	char *p = rv;
	while(*s) {
		D printf("D %d(%c) ", U(*s), *s);
		if(*s == '%') {
			if(!s[1]) {
				fprintf(stderr, "WARN: decode_av: empty '%%' escape.");
				free(rv);
				return 0;
			}
			else if(spc[U(s[1])] >= 0) {
				s++;
				*p++ = (char) ( spc[U(*s)] );
				D printf(" %d(%c) to %d\n", U(*s), *s, U(p[-1]));
				s++;
			}
			else if(!s[2]) {
				fprintf(stderr, "WARN: decode_av: unfinished '%%' escape.");
				free(rv);
				return 0;
			}
			else if( isxdigit(s[1]) && isxdigit(s[2])) {
				char buf[3];
				s++;
				buf[0] = *s++;
				buf[1] = *s++;
				buf[2] = '\0';
				*p++ = strtol(buf, 0, 16);
				D printf(" %c%c to %d\n", s[-2], s[-1], U(p[-1]));
			}
			else	{
				fprintf(stderr, "WARN: decode_av:"
					" Bad chars in '%%' escape: %d %d",
					s[1], s[2]);
				free(rv);
				return 0;
			}
		}
		else {
			D printf(" to %c\n", *s);
			*p++ = *s++;
			}
	}
	*p = '\0';
	return rv;
}



LOCAL int print_av_noterm(FILE *f, const char *a, const char *v)
{
	int rv;
	assert(a);
	assert(f);
	if(v == 0)
		fprintf(stderr, "WARN: nil value for attribute {%s}\n", a);
	char *s = encode_av(v?v:"");
	assert(s);
	rv = fprintf(f, "%s=%s", a, s);
	free(s);
	return rv;
}

int print_av(FILE *f, const char *a, const char *v)
{
	int rv = print_av_noterm(f, a, v);
	if(rv == EOF)
		return EOF;
	if( putc(';', f) == EOF)
		return EOF;
	return rv+1;
}


int print_avc(FILE *f, const char *a, const char *v, const char *c)
{
	if( !c )
		return print_av(f, a, v);

	int rv1 = print_av_noterm(f, a, v);
	if( rv1 == EOF)
		return EOF;
	int rv2 = fprintf(f, " # %s", c);
	if( rv2 == EOF)
		return EOF;
	return rv1 + rv2;
}




LOCAL void test()
{
	int j;
	for(j=0; j<3000; j++) {
		int i;
		C int n = (j+8)/10;
		char *se, *sed;
		char *s = (char *)malloc(n+1);
		for(i=0; i<n; i++)
			s[i] = U( ((i*17+97+i*n+(i&n))^(i*197+13+(i|n))) );
		s[i] = '\0';
		se = encode_av(s);
		sed = decode_av(se);
		assert(sed != 0);
		D printf("n=%d s[0]=%d <%s> <%s> <%s>\n", n, s[0], s, se, sed);
		assert(strcmp(s, sed) == 0);
		free(s);
		free(se);
		free(sed);
	}
}

#ifdef TEST
int main(int argc, char **argv)
{
	test();
}
#endif
