/*
    string utilities
    Copyright (C) 2011-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 <libziaint.h>
#include <zdebug.h>
#include <zstr.h>

#include <ctype.h>
#include <stdlib.h>
#include <string.h>

gint z_compare_string (gconstpointer a, gconstpointer b){
	gchar **ca, **cb;

	ca=(gchar **)a;
	cb=(gchar **)b;

	return strcmp(*ca, *cb);
}


typedef unsigned chartype;

char *z_strstr(const char *phaystack, const char *pneedle){
    register const unsigned char *haystack, *needle;
    register chartype b, c;

    haystack = (const unsigned char *) phaystack;
    needle = (const unsigned char *) pneedle;

    b = *needle;
    if (b != '\0'){
        if (b != '.' && b != '?'){
            haystack--;             /* possible ANSI violation */
            do{
                c = *++haystack;
                if (c == '\0') goto ret0;
            }while (c != b);
        }
        c = *++needle;
        if (c == '\0') goto foundneedle;
        ++needle;
        goto jin;

        for (;;){
            register chartype a;
            register const unsigned char *rhaystack, *rneedle;

            do{
                a = *++haystack;
                if (a == '\0') goto ret0;
                if (a == b) break;
                a = *++haystack;
                if (a == '\0') goto ret0;
shloop:;
            }while (a != b && b != '.' && b != '?');

jin:;     
            a = *++haystack;
            if (a == '\0') goto ret0;
            if (c!='.' && c!='?' && a != c)    goto shloop;

            rhaystack = haystack-- + 1;
            rneedle = needle;
            a = *rneedle;

            if (*rhaystack == a || a=='.' || a=='?')
                do{
                    if (a == '\0') goto foundneedle;
                    ++rhaystack;
                    a = *++needle;
                    if (a!='.' && a!='?'){
                        if (*rhaystack != a) break;
                    }
                    if (a == '\0') goto foundneedle;
                        
                    ++rhaystack;
                    a = *++needle;
                }while (*rhaystack == a || a == '.' || a=='?');

            needle = rneedle;       /* took the register-poor approach */

            if (a == '\0') break;
        }
    }
foundneedle:;
    return (char*) haystack;
ret0:;
    return 0;
}


char *z_strcasestr(const char *phaystack, const char *pneedle){
    register const unsigned char *haystack, *needle;
    register chartype b, c;

    haystack = (const unsigned char *) phaystack;
    needle = (const unsigned char *) pneedle;

    b = tolower(*needle);
    if (b != '\0'){
            haystack--;             /* possible ANSI violation */
            do{
                c = tolower(*++haystack);
                if (c == '\0') goto ret0;
            }while (c != b);
        c = tolower(*++needle);
        if (c == '\0') goto foundneedle;
        ++needle;
        goto jin;

        for (;;){
            register chartype a;
            register const unsigned char *rhaystack, *rneedle;

            do{
                a = tolower(*++haystack);
                if (a == '\0') goto ret0;
                if (a == b) break;
                a = tolower(*++haystack);
                if (a == '\0') goto ret0;
shloop:;
            }while (a != b);

jin:;     
            a = tolower(*++haystack);
            if (a == '\0') goto ret0;
            if (a != c)    goto shloop;

            rhaystack = haystack-- + 1;
            rneedle = needle;
            a = tolower(*rneedle);

            if (tolower(*rhaystack) == a)
                do{
                    if (a == '\0') goto foundneedle;
                    ++rhaystack;
                    a = tolower(*++needle);
                        if (tolower(*rhaystack) != a) break;
                    if (a == '\0') goto foundneedle;
                        
                    ++rhaystack;
                    a = tolower(*++needle);
                }while (tolower(*rhaystack) == a);

            needle = rneedle;       /* took the register-poor approach */

            if (a == '\0') break;
        }
    }
foundneedle:;
    return (char*) haystack;
ret0:;
    return 0;
}

int z_min3(int a, int b, int c)
{
    int min = a;
    if (b < min) min=b;
    if (c < min) min=c;
    return min;
}


