/*
    eprintf.c - extended printf
    Copyright (C) 2002-2020  Ladislav Vaiz <ok1zia@nagano.cz>

    This program is free software; you can redistribute it and/or
    modify it under the terms of the GNU General Public License
    version 2 as published by the Free Software Foundation.

*/

#include <ctype.h>
#include <stdarg.h>
#include <stdint.h>
#include <string.h>
#include <glib.h>

static char *base64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; 
static char *hexchars = "0123456789abcdef";

int zg_string_veprintfa(const char *flags, GString *gs, char *fmt, va_list l){
	int oldlen, len, i;
	char *add, *c, *d, *fmt2, type;
	unsigned char *bin;
	GString *gs2;
	
	oldlen = gs->len;
	fmt2 = g_new(char, strlen(fmt) + 1);
	for (c = fmt; *c != '\0'; c++){
		int lcnt = 0;
		if (*c != '%'){
			g_string_append_c(gs, *c);
			continue;
		}
		type = '\0';
		for (d = fmt2; *c != '\0'; c++, d++){
			*d = *c;
			if (!isalpha(*c)) continue;
			if (*c == 'l') {
				lcnt++;
				continue;
			}
			type = *c;
			*d = tolower(*d);
			d++;
			*d = '\0';
			break;
		}
		add = NULL;
		switch (tolower(type)){
			case 'b': // base64
				bin = va_arg(l, unsigned char *);
				len = va_arg(l, int);
				for (i = 0; i < len; i+= 3){
					g_string_append_c(gs, base64[ bin[i] >> 2 ]); // output char 0
					if (i + 1 < len){
						g_string_append_c(gs, base64[ ((bin[i] & 0x03) << 4) | ((bin[i + 1] & 0xf0) >> 4)]); // output char 1
						if (i + 2 < len){
							g_string_append_c(gs, base64[ ((bin[i + 1] & 0x0f) << 2) | ((bin[i + 2] & 0xc0) >> 6)]); // output char 2
							g_string_append_c(gs, base64[ bin[i + 2] & 0x3f ]); // output char 3
						}else{
							g_string_append_c(gs, base64[ (bin[i + 1] & 0x0f) << 2]); // output char 2
							g_string_append_c(gs, '='); //output char 3
						}
					}else{
						g_string_append_c(gs, base64[ ((bin[i] & 0x03) << 4) ]); // output char 1
						g_string_append(gs, "=="); // output chars 2,3
					}
				}
				break;
			case 'y': // hexa
				bin = va_arg(l, unsigned char *);
				len = va_arg(l, int);
				for (i = 0; i < len; i++){
					g_string_append_c(gs, hexchars[ bin[i] >> 4]); 
					g_string_append_c(gs, hexchars[ bin[i] & 0x0f]); 
				}
			case 'c':
				add = g_strdup_printf(fmt2, va_arg(l, int)); // char is promoted to int when passed through ...
				break;
			case 'd':
			case 'i':
			case 'u':
			case 'x':
				if (lcnt == 2){
					add = g_strdup_printf(fmt2, va_arg(l, long long));
				}else if (lcnt == 1){
					add = g_strdup_printf(fmt2, va_arg(l, long));
				}else{
					add = g_strdup_printf(fmt2, va_arg(l, int));
				}
				break;
			case 'f':
				add = g_strdup_printf(fmt2, va_arg(l, double));
				break;
            case 'p':
                add = g_strdup_printf(fmt2, va_arg(l, void *));
                break;
			case 's':
				add = g_strdup_printf(fmt2, va_arg(l, char *));
				break;
			default:
				add = g_strdup(fmt2);
				break;

		}

		if (add != NULL && isupper(type)){
			for (d = add; *d != '\0'; d++) *d = toupper(*d);
		}

		switch(flags[0]){
			case 'b': // handled in code before
				break;
			case 'e': // escape with '\'   
				for (d = add; *d != '\0'; d++){
                    switch(*d){
                        case ';':
                            g_string_append(gs, "\\;");
                            break;
                        case '\\':
                            g_string_append(gs, "\\\\");
                            break;
                        case '\r':
                            g_string_append(gs, "\\r");
                            break;
                        case '\n':
                            g_string_append(gs, "\\n");
                            break;
                        default:
                            g_string_append_c(gs, *d);
                            break;
                    }
                }
				break;
			case 'h': // html
				for (d = add; *d != '\0'; d++){
					switch(*d){
						case '<':
							g_string_append(gs, "&lt;");
							break;
						case '>':
							g_string_append(gs, "&gt;");
							break;
						case '"':
							g_string_append(gs, "&quot;");
							break;
						case '&':
							g_string_append(gs, "&amp;");
							break;
						case '\'':
							g_string_append(gs, "&#39;");
							break;
						default:
							g_string_append_c(gs, *d);    
					}
				}
				break;
			case 'j': // json
				for (d = add; *d != '\0'; d++){
					switch (*d){
//					case ';':
//						g_string_append(gs, "\\;");
//						break;
					case '"':
						g_string_append(gs, "\\\"");
						break;
					case '\\':
						g_string_append(gs, "\\\\");
						break;
					case '\r':
						g_string_append(gs, "\\r");
						break;
					case '\n':
						g_string_append(gs, "\\n");
						break;
					default:
						g_string_append_c(gs, *d);
						break;
					}
				}
				break;
			case 'q': // quoted-printable
				gs2 = g_string_sized_new(strlen(add));
				for (d = add; *d != '\0'; d++){
					if (gs2->len > 70){
						g_string_append(gs, gs2->str);
						g_string_append(gs, "=\r\n");
						g_string_assign(gs2, "");
					}
					if (*d == '\r') continue;
					if (*d == '\n') {
						g_string_append(gs, "\r\n");
						continue;
					}
					if ((*(d+1) == '\r' || *(d+1) == '\n') && (*d == ' ' || *d == '\t')){
						g_string_append_printf(gs, "=%02X", (unsigned char)*d);
						continue;
					}
					if (*d & 0x80 || *d == '=' || (*d & 0xf8) == 0){
						g_string_append_printf(gs, "=%02X", (unsigned char)*d);
						continue;
					}
					g_string_append_c(gs, *d);
				}
				g_string_append(gs, gs2->str);
				g_string_free(gs, TRUE);
				break;
			case 's': // postgresql
				for (d = add; *d != '\0'; d++){
					switch (*c){
						case '\'':
							g_string_append(gs, "''");
							break;
						case '\\':
							g_string_append(gs, "\\\\");
							break;
					}
				}
				break;
			case 'u': // url
				for (d = add; *d != '\0'; d++){
					if (isalnum(*d) || *d == '_' || *d == '-' || *d == '.'){
						g_string_append_c(gs, *d);
						continue;
					}
					g_string_append_c(gs, '%');
					g_string_append_printf(gs, "%02X", (unsigned char)*d);
				}
				break;
			case 'w': // wiki
			    g_string_append(gs, "<nowiki>");
				for (d = add; *d != '\0'; d++){
					switch (*c){
						case '<':
							g_string_append(gs, "&lt;");
							break;
						case '>':
							g_string_append(gs, "&gt;");
							break;
						case '"':
							g_string_append(gs, "&quot;");
							break;
						case '&':
							g_string_append(gs, "&amp;");
							break;
						case '\'':
							g_string_append(gs, "&#39;");
							break;
						default:
							g_string_append_c(gs, *d);    
					}
				}
			    g_string_append(gs, "</nowiki>");
				break;
			default:
				g_string_append(gs, add);
		} // switch
		if (add) g_free(add);
	} // for

	g_free(fmt2);
	return gs->len - oldlen;
}


