/* ARISA - Utility Functions 
 * Copyright (C) 2003, 2004 Carl Ritson
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
 */

#include "arisa.h"

#include <fcntl.h>
#include <unistd.h>

#include <stdarg.h>
#include <ctype.h>

#include <errno.h>


inline time_t xtime(void) {
	time_t t;
	/* FIXME: do drift calculation? */
	t = time(NULL);
	return t;
}

/* strncpy doesn't quite do what I want */
inline char *xstrncpy(char *dst, const char *src, size_t n) {
	strncpy(dst,src,n);
	dst[n-1] = '\0';
	return dst;
}

size_t xstrncpy_rpl(char *dst, const char *src, size_t len,
		const char *pattern, const char *replace) {
	const size_t p_len	= strlen(pattern);
	const size_t r_len	= strlen(replace);
	const char p0		= pattern[0];
	size_t d_pos		= 0;
	
	while(d_pos < (len-1) && *src != '\0') {
		if(*src == p0) {
			if(strncmp(src,pattern,p_len) == 0) {
				strncpy(dst+d_pos,replace,
					IMIN((len-1) + d_pos,r_len));
				d_pos	+= r_len;
				src	+= p_len;
			} else {
				dst[d_pos++] = *(src++);
			}
		} else {
			dst[d_pos++] = *(src++);
		}
	}
	dst[d_pos] = '\0';
	
	return d_pos;
}
	
inline int IMAX(int a, int b) {
	return a < b ? b : a;
}

inline int IMIN(int a, int b) {
	return a > b ? b : a;
}

char *readline_term(FILE *fp, char *buffer, size_t size) {
	int i,ret;

	for(i = 0; i < (size - 1); ++i) {
		ret = fgetc(fp);
		if(ret != EOF && ret != '\n')
			buffer[i] = (char) ret;
		else
			break;
	}
	buffer[i] = '\0';
	if(i != 0)
		return buffer;
	else
		return NULL;
}

char *readline_file_nt(FILE *fp, char *buffer, size_t size, size_t *ret_size) {
	char *tmp;
	
	*ret_size = fread(buffer,1,size-1,fp);
	buffer[*ret_size] = '\0';
	
	if((tmp = strchr(buffer,'\n')) != NULL) {
		// this is always possible due to earlier termination
		*(tmp+1) = '\0';
		fseek(fp,-(*ret_size - (tmp - buffer) - 1),SEEK_CUR);
		*ret_size = tmp - buffer;
	}

	if(tmp != NULL || *ret_size > 0)
		return buffer;
	else
		return NULL;
}

char *readline_file(FILE *fp, char *buffer, size_t size) {
	size_t ret_size = 0;
	char *ret = readline_file_nt(fp,buffer,size,&ret_size);

	if(ret != NULL && ret_size > 0) {
		if(buffer[ret_size-1] == '\n')
			buffer[ret_size-1] = '\0';
	}

	return ret;
}

void trim(char *str) {
	int i, start = 0, end = 0;
	
	assert(str != NULL);
	
	if(*str == '\0')
		return;	
	
	for(i = 0; isspace(str[i]) && str[i] != '\0'; ++i) ;
	start = i;
	for(i = strlen(str) - 1; isspace(str[i]) && i > start; --i) ;
	end = i + 1;
	
	if(start == end) {
		*str = '\0';
		return;
	}

	memmove(str, str + start, end - start);
	str[(end - start)] = '\0';
}

char *readtline_term(FILE *fp, char *buffer, size_t size) {
	char *line = readline_term(fp,buffer,size);
	if(line != NULL)
		trim(line);
	return line;
}

char *readtline_file(FILE *fp, char *buffer, size_t size) {
	char *line = readline_file(fp,buffer,size);
	if(line != NULL)
		trim(line);
	return line;
}

void splitline(char *part1, char **part2) {
	int i;
	assert(part1 != NULL);
	for(i = 0; part1[i] != '\0'; ++i) {
		if(isspace(part1[i]))
			break;
	}
	if(part1[i] != '\0') {
		/* Space found, so split */
		part1[i] = '\0';
		for(i = i + 1; part1[i] != '\0' && isspace(part1[i]); ++i) ;
		*part2 = (part1 + i);
	} else {
		*part2 = NULL;
	}
}

