/* $Id: dynstuff.c,v 1.39.2.1 2007-03-11 12:27:18 porridge Exp $ */

/*
 *  (C) Copyright 2001-2006 Wojtek Kaniewski <wojtekka@irc.pl>
 *                          Dawid Jarosz <dawjar@poczta.onet.pl>
 *                          Adam Wysocki <gophi@ekg.chmurka.net>
 *
 *  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.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "libgadu.h"
#include "dynstuff.h"
#include "stuff.h"
#include "xmalloc.h"

/*
 * list_add_sorted()
 *
 * dodaje do listy dany element. przy okazji może też skopiować zawartość.
 * jeśli poda się jako ostatni parametr funkcję porównującą zawartość
 * elementów, może posortować od razu.
 *
 *  - list - wskaźnik do listy,
 *  - data - wskaźnik do elementu,
 *  - alloc_size - rozmiar elementu, jeśli chcemy go skopiować.
 *
 * zwraca wskaźnik zaalokowanego elementu lub NULL w przpadku błędu.
 */
void *list_add_sorted(list_t *list, void *data, int alloc_size, int (*comparision)(void *, void *))
{
	list_t new, tmp;

	if (!list) {
		errno = EFAULT;
		return NULL;
	}

	new = xmalloc(sizeof(struct list));

	new->data = data;
	new->next = NULL;

	if (alloc_size) {
		new->data = xmalloc(alloc_size);
		memcpy(new->data, data, alloc_size);
	}

	if (!(tmp = *list)) {
		*list = new;
	} else {
		if (!comparision) {
			while (tmp->next)
				tmp = tmp->next;
			tmp->next = new;
		} else {
			list_t prev = NULL;
			
			while (comparision(new->data, tmp->data) > 0) {
				prev = tmp;
				tmp = tmp->next;
				if (!tmp)
					break;
			}
			
			if (!prev) {
				tmp = *list;
				*list = new;
				new->next = tmp;
			} else {
				prev->next = new;
				new->next = tmp;
			}
		}
	}

	return new->data;
}

/*
 * list_add()
 *
 * wrapper do list_add_sorted(), który zachowuje poprzednią składnię.
 */
void *list_add(list_t *list, void *data, int alloc_size)
{
	return list_add_sorted(list, data, alloc_size, NULL);
}

/*
 * list_remove()
 *
 * usuwa z listy wpis z podanym elementem.
 *
 *  - list - wskaźnik do listy,
 *  - data - element,
 *  - free_data - zwolnić pamięć po elemencie.
 */
int list_remove(list_t *list, void *data, int free_data)
{
	list_t tmp, last = NULL;

	if (!list) {
		errno = EFAULT;
		return -1;
	}

	tmp = *list;
	if (tmp->data == data) {
		*list = tmp->next;
	} else {
		for (; tmp && tmp->data != data; tmp = tmp->next)
			last = tmp;
		if (!tmp) {
			errno = ENOENT;
			return -1;
		}
		last->next = tmp->next;
	}

	if (free_data)
		xfree(tmp->data);
	xfree(tmp);

	return 0;
}

/*
 * list_count()
 *
 * zwraca ilość elementów w danej liście.
 *
 *  - list - lista.
 */
int list_count(list_t list)
{
	int count = 0;

	for (; list; list = list->next)
		count++;

	return count;
}

/*
 * list_destroy()
 *
 * niszczy wszystkie elementy listy.
 *
 *  - list - lista,
 *  - free_data - czy zwalniać bufor danych?
 */
int list_destroy(list_t list, int free_data)
{
	list_t tmp;
	
	while (list) {
		if (free_data)
			xfree(list->data);

		tmp = list->next;

		xfree(list);

		list = tmp;
	}

	return 0;
}

/*
 * string_realloc()
 *
 * upewnia się, że w stringu będzie wystarczająco dużo miejsca.
 *
 *  - s - ciąg znaków,
 *  - count - wymagana ilość znaków (bez końcowego '\0').
 */
static void string_realloc(string_t s, int count)
{
	char *tmp;
	
	if (s->str && count + 1 <= s->size)
		return;
	
	tmp = xrealloc(s->str, count + 81);
	if (!s->str)
		*tmp = 0;
	tmp[count + 80] = 0;
	s->size = count + 81;
	s->str = tmp;
}
	