int zg_string_eprintfa(const char *flags, GString *gs, char *fmt, ...){
	va_list l;
	int ret;

	va_start(l, fmt);
	ret = zg_string_veprintfa(flags, gs, fmt, l);
	va_end(l);
	return ret;
}

int zg_string_eprintf(const char *flags, GString *gs, char *fmt, ...){
	va_list l;
	int ret;

	g_string_assign(gs, "");
	va_start(l, fmt);
	ret = zg_string_veprintfa(flags, gs, fmt, l);
	va_end(l);
	return ret;
}

int z_tokens(const char *str){
    int backslash=0;
    int tokens=1;
    const char *c;

    for (c = str; *c != '\0'; c++){
        if (backslash){
            backslash = 0;
            continue;
        }

        switch (*c){
            case '\\':
                backslash = 1;
                break;
            case ';':
                tokens++;
                break;
        }
    }
    return tokens;
}

// modifies str!
char *z_tokenize(char *str, int *tokenpos){
    int backslash = 0;
    int i;
    char c, *d, *buf;
    int len;

    len = strlen(str + *tokenpos) + *tokenpos;
    if (*tokenpos >= len) return NULL;
    if (*tokenpos < 0) return NULL;
    buf = d = str + *tokenpos;

    for (i = *tokenpos; i < len; i++){
        c = str[i];
        if (backslash){
            backslash = 0;
            switch(c){
                case '\\':
                case ';':
                    break;
                case 'r':
                    c = '\r';
                    break;
                case 'n':
                    c = '\n';
                    break;
                default: /* error */
                    //fprintf(stderr, "bad escape \\%c\n", c); TODO what to do?
                    break;
            }
        }else{
            switch (c){
                case '\\':
                    backslash = 1;
                    continue;
                case ';':
                    *tokenpos = i + 1;
                    *d='\0';
                    return buf;
                default:
                    break;
            }
        }
        *d++ = c;
    }
    *tokenpos = -1;
    *d='\0';
    return buf;
}