int lrand(int limit) {
	return (int) (((float)limit)*rand()/(RAND_MAX+1.0));
}

#define PTRARR_BLOCK_SIZE 4
static inline int no_blocks(int no_elem) {
	return no_elem == 0 ? 0 : (no_elem >> 2) + 1;
}
void ptrarr_add(void ***arr, int *no_elem, void *ptr) {
	int i;
	for(i = 0; i < *no_elem; ++i) {
		if((*arr)[i] == ptr)
			return;
	}
	if(no_blocks(*no_elem) != no_blocks((*no_elem)+1)) {
		(*arr) = xreloc(*arr,sizeof(void *) * 
			(no_blocks((*no_elem)+1) * PTRARR_BLOCK_SIZE));
	}
	(*no_elem)++;
	(*arr)[(*no_elem)-1] = ptr;
}

void ptrarr_del(void ***arr, int *no_elem, void *ptr) {
	int i,j,del;
	
	for(i = 0, j = 0, del = 0; j < *no_elem;) {
		if((*arr)[j] != ptr) {
			if(i != j)
				(*arr)[i] = (*arr)[j];
			i++; j++;
		} else {
			j++;
			del++;
		}
	}
	
	if(no_blocks(*no_elem) != no_blocks((*no_elem)-del)) {
		(*arr) = xreloc(*arr,sizeof(void *) *
			(no_blocks((*no_elem)-del) * PTRARR_BLOCK_SIZE));
	}
	(*no_elem) -= del;
}

void *ptrarr_dup(void **arr, int no_elem, int *ret_no_elem) {
	void **ret = NULL;
	
	if(arr != NULL) {
		ret = xalloc(sizeof(void *) *
			no_blocks(no_elem) * PTRARR_BLOCK_SIZE);
		memcpy(ret,arr,sizeof(void *) * no_elem);
	}
	
	(*ret_no_elem) = no_elem;

	return (void *)ret;
}

void ptrarr_alloc(void ***arr, int *no_elem, int size) {
	if(size != 0) {
		int i;
		(*arr) = xalloc(sizeof(void *) *
				no_blocks(size) * PTRARR_BLOCK_SIZE);
		for(i = 0; i < size; ++i)
			(*arr)[i] = NULL;
	} else {
		(*arr) = NULL;
	}
	(*no_elem) = size;
}

#if 0
void ptrarr_reduce(void ***arr, int *no_elem, int hint) {
	void **newarr = NULL;
	int i,j;
	
	if(hint <= 0) {
		for(i = 0, hint = 0; i < *no_elem; ++i) {
			if((*arr)[i] != NULL)
				hint++;
		}
	}

	newarr = xalloc(sizeof(void *) * hint);
	for(i = 0, j = 0; i < *no_elem; ++i) {
		if((*arr)[i] != NULL)
			newarr[j++] = (*arr)[i];
	}
	xfree(*arr);
	
	*arr = newarr;
	*no_elem = hint;
}
#endif

int ptrarr_swap(void ***arr, int *no_elem, int elem1, int elem2) {
	void *tmp;

	if(elem1 < 0 || elem2 < 0)
		return -1;
	if(elem1 >= *no_elem || elem2 >= *no_elem)
		return -1;
	
	tmp = (*arr)[elem1];
	(*arr)[elem1] = (*arr)[elem2];
	(*arr)[elem2] = tmp;

	return 0;
}

int ptrarr_move(void ***arr, int *no_elem, int from_pos, int to_pos) {
	void *tmp;
	int i;

	if(from_pos < 0 || to_pos < 0)
		return -1;
	else if(from_pos >= *no_elem || to_pos >= *no_elem)
		return -1;
	else if(from_pos == to_pos)
		return 0;

	tmp = (*arr)[from_pos];
	if(from_pos < to_pos) {
		for(i = from_pos; i < to_pos; ++i)
			(*arr)[i] = (*arr)[i+1];
	} else {
		for(i = from_pos; i > to_pos; --i)
			(*arr)[i] = (*arr)[i-1];
	}
	(*arr)[to_pos] = tmp;

	return 0;
}