/*
 * string_append_c()
 *
 * dodaje do danego ciągu jeden znak, alokując przy tym odpowiednią ilość
 * pamięci.
 *
 *  - s - ciąg znaków.
 *  - c - znaczek do dopisania.
 */
int string_append_c(string_t s, char c)
{
	if (!s) {
		errno = EFAULT;
		return -1;
	}
	
	string_realloc(s, s->len + 1);

	s->str[s->len + 1] = 0;
	s->str[s->len++] = c;

	return 0;
}

/*
 * string_append_n()
 *
 * dodaje tekst do bufora alokując odpowiednią ilość pamięci.
 *
 *  - s - ciąg znaków,
 *  - str - tekst do dopisania,
 *  - count - ile znaków tego tekstu dopisać? (-1 znaczy, że cały).
 */
int string_append_n(string_t s, const char *str, int count)
{
	if (!s || !str) {
		errno = EFAULT;
		return -1;
	}

	if (count == -1)
		count = strlen(str);

	string_realloc(s, s->len + count);

	s->str[s->len + count] = 0;
	strncpy(s->str + s->len, str, count);

	s->len += count;

	return 0;
}

int string_append(string_t s, const char *str)
{
	return string_append_n(s, str, -1);
}

/*
 * string_insert_n()
 *
 * wstawia tekst w podane miejsce bufora.
 *  
 *  - s - ciąg znaków,
 *  - index - miejsce, gdzie mamy wpisać (liczone od 0),
 *  - str - tekst do dopisania,
 *  - count - ilość znaków do dopisania (-1 znaczy, że wszystkie).
 */
void string_insert_n(string_t s, int index, const char *str, int count)
{
	if (!s || !str)
		return;

	if (count == -1)
		count = strlen(str);

	if (index > s->len)
		index = s->len;
	
	string_realloc(s, s->len + count);

	memmove(s->str + index + count, s->str + index, s->len + 1 - index);
	memmove(s->str + index, str, count);

	s->len += count;
}

void string_insert(string_t s, int index, const char *str)
{
	string_insert_n(s, index, str, -1);
}

/*
 * string_init()
 *
 * inicjuje strukturę string. alokuje pamięć i przypisuje pierwszą wartość.
 *
 *  - value - jeśli NULL, ciąg jest pusty, inaczej kopiuje tam.
 *
 * zwraca zaalokowaną strukturę `string'.
 */
string_t string_init(const char *value)
{
	string_t tmp = xmalloc(sizeof(struct string));

	if (!value)
		value = "";

	tmp->str = xstrdup(value);
	tmp->len = strlen(value);
	tmp->size = strlen(value) + 1;

	return tmp;
}

/*
 * string_clear()
 *
 * czyści zawartość struktury `string'.
 *
 *  - s - ciąg znaków.
 */
void string_clear(string_t s)
{
	if (!s)
		return;

	if (s->size > 160) {
		s->str = xrealloc(s->str, 80);
		s->size = 80;
	}

	s->str[0] = 0;
	s->len = 0;
}

/*
 * string_free()
 *
 * zwalnia pamięć po strukturze string i może też zwolnić pamięć po samym
 * ciągu znaków.
 *
 *  - s - struktura, którą wycinamy,
 *  - free_string - zwolnić pamięć po ciągu znaków?
 *
 * jeśli free_string=0 zwraca wskaźnik do ciągu, inaczej NULL.
 */
char *string_free(string_t s, int free_string)
{
	char *tmp = NULL;

	if (!s)
		return NULL;

	if (free_string)
		xfree(s->str);
	else
		tmp = s->str;

	xfree(s);

	return tmp;
}

/*
 * itoa()
 *
 * prosta funkcja, która zwraca tekstową reprezentację liczby. w obrębie
 * danego wywołania jakiejś funkcji lub wyrażenia może być wywołania 10
 * razy, ponieważ tyle mamy statycznych buforów. lepsze to niż ciągłe
 * tworzenie tymczasowych buforów na stosie i sprintf()owanie.
 *
 *  - i - liczba do zamiany.
 *
 * zwraca adres do bufora, którego _NIE_NALEŻY_ zwalniać.
 */
const char *itoa(long int i)
{
	static char bufs[10][16];
	static int index = 0;
	char *tmp = bufs[index++];

	if (index > 9)
		index = 0;
	
	snprintf(tmp, 16, "%ld", i);

	return tmp;
}

