/*
 * utils.c             General purpose utilities
 *
 * Copyright (c) 2001-2013 Thomas Graf <tgraf@suug.ch>
 * Copyright (c) 2013 Red Hat, Inc.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 */

#include <bmon/bmon.h>
#include <bmon/conf.h>
#include <bmon/utils.h>

void *xcalloc(size_t n, size_t s)
{
	void *d = calloc(n, s);

	if (NULL == d) {
		fprintf(stderr, "xalloc: Out of memory\n");
		exit(ENOMEM);
	}

	return d;
}

void *xrealloc(void *p, size_t s)
{
	void *d = realloc(p, s);

	if (NULL == d) {
		fprintf(stderr, "xrealloc: Out of memory!\n");
		exit(ENOMEM);
	}

	return d;
}

void xfree(void *d)
{
	if (d)
		free(d);
}

float timestamp_to_float(timestamp_t *src)
{
	return (float) src->tv_sec + ((float) src->tv_usec / 1000000.0f);
}

int64_t timestamp_to_int(timestamp_t *src)
{
	return (src->tv_sec * 1000000ULL) + src->tv_usec;
}

void float_to_timestamp(timestamp_t *dst, float src)
{
	dst->tv_sec = (time_t) src;
	dst->tv_usec = (src - ((float) ((time_t) src))) * 1000000.0f;
}

void timestamp_add(timestamp_t *dst, timestamp_t *src1, timestamp_t *src2)
{
	dst->tv_sec = src1->tv_sec + src2->tv_sec;
	dst->tv_usec = src1->tv_usec + src2->tv_usec;

	if (dst->tv_usec >= 1000000) {
		dst->tv_sec++;
		dst->tv_usec -= 1000000;
	}
}

void timestamp_sub(timestamp_t *dst, timestamp_t *src1, timestamp_t *src2)
{
	dst->tv_sec = src1->tv_sec - src2->tv_sec;
	dst->tv_usec = src1->tv_usec - src2->tv_usec;
	if (dst->tv_usec < 0) {
		dst->tv_sec--;
		dst->tv_usec += 1000000;
	}
}

int timestamp_le(timestamp_t *a, timestamp_t *b)
{
	if (a->tv_sec > b->tv_sec)
		return 0;

	if (a->tv_sec < b->tv_sec || a->tv_usec <= b->tv_usec)
		return 1;
	
	return 0;
}

int timestamp_is_negative(timestamp_t *ts)
{
	return (ts->tv_sec < 0 || ts->tv_usec < 0);
}

void update_timestamp(timestamp_t *dst)
{
	struct timeval tv;

	gettimeofday(&tv, NULL);

	dst->tv_sec = tv.tv_sec;
	dst->tv_usec = tv.tv_usec;
}

void copy_timestamp(timestamp_t *ts1, timestamp_t *ts2)
{
	ts1->tv_sec = ts2->tv_sec;
	ts2->tv_usec = ts2->tv_usec;
}

float timestamp_diff(timestamp_t *t1, timestamp_t *t2)
{
	float diff;

	diff = t2->tv_sec - t1->tv_sec;
	diff += (((float) t2->tv_usec - (float) t1->tv_usec) / 1000000.0f);

	return diff;
}

#if 0


float diff_now(timestamp_t *t1)
{
	timestamp_t now;
	update_ts(&now);
	return time_diff(t1, &now);
}

const char *xinet_ntop(struct sockaddr *src, char *dst, socklen_t cnt)
{
	void *s;
	int family;

	if (src->sa_family == AF_INET6) {
		s = &((struct sockaddr_in6 *) src)->sin6_addr;
		family = AF_INET6;
	} else if (src->sa_family == AF_INET) {
		s = &((struct sockaddr_in *) src)->sin_addr;
		family = AF_INET;
	} else
		return NULL;

	return inet_ntop(family, s, dst, cnt);
}

uint64_t parse_size(const char *str)
{
	char *p;
	uint64_t l = strtol(str, &p, 0);
	if (p == str)
		return -1;

	if (*p) {
		if (!strcasecmp(p, "kb") || !strcasecmp(p, "k"))
			l *= 1024;
		else if (!strcasecmp(p, "gb") || !strcasecmp(p, "g"))
			l *= 1024*1024*1024;
		else if (!strcasecmp(p, "gbit"))
			l *= 1024*1024*1024/8;
		else if (!strcasecmp(p, "mb") || !strcasecmp(p, "m"))
			l *= 1024*1024;
		else if (!strcasecmp(p, "mbit"))
			l *= 1024*1024/8;
		else if (!strcasecmp(p, "kbit"))
			l *= 1024/8;
		else if (strcasecmp(p, "b") != 0)
			return -1;
	}

	return l;
}

#ifndef HAVE_STRDUP
char *strdup(const char *s)
{
	char *t = xcalloc(1, strlen(s) + 1);
	memcpy(t, s, strlen(s));
	return s;
}
#endif

char *timestamp2str(timestamp_t *ts, char *buf, size_t len)
{
	int i, split[5];
	char *units[] = {"d", "h", "m", "s", "usec"};
	time_t sec = ts->tv_sec;

#define _SPLIT(idx, unit) if ((split[idx] = sec / unit) > 0) sec %= unit
	_SPLIT(0, 86400);	/* days */
	_SPLIT(1, 3600);	/* hours */
	_SPLIT(2, 60);		/* minutes */
	_SPLIT(3, 1);		/* seconds */
#undef  _SPLIT
	split[4] = ts->tv_usec;

	memset(buf, 0, len);

	for (i = 0; i < ARRAY_SIZE(split); i++) {
		if (split[i] > 0) {
			char t[64];
			snprintf(t, sizeof(t), "%s%d%s",
				 strlen(buf) ? " " : "", split[i], units[i]);
			strncat(buf, t, len - strlen(buf) - 1);
		}
	}

	return buf;
}