int z_levenshtein(const char *s, const char *t){
    int i, j, k, n, m, cost, *d, ret;

    n = strlen(s); 
    m = strlen(t);
    if (!n) return -1;
    if (!m) return -1;
    
    // d is a table with n+1 rows and m+1 columns
    d = (int *)g_malloc(sizeof(int) * (m + 1) * (n + 1));
    m++;
    n++;
    // step 2  
    for(k = 0; k < n; k++) d[k] = k;    // deletion
    for(k = 0; k < m; k++) d[k*n] = k;  // insertion
    // step 3 and 4    
    for(i = 1; i<n; i++)
    for(j=1; j<m; j++){
        // step 5
        if(s[i - 1] == t[j - 1])
            cost = 0;
        else
            cost = 1;
        // step 6             
        d[j * n + i] = z_min3(
            d[(j - 1) * n + i] + 1,
            d[j * n + i - 1] + 1,
            d[(j - 1) * n + i - 1] + cost);
    }
    ret = d[n * m - 1];
    g_free(d);
    return ret;
}

char *z_string_hex(GString *gs, char *data, int len){
	int i;
	
	for (i = 0; i < len; i++){
		if (i > 0) g_string_append_c(gs, ' ');
		g_string_append_printf(gs, "%02x", (unsigned char)data[i]);
	}
    return gs->str;
}

char *z_strip_crlf(char *str){
	if (str == NULL) return NULL;

	int l = strlen(str);
    if (l > 0 && str[l-1] == '\n'){
		str[l - 1] = '\0';
		l--;
	}
    if (l > 0 && str[l-1] == '\r') {
		str[l - 1] = '\0';
	}
	return str;
}

char *z_strip_from(char *str, char charfrom){
	char *c;

	c = strchr(str, charfrom);
	if (c) *c = '\0';
	return str;
}

char *z_1250_to_8859_2(char *src){
	char *c;
	for (c = src; *c != '\0'; c++){
		switch ((unsigned char)*c){
            // win1250      iso8859-2
			case 0x9e: *c = 0xbe; break;
			case 0x9a: *c = 0xb9; break;
			case 0x9d: *c = 0xbb; break;
			case 0x8e: *c = 0xae; break;
			case 0x8a: *c = 0xa9; break;
			case 0x8d: *c = 0xab; break;
		}
	}
	return src;
}

void *z_strtop(const char *str){
    long int li;
#ifdef Z_MSC_MINGW
	li = strtoul(str, NULL, 16);
    //dbg("z_strtop('%s') = %lx\n", str, li);
#else
	long long int lli;
	lli = strtoll(str, NULL, 16);
    li = (long int) lli;
   //dbg("z_strtop('%s') = %llx = %lx\n", str, lli, li);
#endif
 	return (void *)li;
}

#ifndef Z_HAVE_G_STRING_APPEND_VPRINTF
void g_string_append_vprintf(GString *gs, const gchar *format, va_list args){
   char *c;
   c = g_strdup_vprintf(format, args);
   g_string_append(gs, c);
   g_free(c);
}
#endif

#ifdef Z_MINGW
static GStaticMutex strtok_mutex = G_STATIC_MUTEX_INIT; 
char *strtok_r(char *str, const char *delim, char **saveptr){
	char *c;
    g_static_mutex_lock(&strtok_mutex);
    c = strtok(str, delim);
    g_static_mutex_unlock(&strtok_mutex);
	return c;
}
#endif

void z_qrg_format(char *s, int size, double qrg){
	int len;

	g_snprintf(s, size - 3, "%0.0f", qrg); 
    len=strlen(s);
    if (len > 3) {
        memmove(s + len - 2, s + len - 3, 4);
        s[len - 3] = '.';
		if (len > 6){
            memmove(s + len - 5, s + len - 6, 8);
            s[len - 6] = '.';
        }
    } 
	if (qrg < 0.0 && len > 2 && s[1] == '.'){
		memmove(s + 1, s + 2, len);
	}
}

double z_qrg_parse(const char *s){
	const char *c;
	char *d;
	double f;
	char *ss = g_new0(char, strlen(s) + 1);

	for (c = s, d = ss; *c != '\0'; c++){
		if (*c == '.') continue;
		*d++ = *c;
	}
	*d = '\0';

	f = atof(ss);
	g_free(ss);
	return f;
}