/*
 * array_make()
 *
 * tworzy tablicę tekstów z jednego, rozdzielonego podanymi znakami.
 *
 *  - string - tekst wejściowy,
 *  - sep - lista elementów oddzielających,
 *  - max - maksymalna ilość elementów tablicy. jeśli równe 0, nie ma
 *          ograniczeń rozmiaru tablicy.
 *  - trim - czy większą ilość elementów oddzielających traktować jako
 *           jeden (na przykład spacje, tabulacja itp.)
 *  - quotes - czy pola mogą być zapisywane w cudzysłowiach lub
 *             apostrofach z escapowanymi znakami.
 *
 * zaalokowaną tablicę z zaalokowanymi ciągami znaków, którą należy
 * zwolnić funkcją array_free()
 */

char **array_make(const char *string, const char *sep, int max, int trim, int quotes)
{
	const char *p, *q;
	char **result = NULL;
	int items = 0, last = 0;

	if (!string || !sep)
		goto failure;

	for (p = string; ; ) {
		int len = 0;
		char *token = NULL;

		if (max && items >= max - 1)
			last = 1;

		if (trim) {
			if (*p == '\\') {
				p++;
			} else {
				while (*p && strchr(sep, *p))
					p++;
				if (!*p)
					break;
			}
		}

		if (!last && quotes && (*p == '\'' || *p == '\"')) {
			char separ = *p;
			int meet_quota;

			for (q = p + 1, len = 0, meet_quota = 0; *q; q++, len++) {
				if (*q == '\\') {
					q++;
					if (!*q)
						break;
				} else if (strchr(q, separ)) {
					meet_quota = 1;
				} else if (strchr(sep, *q) && meet_quota == 1)
					break;

			}

			if ((token = xcalloc(1, len + 1))) {
				char *r = token;

				for (q = p + 1, meet_quota = 0; *q; q++, r++) {
					if (*q == '\\') {
						q++;

						if (!*q)
							break;

						switch (*q) {
							case 'n':
								*r = '\n';
								break;
							case 'r':
								*r = '\r';
								break;
							case 't':
								*r = '\t';
								break;
							default:
								*r = *q;
						}
					} else if (*q == separ) {
						r--;
						meet_quota = 1;
					} else if (strchr(sep, *q) && meet_quota == 1) {
						break;
					} else
						*r = *q;
				}

				*r = 0;
			}

			p = q;
		} else {
			for (q = p, len = 0; *q && (last || !strchr(sep, *q)); q++, len++);
			token = xcalloc(1, len + 1);
			strncpy(token, p, len);
			token[len] = 0;
			p = q;
		}

		result = xrealloc(result, (items + 2) * sizeof(char*));
		result[items] = token;
		result[++items] = NULL;

		if (!*p)
			break;
		p++;
	}

failure:
	if (!items)
		result = xcalloc(1, sizeof(char*));

	return result;
}

/*
 * array_make_quoted()
 *
 * tworzy tablicę tekstów z jednego, rozdzielonego podanymi znakami.
 * różnica w stosunko do array_make() polega na tym, iż array_make nie
 * zapisuje znaków cudzysłowia w zwracanej tablicy
 *
 *  - string - tekst wejściowy,
 *  - sep - lista elementów oddzielających,
 *  - max - maksymalna ilość elementów tablicy. jeśli równe 0, nie ma
 *          ograniczeń rozmiaru tablicy.
 *  - trim - czy większą ilość elementów oddzielających traktować jako
 *           jeden (na przykład spacje, tabulacja itp.)
 *  - quotes - czy pola mogą być zapisywane w cudzysłowiach lub
 *             apostrofach z escapowanymi znakami.
 *
 * zaalokowaną tablicę z zaalokowanymi ciągami znaków, którą należy
 * zwolnić funkcją array_free()
 */