int parse_date(const char *str, xdate_t *dst)
{
	time_t now = time(NULL);
	struct tm *now_tm = localtime(&now);
	const char *next;
	char *p;
	struct tm backup;

	memset(dst, 0, sizeof(*dst));

	if (strchr(str, '-')) {
		next = strptime(str, "%Y-%m-%d", &dst->d_tm);
		if (next == NULL ||
		    (*next != '\0' && *next != ' '))
			goto invalid_date;
	} else {
		dst->d_tm.tm_mday  = now_tm->tm_mday;
		dst->d_tm.tm_mon   = now_tm->tm_mon;
		dst->d_tm.tm_year  = now_tm->tm_year;
		dst->d_tm.tm_wday  = now_tm->tm_wday;
		dst->d_tm.tm_yday  = now_tm->tm_yday;
		dst->d_tm.tm_isdst = now_tm->tm_isdst;
		next = str;
	}

	if (*next == '\0')
		return 0;

	while (*next == ' ')
		next++;

	if (*next == '.')
		goto read_us;

	/* Make a backup, we can't rely on strptime to not screw
	 * up what we've got so far. */
	memset(&backup, 0, sizeof(backup));
	memcpy(&backup, &dst->d_tm, sizeof(backup));

	next = strptime(next, "%H:%M:%S", &dst->d_tm);
	if (next == NULL ||
	    (*next != '\0' && *next != '.'))
		goto invalid_date;

	dst->d_tm.tm_mday  = backup.tm_mday;
	dst->d_tm.tm_mon   = backup.tm_mon;
	dst->d_tm.tm_year  = backup.tm_year;
	dst->d_tm.tm_wday  = backup.tm_wday;
	dst->d_tm.tm_yday  = backup.tm_yday;
	dst->d_tm.tm_isdst = backup.tm_isdst;

	if (*next == '\0')
		return 0;
read_us:
	if (*next == '.')
		next++;
	else
		BUG();

	dst->d_usec = strtoul(next, &p, 10);

	if (*p != '\0')
		goto invalid_date;
	
	return 0;

invalid_date:
	fprintf(stderr, "Invalid date \"%s\"\n", str);
	return -1;
}

static inline void print_token(FILE *fd, struct db_token *tok)
{
	fprintf(fd, "%s", tok->t_name);

	if (tok->t_flags & DB_T_ATTR)
		fprintf(fd, "<attr>");
}

void db_print_filter(FILE *fd, struct db_filter *filter)
{
	if (filter->f_node)
		print_token(fd, filter->f_node);

	if (filter->f_group) {
		fprintf(fd, ".");
		print_token(fd, filter->f_group);
	}

	if (filter->f_item) {
		fprintf(fd, ".");
		print_token(fd, filter->f_item);
	}

	if (filter->f_attr) {
		fprintf(fd, ".");
		print_token(fd, filter->f_attr);
	}

	if (filter->f_field)
		fprintf(fd, "@%s", filter->f_field);
}

void *db_filter__scan_string(const char *);
void db_filter__switch_to_buffer(void *);
int db_filter_parse(void);

struct db_filter * parse_db_filter(const char *buf)
{
	struct db_filter *f;
	struct db_token *tok, *t;

	void *state = db_filter__scan_string(buf);
	db_filter__switch_to_buffer(state);

	if (db_filter_parse()) {
		fprintf(stderr, "Failed to parse filter \"%s\"\n", buf);
		return NULL;
	}

	tok = db_filter_out; /* generated by yacc */
	if (tok == NULL)
		return NULL;
	
	f = xcalloc(1, sizeof(*f));

	f->f_node = tok;

	if (!tok->t_next)
		goto out;
	tok = tok->t_next;

	if (tok->t_flags & DB_T_ATTR) {
		fprintf(stderr, "Node may not contain an attribute field\n");
		goto errout;
	}

	f->f_group = tok;
	if (!tok->t_next)
		goto out;
	tok = tok->t_next;

	if (tok->t_flags & DB_T_ATTR) {
		fprintf(stderr, "Group may not contain an attribute field\n");
		goto errout;
	}

	f->f_item = tok;

	if (!tok->t_next)
		goto out;
	tok = tok->t_next;

	if (tok->t_flags & DB_T_ATTR) {
		if (!tok->t_name)
			BUG();
		f->f_field = tok->t_name;
		goto out;
	} else
		f->f_attr = tok;

	if (!tok->t_next)
		goto out;
	tok = tok->t_next;

	if (tok->t_flags & DB_T_ATTR) {
		if (!tok->t_name)
			BUG();
		f->f_field = tok->t_name;
	} else {
		fprintf(stderr, "Unexpected additional token after attribute\n");
		goto errout;
	}

out:
	/* free unused tokens */
	for (tok = tok->t_next ; tok; tok = t) {
		t = tok->t_next;
		if (tok->t_name)
			free(tok->t_name);
		free(tok);
	}

	return f;

errout:
	free(f);
	f = NULL;
	tok = db_filter_out;
	goto out;
}
#endif