char *z_html2txt(char *html){
	GString *gs;
	char *ret, *c;
    int i;

	gs = g_string_new(html);

	// remove comments
	z_string_replace_from_to(gs, "<!--", "-->", "", ZSR_ALL);

	// keep only body
	c = z_strcasestr(gs->str, "<body");
	if (c != NULL) g_string_erase(gs, 0, c - gs->str);

	c = z_strcasestr(gs->str, "</body>");
	if (c != NULL) g_string_truncate(gs, (c - gs->str) + 7);

	
	// remove CRLF
	for (i = 0; i < (int)gs->len; i++){
		if (gs->str[i] == '\r' || gs->str[i] == '\n' || gs->str[i] == '\t'){
			gs->str[i] = ' ';
			i--;
		}
	}

	// replace some tags with \n
	z_string_replace(gs, "<br>", "\n", ZSR_ALL | ZSR_CI);
	z_string_replace(gs, "<table", "\n<table", ZSR_ALL | ZSR_CI);
	z_string_replace(gs, "</table", "\n</table", ZSR_ALL | ZSR_CI);
	z_string_replace(gs, "</tr", "\n</tr", ZSR_ALL | ZSR_CI);
	z_string_replace(gs, "</div", "\n</div", ZSR_ALL | ZSR_CI);
	z_string_replace(gs, "</h", "\n</h", ZSR_ALL | ZSR_CI);

	// remove tags
	z_string_replace_from_to(gs, "<", ">", " ", ZSR_ALL);

	// shrink more spaces to one
	for (i = 0; i < (int)gs->len; i++){
		if (gs->str[i] == ' ' && gs->str[i+1] == ' '){
			g_string_erase(gs, i, 1);
			i--;
		}
	}

	// remove only spaces (" \n")
	for (i = 0; i < (int)gs->len; i++){
		if (gs->str[i] == ' ' && gs->str[i+1] == '\n'){
			g_string_erase(gs, i, 1);
			i--;
		}
	}

	// convert &nbsp;'s to spaces
	z_string_replace(gs, "&nbsp;", " ", ZSR_ALL | ZSR_CI);

	// some other special characters
	z_string_replace(gs, "&copy;", "(C)", ZSR_ALL | ZSR_CI);

	// shrink more \n's to two
	for (i = 0; i < (int)gs->len - 2; i++){
		if (gs->str[i] == '\n' && gs->str[i+1] == '\n' && gs->str[i+2] == '\n'){
			g_string_erase(gs, i, 1);
			i--;
		}
	}

	// remove leading \n
	if (gs->str[0] =='\n') g_string_erase(gs, 0, 1);
	if (gs->str[0] =='\n') g_string_erase(gs, 0, 1);

	// remove trailing \n
	if (gs->len > 0 && gs->str[gs->len - 1] == '\n') g_string_truncate(gs, gs->len - 1);
	if (gs->len > 0 && gs->str[gs->len - 1] == '\n') g_string_truncate(gs, gs->len - 1);


	ret = g_strdup(gs->str);
	g_string_free(gs, TRUE);
	return ret;
}



int z_string_replace(GString *gs, char *pattern, char *newstr, int flags){
	int pos = -1;
	int i;
	char *c;

	for (i = 0; i < (int)gs->len; i++){
		if (flags & ZSR_CI)
			c = z_strcasestr(gs->str + i, pattern);
		else
			c = strstr(gs->str + i, pattern);

		if (c == NULL) break;
		
		pos = c - gs->str;
		g_string_erase(gs, pos, strlen(pattern));
		g_string_insert(gs, pos, newstr);
		i = pos + strlen(newstr) - 1;
		if ((flags & ZSR_ALL) == 0) break;
	}
	return pos;
}