char **strarr_dup(char **in, int no_in, int *no_out) {
	char **arr = NULL;
	int i;
	
	arr = PTRARR_DUP(in,no_in,no_out);
	for(i = 0; i < *no_out; ++i) {
		if(arr[i] != NULL)
			arr[i] = xstrdup(arr[i]);
	}
	
	return arr;
}

void strarr_free(char ***arr, int *no_arr) {
	int i;
	if(*arr != NULL) {
		for(i = 0; i < *no_arr; ++i) {
			if((*arr)[i] != NULL)
				xfree((*arr)[i]);
		}
		xfree(*arr);
		*arr = NULL;
	}
	*no_arr = 0;
}

void pathtofn(const char *path, char *fn, const size_t fn_len) {
	int i,ls;
	for(i = 0, ls = 0; path[i] != '\0'; ++i) {
		if(path[i] == '/')
			ls = i + 1;
	}
	xstrncpy(fn,path+ls,fn_len);
}

char **split_opt(char *input, char *output, int *no_opt) {
	char **ret = NULL;
	int lpos,ppos,opt,q;
	
	if(output == NULL) {
		output = xstrdup(input);
		input = output;
	} else if(input != output) {
		strcpy(output,input);
		input = output;
	}

retry:
	if(*no_opt > 0)
		ret = xalloc(sizeof(char *)*(*no_opt));
	
	for(lpos = -1,ppos = opt = 0,q = -1; input[ppos] != '\0' 
			&& (*no_opt == 0 || opt < (*no_opt)-1); ++ppos) {
		if(lpos == -1 && !isspace(input[ppos])) {
			if(input[ppos] == '"')
				lpos = q = ppos;
			else
				lpos = ppos;
		} else if(lpos != -1 && isspace(input[ppos]) && q == -1) {
			if(ret != NULL) {
				ret[opt++] = &(input[lpos]);
				input[ppos] = '\0';
			} else
				opt++;
			lpos = -1;
		} else if(q != -1 && input[ppos] == '"') {
			if(input[ppos-1] != '\\') {
				if(ret != NULL) {
					ret[opt++] = &(input[lpos+1]);
					input[lpos] = '\0';
					input[ppos] = '\0';
				} else
					opt++;
				lpos = -1;
				q = -1;
			}
		}
	}
	if(lpos != -1) {
		if(ret != NULL) {
			if(q == -1)
				ret[opt++] = &(input[lpos]);
			else
				ret[opt++] = &(input[lpos+1]);
		} else
			opt++;
	} else {
		for(; input[ppos] != '\0'; ++ppos) {
			if(!isspace(input[ppos])) {
				if(ret != NULL)
					ret[opt++] = &(input[ppos]);
				else
					opt++;
				break;
			}
		}
	}
		
	if(ret == NULL && opt != 0) {
		*no_opt = opt + 1;
		goto retry;
	} else
		*no_opt = opt;
	
	return ret;
}