char *z_base64dec(char *bin, int maxlen, int *plen, char *src){
    int i, j, len, c0, c1, c2, c3;

	if (!plen) plen = &len;
    *plen = strlen(src);
    if (*plen <= 0) return bin;

    if (!bin) bin = g_new0(char, (*plen * 3) / 4 + 1);  // yes, we can allocate more than needed

    for (i = 0, j = 0; i < *plen; ){
        char *p;

        for (; i < *plen; i++) {
            p = strchr(base64, src[i]);
			if (p) { i++; break; }
        }
        c0 = p - base64;
        
        for (; i < *plen; i++) {
            p = strchr(base64, src[i]);
            if (p) { i++; break; }
        }
        c1 = p - base64;

        bin[j++] = ((c0 & 0x3f) << 2) | ((c1 & 0x30) >> 4);
        if (j == maxlen) break;

        for (; i < *plen; i++) {
            if (src[i] == '=') goto end;
            p = strchr(base64, src[i]);
			if (p) { i++; break; }
        }
        c2 = p - base64;

        bin[j++] = ((c1 & 0x0f) << 4) | ((c2 & 0x3c) >> 2);
        if (j == maxlen) break;

        for (; i < *plen; i++) {
            if (src[i] == '=') goto end;
            p = strchr(base64, src[i]);
			if (p) { i++; break; }
        }
        c3 = p - base64;

        bin[j++] = ((c2 & 0x03) << 6) | (c3 & 0x3f);
        if (j == maxlen) break;

    }
end:;    
    return bin;
}

char *z_hexadec(char *bin, int maxlen, int *plen, char *src){
    int len, i, j;
    char c;

	if (!plen) plen = &len;
    *plen = strlen(src);
    if (*plen <= 0) return bin;

    if (!bin) bin = g_new0(char, *plen / 2 + 1);  // yes, we can allocate more than needed

    for (i = 0, j = 0; i < *plen; i++){
        c = src[i];
        if (c >= '0' && c <= '9') 
            bin[j] = c - '0';
        else if (c >= 'a' && c <= 'f') 
            bin[j] = c - 'a' + 10;
        else if (c >= 'A' && c <= 'F') 
            bin[j] = c - 'A' + 10;

        bin[j] <<= 4;
        i++;
        if (i == *plen) break;

        c = src[i];
        if (c >= '0' && c <= '9') 
            bin[j] |= c - '0';
        else if (c >= 'a' && c <= 'f') 
            bin[j] |= c - 'a' + 10;
        else if (c >= 'A' && c <= 'F') 
            bin[j] |= c - 'A' + 10;

        j++;
        if (j == maxlen) break;
    }
    return bin;
}