char **array_make_quoted(const char *string, const char *sep, int max, int trim, int quotes)
{
	const char *p, *q;
	char **result = NULL;
	int items = 0, last = 0;

	if (!string || !sep)
		goto failure;

	for (p = string; ; ) {
		int len = 0;
		char *token = NULL;

		if (max && items >= max - 1)
			last = 1;

		if (trim) {
			if (*p == '\\') {
				p++;
			} else {
				while (*p && strchr(sep, *p))
					p++;
				if (!*p)
					break;
			}
		}

		if (!last && quotes && (*p == '\'' || *p == '\"')) {
			char separ = *p;
			int meet_quota;

			for (q = p + 1, len = 0, meet_quota = 0; *q; q++, len++) {
				if (*q == '\\') {
					q++;
					if (!*q)
						break;
				} else if (strchr(q, separ)) {
					meet_quota = 1;
				} else if (strchr(sep, *q) && meet_quota == 1)
					break;

			}

			len++;

			if ((token = xcalloc(1, len + 1))) {
				char *r = token;

				for (q = p + 1, meet_quota = 0, *r = *p, r++; *q; q++, r++) {
					if (*q == '\\') {
						q++;

						if (!*q)
							break;

						switch (*q) {
							case 'n':
								*r = '\n';
								break;
							case 'r':
								*r = '\r';
								break;
							case 't':
								*r = '\t';
								break;
							default:
								*r = *q;
						}
					} else if (*q == separ) {
						*r = *q;
						meet_quota = 1;
					} else if (strchr(sep, *q) && meet_quota == 1) {
						break;
					} else
						*r = *q;
				}

				*r = 0;
			}

			//p = (*q) ? q + 1 : q;
			p = q;
		} else {
			for (q = p, len = 0; *q && (last || !strchr(sep, *q)); q++, len++);
			token = xcalloc(1, len + 1);
			strncpy(token, p, len);
			token[len] = 0;
			p = q;
		}

		result = xrealloc(result, (items + 2) * sizeof(char*));
		result[items] = token;
		result[++items] = NULL;

		if (!*p)
			break;
		p++;
	}

failure:
	if (!items)
		result = xcalloc(1, sizeof(char*));

	return result;
}

/*
 * array_count()
 *
 * zwraca ilość elementów tablicy.
 */
int array_count(char **array)
{
	int result = 0;

	if (!array)
		return 0;

	while (*array) {
		result++;
		array++;
	}

	return result;
}

/* 
 * array_add()
 *
 * dodaje element do tablicy.
 */
void array_add(char ***array, char *string)
{
	int count = array_count(*array);

	*array = xrealloc(*array, (count + 2) * sizeof(char*));
	(*array)[count + 1] = NULL;
	(*array)[count] = string;
}

/*
 * array_join()
 *
 * łączy elementy tablicy w jeden string oddzielając elementy odpowiednim
 * separatorem.
 *
 *  - array - wskaźnik do tablicy,
 *  - sep - seperator.
 *
 * zwrócony ciąg znaków należy zwolnić.
 */
char *array_join(char **array, const char *sep)
{
	string_t s = string_init(NULL);
	int i;

	if (!array)
		return xstrdup("");

	for (i = 0; array[i]; i++) {
		if (i)
			string_append(s, sep);

		string_append(s, array[i]);
	}

	return string_free(s, 0);
}

/*
 * array_item_contains()
 *
 * stwierdza czy w tablicy znajduje się element zawierający podany ciąg
 *
 *  - array - tablica,
 *  - string - szukany ciąg znaków,
 *  - casesensitive - czy mamy zwracać uwagę na wielkość znaków?
 *
 * 0/1
 */
int array_item_contains(char **array, const char *string, int casesensitive)
{
        int i;

        if (!array || !string)
                return 0;

        for (i = 0; array[i]; i++) {
                if (casesensitive && strstr(array[i], string))
                        return 1;
                if (!casesensitive && strcasestr(array[i], string))
                        return 1;
        }

        return 0;
}

/*
 * array_add_check()
 *
 * dodaje element do tablicy, uprzednio sprawdzając
 * czy taki już w niej nie istnieje
 *
 *  - array - tablica,
 *  - string - szukany ciąg znaków,
 *  - casesensitive - czy mamy zwracać uwagę na wielkość znaków?
 */
void array_add_check(char ***array, char *string, int casesensitive)
{
        if (!array_item_contains(*array, string, casesensitive))
                array_add(array, string);
        else
                xfree(string);
}

/*
 * array_contains()
 *
 * stwierdza, czy tablica zawiera podany element.
 *
 *  - array - tablica,
 *  - string - szukany ciąg znaków,
 *  - casesensitive - czy mamy zwracać uwagę na wielkość znaków?
 *
 * 0/1
 */