int z_string_replace_from_to(GString *gs, char *from, char *to, char *newstr, int flags){
	int i;
	int pos = -1;
	char *c, *d;

	for (i = 0; i < (int)gs->len; i++){
		if (flags & ZSR_CI)
			c = z_strcasestr(gs->str + i, from);
		else
			c = strstr(gs->str + i, from);
		if (c == NULL) break;

		if (flags & ZSR_CI)
			d = z_strcasestr(c + strlen(from), to);
		else
			d = strstr(c + strlen(from), to);
		if (d == NULL) break; // ignore non-closed combination

		pos = c - gs->str;
		g_string_erase(gs, pos, (d - c) + strlen(to));
		g_string_insert(gs, pos, newstr);
		i = pos + strlen(newstr) - 1;

		if ((flags & ZSR_ALL) == 0) break;
	}
	return pos;
}

void z_split2(char *src, char delimiter, char **key, char **val, int flags){
    char *c;
    const char *kx, *vx;
    
    if (!src) {
        *key = NULL;
        *val = NULL;
        return;
    }
    
    c = strchr(src, delimiter);
    if (!c) {
        *key = NULL;
        *val = NULL;
        return;
    }

    *c = '\0';

    if (!(flags & ZSPL_NSTRIP)){
        kx = z_trim_beg(src);
        vx = z_trim_beg(c + 1);
    }else{
        kx = src;
        vx = c + 1;
    }

    *key = g_strndup(kx, c - kx);
    *val = g_strdup(vx);

    if (!(flags & ZSPL_NSTRIP)){
        z_trim_end(*key);  
        z_trim_end(*val);  
    }
}


int zstr_begins_with(const char *haystack, const char *needle, int casesensitive){
    if (casesensitive){
        return strncmp(haystack, needle, strlen(needle)) == 0;
    }else{
        return strncasecmp(haystack, needle, strlen(needle)) == 0;
    }
}


char *zstr_shorten(const char *src, int maxlen){
	char *c;
	int l = strlen(src);
	if (maxlen < 3 || l <= maxlen) return g_strdup(src);
	
	c = g_new0(char, maxlen + 1);
	memcpy(c, src, maxlen / 2);
	c[maxlen/2] = '~';
	memcpy(c + maxlen / 2 + 1, src + l - (maxlen + 1)/ 2 + 1, maxlen - maxlen / 2);
	return c;
}

void z_string_bytes(GString *gs, long long bytes){
	
    if (bytes < 10*1024LL){
        g_string_append_printf(gs, "%d B", (int)bytes);
    }else if (bytes < 10 * 1024 * 1024LL){
        g_string_append_printf(gs, "%3.1f KB", (double)(bytes / 1024.0));
    }else if (bytes < 10L * 1024 * 1024 * 1024LL){
        g_string_append_printf(gs, "%3.1f MB", (double)(bytes / (1024.0 * 1024.0)));
    }else{
        g_string_append_printf(gs, "%3.1f GB", (double)(bytes / (1024.0 * 1024.0 * 1024.0)));
    }
}


void z_strtok_csv_simple(GString *gs, GString *token){
	int i, quote = 0;

	g_string_truncate(token, 0);
	
	for (i = 0; i < (int)gs->len; i++){
		char c = gs->str[i];
		if (quote){
			quote = 0;
			switch (c){
				case '"':
					g_string_append_c(token, c);
					break;
				case ';':
					g_string_erase(gs, 0, i + 1);
					return;
			}
		}
		else{
			switch (c){
				case '"':
					if (i > 0) quote = 1;
					break;
				case ';':
					g_string_erase(gs, 0, i + 1);
					return;
				default:
					g_string_append_c(token, c);
					break;
			}
		}
	}
	g_string_erase(gs, 0, i);

}

void zg_string_trim(GString *gs){
	while (gs->len > 0 && isspace(gs->str[0])) {
		g_string_erase(gs, 0, 1);
	}

	while (gs->len > 0 && isspace(gs->str[gs->len - 1])) {
		g_string_erase(gs, gs->len - 1, 1);
	}
}


gpointer zg_memdup(gconstpointer mem, gsize byte_size)
{
    gpointer new_mem;

    if (mem && byte_size != 0)
    {
        new_mem = g_malloc(byte_size);
        memcpy(new_mem, mem, byte_size);
    }
    else
        new_mem = NULL;

    return new_mem;
}