#include <uwsgi.h>

extern struct uwsgi_server uwsgi;

struct uwsgi_redislog_state {
	int fd;
	char *password;
	char *address;
	char *id;
	char *command;
	char *prefix;
	char msgsize[11];
	struct iovec iovec[7];
	char response[8];
};

static char *uwsgi_redis_logger_build_command(char *src) {
	ssize_t len = 4096;
	char *dst = uwsgi_calloc(len);
	char *orig_dst = dst;
	int count = 2;
	char *ptr = src;
	
	// first of all, count the number of spaces
	while(*ptr++) { if (*ptr == ' ') count++; }

	int pos = snprintf(dst, 4096, "*%d\r\n", count);
	dst+=pos;
	len -= pos;

	ptr = src;
	char *base = src;

	while(*ptr++) {
		if (*ptr == ' ') {
			pos = snprintf(dst, len, "$%d\r\n%.*s\r\n", (int) (ptr-base), (int) (ptr-base), base);
			if (pos >= len || pos < 0) {
				// i do not know what to do, better to exit...
				exit(1);
			}
			base = ptr+1;
			dst+=pos;
			len-= pos;
		}	
	}

	pos = snprintf(dst, len, "$%d\r\n%.*s\r\n", (int) ((ptr-1)-base), (int) ((ptr-1)-base), base);
	if (pos > len || pos < 0) {
		// i do not know what to do, better to exit...
		exit(1);
	}
	return orig_dst;
}

static ssize_t uwsgi_redis_logger_discard_response(struct uwsgi_redislog_state *uredislog) {
	ssize_t ret = 0, ret2;
again:
	// read til a \n is found (ugly but fast)
	ret2 = read(uredislog->fd, uredislog->response, 8);
	if (ret2 <= 0) {
		close(uredislog->fd);
		uredislog->fd = -1;
		return -1;
	}
	ret += ret2;
	if (!memchr(uredislog->response, '\n', ret2)) {
		goto again;
	}
	return ret;
}

ssize_t uwsgi_redis_logger(struct uwsgi_logger *ul, char *message, size_t len) {

	ssize_t ret;
	struct uwsgi_redislog_state *uredislog = NULL;

	if (!ul->configured) {

		if (!ul->data) {
			ul->data = uwsgi_calloc(sizeof(struct uwsgi_redislog_state));
			uredislog = (struct uwsgi_redislog_state *) ul->data;
		}

		if (ul->arg != NULL) {
			char *logarg = uwsgi_str(ul->arg);
			char *at = strchr(logarg, '@');
			if (at) {
			  *at = 0;
			  uredislog->password = logarg;
			  logarg = at + 1;
			}
			char *comma1 = strchr(logarg, ',');
			if (!comma1) {
				char *slash = strchr(logarg, '/');
				if (slash) {
				  *slash = 0;
				  uredislog->id = slash + 1;
				}
				uredislog->address = uwsgi_resolve_ip(logarg);
				goto done;
			}
			*comma1 = 0;
			char *slash = strchr(logarg, '/');
			if (slash) {
			  *slash = 0;
			  uredislog->id = slash + 1;
			}
			uredislog->address = logarg;
			comma1++;
			if (*comma1 == 0) goto done;

			char *comma2 = strchr(comma1,',');
			if (!comma2) {
				uredislog->command = uwsgi_redis_logger_build_command(comma1);
				goto done;
			}

			*comma2 = 0;
			uredislog->command = uwsgi_redis_logger_build_command(comma1);
			comma2++;
			if (*comma2 == 0) goto done;

			uredislog->prefix = comma2;
		}

done:

		if (!uredislog->password) uredislog->password = NULL;
		if (!uredislog->id) uredislog->id = "0";
		if (!uredislog->address) uredislog->address = uwsgi_str("127.0.0.1:6379");
		if (!uredislog->command) uredislog->command = "*3\r\n$7\r\npublish\r\n$5\r\nuwsgi\r\n";
		if (!uredislog->prefix) uredislog->prefix = "";

		uredislog->fd = -1;

		uredislog->iovec[0].iov_base = uredislog->command;
		uredislog->iovec[0].iov_len = strlen(uredislog->command);
		uredislog->iovec[1].iov_base = "$";
		uredislog->iovec[1].iov_len = 1;

		uredislog->iovec[2].iov_base = uredislog->msgsize;

		uredislog->iovec[3].iov_base = "\r\n";
		uredislog->iovec[3].iov_len = 2;

		uredislog->iovec[4].iov_base = uredislog->prefix;
		uredislog->iovec[4].iov_len = strlen(uredislog->prefix);

		uredislog->iovec[6].iov_base = "\r\n";
		uredislog->iovec[6].iov_len = 2;

		ul->configured = 1;
	}

	uredislog = (struct uwsgi_redislog_state *) ul->data;
	if (uredislog->fd == -1) {
		struct iovec	setup_iov;
		char		setup_buf[4096];
		uredislog->fd = uwsgi_connect(uredislog->address, uwsgi.socket_timeout, 0);
		if (uredislog->password) {
		  setup_iov.iov_len = snprintf(
		    setup_buf, sizeof (setup_buf), "*2\r\n$4\r\nauth\r\n$%zu\r\n%*s\r\n",
		    strlen(uredislog->password), (int)strlen(uredislog->password), uredislog->password);
		  setup_iov.iov_base = setup_buf;
		  ret = writev(uredislog->fd, &setup_iov, 1);
		  if (ret <= 0) {
		    close(uredislog->fd);
		    uredislog->fd = -1;
		    return -1;
		  }
		  uwsgi_redis_logger_discard_response(uredislog);
		}
		if (uredislog->id) {
		  setup_iov.iov_len = snprintf(
		    setup_buf, sizeof (setup_buf), "*2\r\n$6\r\nselect\r\n$%zu\r\n%*s\r\n",
	            strlen(uredislog->id), (int)strlen(uredislog->id), uredislog->id);
		  setup_iov.iov_base = setup_buf;
		  ret = writev(uredislog->fd, &setup_iov, 1);
		  if (ret <= 0) {
		    close(uredislog->fd);
		    uredislog->fd = -1;
		    return -1;
		  }
		  uwsgi_redis_logger_discard_response(uredislog);
		}
	}

	if (uredislog->fd == -1) return -1;

	// drop newline
        if (message[len-1] == '\n') len--;

	uwsgi_num2str2(len + uredislog->iovec[4].iov_len, uredislog->msgsize);
	uredislog->iovec[2].iov_len = strlen(uredislog->msgsize);

	uredislog->iovec[5].iov_base = message;
	uredislog->iovec[5].iov_len = len;
	
	ret = writev(uredislog->fd, uredislog->iovec, 7);
	if (ret <= 0) {
		close(uredislog->fd);
		uredislog->fd = -1;
		return -1;
	}

	uwsgi_redis_logger_discard_response(uredislog);

	return ret;

}

void uwsgi_redislog_register() {
	uwsgi_register_logger("redislog", uwsgi_redis_logger);
}

struct uwsgi_plugin redislog_plugin = {

        .name = "redislog",
        .on_load = uwsgi_redislog_register,

};