size_t colourise(colourtype_t colour, char *buffer, int bufsize, 
		const char *data) {
	char *ptr,*c;
	int i,j;
	
	if(colour == COLOUR_INT) {
		return (size_t) (strlen(xstrncpy(buffer,data,bufsize)));
	} else if(colour == COLOUR_NONE) {
		for(i = 0, j = 0; data[i] != '\0' && j < (bufsize-1); ++i) {
			if(data[i] == '%') {
				i++;
				switch(tolower(data[i])) {
					case 'k': case 'r': case 'g':
					case 'y': case 'b': case 'm':
					case 'c': case 'w': case 'n':
					case '9': case '_': case 'u':
						break;
					case '%':
						buffer[j++] = '%';
						break;
					default:
						buffer[j++] = data[i-1];
						buffer[j++] = data[i];
						break;
				}
			} else
				buffer[j++] = data[i];
		}
		if(buffer[j] != '\0')
			buffer[j] = '\0';
		return j;
	} else {
		if(data == buffer)
			ptr = xstrdup(data);
		else
			ptr = (char *)data;
		for(i = 0, j = 0; ptr[i] != '\0' && j < (bufsize-1); ++i) {
			if(ptr[i] == '%') {
				i++;
				switch(ptr[i]) {
					case '_':
					case '9': c = IRC_BOLD;		break;
					case 'k': c = IRC_BLACK;	break;
					case 'K': c = IRC_GRAY; 	break;
					case 'r': c = IRC_RED; 		break;
					case 'R': c = IRC_LRED;		break;
					case 'g': c = IRC_GREEN;	break;
					case 'G': c = IRC_LGREEN;	break;
					case 'y': c = IRC_YELLOW;	break;
					case 'Y': c = IRC_LYELLOW;	break;
					case 'b': c = IRC_BLUE;		break;
					case 'B': c = IRC_LBLUE;	break;
					case 'm': c = IRC_MAGENTA;	break;
					case 'M': c = IRC_LMAGENTA;	break;
					case 'c': c = IRC_CYAN;		break;
					case 'C': c = IRC_LCYAN;	break;
					case 'w': c = IRC_WHITE;	break;
					case 'W': c = IRC_LWHITE;	break;
					case 'U': c = IRC_UNDERLINE;	break;
					case 'n': c = IRC_OFF;		break;
					case '%': c = "%";		break;
					default:  c = NULL;		break;
				}
				if(c != NULL) {
					strncpy(buffer+j,c,bufsize-j);
					j += strlen(c);
				} else {
					buffer[j++] = ptr[i-1];
					buffer[j++] = ptr[i];
				}
			} else
				buffer[j++] = ptr[i];
		}
		if(buffer[j] != '\0')
			buffer[j] = '\0';
		if(ptr != data)
			xfree(ptr);
		return j;
	}
}

size_t quote_colours(char *buffer, size_t bufsize, const char *data) {
	int i,j;

	for(i = 0, j = 0; j < (bufsize-1) && data[i] != '\0'; ++i) {
		if(data[i] == '%')
			buffer[j++] = '%';
		buffer[j++] = data[i];
	}
	buffer[j] = '\0';

	return j;
}

#if defined(HAVE_STRERROR_R)
char *lstrerror_r(int errnum, char *buffer, size_t bufsize) {
	/* SuSv3 strerror_r returns int, the glibc man page acknowledges this
	 * and says their strerror_r returns int, but in the headers it doesn't.
	 * 
	 * So here is crude function that trys to handle int and char* versions
	 * on the fly.
	 */
	if(strerror_r(EINVAL,buffer,bufsize) == 0) {
		/* int version */
		if(strerror_r(errnum,buffer,bufsize) != 0)
			snprintf(buffer,bufsize,"Unknown: %d",errnum);
		return buffer;
	} else {
		char *ptr = (char *)strerror_r(errnum,buffer,bufsize);
		/* char* version */
		if(ptr == 0)
			snprintf(buffer,bufsize,"Unknown: %d",errnum);
		else
			snprintf(buffer,bufsize,"%s",ptr);
		return buffer;
	}
}
#else /* defined(HAVE_STRERROR_R) */
char *lstrerror_r(int errnum, char *buffer, size_t bufsize) {
	/* In case a version of strerror_r is not found then use strerror
	 * and hope that it is thread safe.  SunOS is a good example.
	 */
	char *str = strerror(errnum);
	xstrncpy(buffer,str,bufsize);
	return buffer;
}
#endif


char *lctime_r(const time_t *timep, char *buf, size_t bufsize) {
#if	!defined(HAVE_SUNOS)
	char ctime_buf[32];
	ctime_r(timep,ctime_buf);
	xstrncpy(buf,ctime_buf,bufsize);
	return buf;
#else
	return ctime_r(timep,buf,bufsize);
#endif
}

char *timestr(time_t time, char *str, size_t strsize) {
	char buffer[32];
	lctime_r(&time,buffer,sizeof(buffer));
	buffer[strlen(buffer)-1] = '\0'; // trailing \n
	xstrncpy(str,buffer,strsize);
	return str;
}