int array_contains(char **array, const char *string, int casesensitive)
{
	int i;

	if (!array || !string)
		return 0;

	for (i = 0; array[i]; i++) {
		if (casesensitive && !strcmp(array[i], string))
			return 1;
		if (!casesensitive && !strcasecmp(array[i], string))
			return 1;
	}

	return 0;
}
	
/*
 * array_free()
 *
 * zwalnia pamieć zajmowaną przez tablicę.
 */
void array_free(char **array)
{
	char **tmp;

	if (!array)
		return;

	for (tmp = array; *tmp; tmp++)
		xfree(*tmp);

	xfree(array);
}

/*
 * eskejpuje:
 *
 * - \ -> \\
 *
 * oraz wystąpienia znaków ze stringa esc:
 *
 * - 0x07 (\a) -> \a
 * - 0x08 (\b) -> \b
 * - 0x09 (\t) -> \t
 * - 0x0A (\n) -> \n
 * - 0x0B (\v) -> \v
 * - 0x0C (\f) -> \f
 * - 0x0D (\r) -> \r
 * - pozostałe -> \xXX (szesnastkowa reprezentacja)
 *
 * jeżeli któryś z wymienionych wyżej znaków (np. \a) nie występuje 
 * w stringu to zostanie przepisany tak jak jest, bez eskejpowania.
 *
 * zwraca nowego, zaalokowanego stringa.
 */
char *escape(const char *src, const char *esc)
{
	string_t dst;

	if (!src)
		return NULL;

	dst = string_init(NULL);

	for (; *src; src++) {
		char ch = *src;
		static const char esctab[] = "abtnvfr";

		if (!(ch == '\\' || strchr(esc, ch))) {
			string_append_c(dst, ch);
			continue;
		}

		string_append_c(dst, '\\');

		if (ch >= 0x07 && ch <= 0x0D)
			string_append_c(dst, esctab[ch - 0x07]);
		else if (ch == '\\')
			string_append_c(dst, '\\');
		else {
			char s[5];
			snprintf(s, sizeof(s), "x%02X", (unsigned char) ch);
			string_append(dst, s);
		}
	}

	return string_free(dst, 0);
}

/*
 * dekoduje stringa wyeskejpowanego przy pomocy escape.
 *
 * - \\ -> \
 * - \xXX -> szesnastkowo zdekodowany znak
 * - \cokolwiekinnego -> \cokolwiekinnego
 *
 * zwraca nowego, zaalokowanego stringa.
 */
char *unescape(const char *src)
{
	int state = 0;
	string_t buf;
	unsigned char hex_msb = 0;

	if (!src)
		return NULL;

	buf = string_init(NULL);

	for (; *src; src++) {
		char ch = *src;

		if (state == 0) {		/* normalny tekst */
			/* sprawdzamy czy mamy cos po '\\', bo jezeli to ostatni 
			 * znak w stringu, to nie zostanie nigdy dodany. */
			if (ch == '\\' && *(src + 1)) {
				state = 1;
				continue;
			}
			string_append_c(buf, ch);
		} else if (state == 1) {	/* kod ucieczki */
			if (ch == 'a')
				ch = '\a';
			else if (ch == 'b')
				ch = '\b';
			else if (ch == 't')
				ch = '\t';
			else if (ch == 'n')
				ch = '\n';
			else if (ch == 'v')
				ch = '\v';
			else if (ch == 'f')
				ch = '\f';
			else if (ch == 'r')
				ch = '\r';
			else if (ch == 'x' && *(src + 1) && *(src + 2)) {
				state = 2;
				continue;
			} else if (ch != '\\')
				string_append_c(buf, '\\');	/* fallback - nieznany kod */
			string_append_c(buf, ch);
			state = 0;
		} else if (state == 2) {	/* pierwsza cyfra kodu szesnastkowego */
			hex_msb = ch;
			state = 3;
		} else if (state == 3) {	/* druga cyfra kodu szesnastkowego */
#define unhex(x) (unsigned char) ((x >= '0' && x <= '9') ? (x - '0') : \
	(x >= 'A' && x <= 'F') ? (x - 'A' + 10) : \
	(x >= 'a' && x <= 'f') ? (x - 'a' + 10) : 0)
			string_append_c(buf, unhex(ch) | (unhex(hex_msb) << 4));
#undef unhex
			state = 0;
		}
	}

	return string_free(buf, 0);
}