char *etimestr(time_t sec, char *str, size_t strsize) {
	if(sec >= 24*60*60)
		snprintf(str,strsize,"%2ldd%2ldh",
				(long)(sec / (24*60*60)),
				(long)(sec - 
				     	((sec/(24*60*60))*24*60*60)) / (60*60));
	else if(sec >= 60*60)
		snprintf(str,strsize,"%2ldh%2ldm",
				(long)(sec / (60*60)),
				(long)(sec - ((sec/(60*60))*60*60)) / 60);
	else
		snprintf(str,strsize,"%2ldm%2lds",
				(long)(sec / 60),(long)(sec - ((sec/60)*60)));
	return str;
}

/* this function cleans "./" and "../" from paths */
char *clean_ds_path(const char *path, char *buffer, size_t bufsize) {
	int i,j,k;
	if(buffer[0] == '/')
		i = 1;
	else
		i = 0;
	for(j = 0; j < (bufsize-1) && path[i] != '\0'; ++i) {
		if(path[i] == '.') {
			for(k = i; path[k] == '.'; ++k);
			if(path[k] == '/' && (k-i) <= 2) {
				i = k;
				continue;
			}
		}
		buffer[j++] = path[i];
	}
	buffer[j] = '\0';
	return buffer;
}

char *format_bytes(char *buffer, size_t bufsize, 
		unsigned long long bytes, int multiplier) {
	unsigned long long b = bytes * (unsigned long long)multiplier;
	if(b < 1024ULL)
		snprintf(buffer,bufsize,"%dB",
				(int)b);
	else if(b < (768ULL*1024ULL))
		snprintf(buffer,bufsize,"%.1fKiB",
				((double)b) / 1024.0);
	else if(b < (768ULL*1024ULL*1024ULL))
		snprintf(buffer,bufsize,"%.1fMiB",
				((double)b) / (1024.0*1024.0));
	else if(b < (768ULL*1024ULL*1024ULL*1024ULL))
		snprintf(buffer,bufsize,"%.1fGiB",
				((double)b) / (1024.0*1024.0*1024.0));
	else
		snprintf(buffer,bufsize,"%.1fTiB",
				((double)b) / (1024.0*1024.0*1024.0*1024.0));
	return buffer;
}

int diff_tv_usec(struct timeval *ti, struct timeval *tj) {
	if(ti->tv_sec == tj->tv_sec)
		 return IMAX(ti->tv_usec,tj->tv_usec) -
			 IMIN(ti->tv_usec,tj->tv_usec);
	else {
		if(IMAX(ti->tv_sec,tj->tv_sec) == ti->tv_sec) {
			return ((ti->tv_sec - tj->tv_sec) * 1000000)
				+ (ti->tv_usec - tj->tv_usec);
		} else {
			return ((tj->tv_sec - ti->tv_sec) * 1000000)
				+ (tj->tv_usec - ti->tv_usec);
		}
	}
}

inline int nextspace(char *str, int pos) {
	int i;
	for(i = pos; str[i] != '\0' && !isspace(str[i]); ++i) ;
	return i;
}

inline int nextchar(char *str, int pos) {
	int i;
	for(i = pos; str[i] != '\0' && isspace(str[i]); ++i) ;
	return i;
}

/** max_sokbuf
 * 	attempt to determine the maximum socket buffer in using an os 
 *	dependent method.
 */
#if defined(HAVE_LINUX)
int max_sokbuf(int r1s0) {
	char buffer[32];
	int ret,fd;
	int val = 0;
	
	if(!r1s0)
		fd = open("/proc/sys/net/core/wmem_max",O_RDONLY);
	else 
		fd = open("/proc/sys/net/core/rmem_max",O_RDONLY);
	
	if(fd != -1) {
		ret = read(fd,buffer,sizeof(buffer)-1);
		if(ret != -1) {
			buffer[ret] = '\0';
			val = atoi(buffer);
		}
		//D("close: %d",fd);
		close(fd);
	}
	if(val <= 0)
		return 64*1024;
	else
		return val;
}
#elif defined(HAVE_FREEBSD) && defined(HAVE_SYSCTL)
#include <sys/sysctl.h>
int max_sokbuf(int r1s0) {
	int mib[3];
	int val,len;
	
	mib[0] = CTL_KERN;
	mib[1] = KERN_IPC;
	mib[2] = KIPC_MAXSOCKBUF;
	
	len = sizeof(val);
	if(sysctl(mib,3,&val,&len,NULL,0) != 0)
		val = 0;

	if(val <= 0)
		return 64*1024;
	else
		return val;
}
#else
int max_sokbuf(int r1s0) {
	return 64*1024;
}
#endif

int wild_match_cf(const char *str, const char *mask, int (*tl)(int)) {
	while(*mask != '\0') {
		if(*mask == '*') {
			if(tl(*str) == tl(*(mask+1))) {
				if(wild_match_cf(str,mask+1,tl))
					return 1;
			}
		} else if(tl(*mask) != tl(*str)) {
			return 0;
		} else {
			mask++;
		}
		
		if(*str == '\0')
			break;
		str++;
	}
	
	if(*mask == '\0' && *str == '\0')
		return 1;
	else
		return 0;
}

int wild_match(const char *str, const char *mask) {
	while(*mask != '\0') {
		if(*mask == '*') {
			if(*str == *(mask+1)) {
				if(wild_match(str,mask+1))
					return 1;
			}
		} else if(*mask != *str) {
			return 0;
		} else {
			mask++;
		}
		
		if(*str == '\0')
			break;
		str++;
	}
	
	if(*mask == '\0' && *str == '\0')
		return 1;
	else
		return 0;
}

void antispin_init(antispin_t *as) {
	as->count	= 0;
	as->second	= 0;
}

void antispin_test_and_sleep(antispin_t *as, unsigned int max_loop) {
	struct timeval now;
	gettimeofday(&now,NULL);

	if(as->second == now.tv_sec) {
		as->count++;
		if(as->count > max_loop)
			usleep((1000000 - now.tv_usec) + 5000);
	} else {
		as->second	= now.tv_sec;
		as->count	= 0;
	}
}

/* a decimal space counts as a digit, so this is really three_character_size */
char *three_digit_size(char *buffer, off_t size) {
	float f, s = (float) size;
	char c;
	int cnt;
	
	for(cnt = 0; cnt < 4; ++cnt) {
		if(s < 1000.0)
			break;
		s /= 1024.0;
	}
	
	if(cnt == 0) {
		sprintf(buffer,"%3dB",(int)size);
		return buffer;
	} else {
		switch(cnt) {
			case 1: c = 'K'; break;
			case 2: c = 'M'; break;
			case 3: c = 'G'; break;
			case 4: c = 'T'; break;
			default:
				c = ' '; break;
		}
		
		for(cnt = 0, f = 100.0; f > 0.1; f /= 10.0) {
			if(s >= f)
				cnt++;
		}
		
		// printf rounds
		snprintf(buffer,4,"%3.*f",(cnt <= 1 ? 1 : 0),s);
		buffer[3] = c;
		buffer[4] = '\0';
		
		return buffer;
	}
}

off_t expand_text_size(const char *str, int si) {
	char *ptr;
	off_t b = si ? 1000 : 1024;
	off_t ret;
	
	ret = (off_t) strtoull(str,&ptr,0);
	
	if(ptr != NULL) {
		switch(*ptr) {
			case 'k': case 'K': ret *= b;		break;
			case 'm': case 'M': ret *= b*b; 	break;
			case 'g': case 'G': ret *= b*b*b;	break;
			case 't': case 'T': ret *= b*b*b*b;	break;
			default:
				break;
		}
	}

	return ret;
}

int test_timer(time_t now, time_t last, uint32_t interval) {
	if(now == 0 && interval != 0)
		now = xtime();
	if(interval != 0 && abs(now - last) >= interval)
		return 1;
	else
		return 0;
}

int test_update_timer(time_t now, time_t *last, uint32_t interval) {
	if(test_timer(now,*last,interval)) {
		*last = now != 0 ? now : xtime();
		return 1;
	} else
		return 0;
}	
