#include "first.h"

#include "buffer.h"
#include "etag.h"

#include <sys/stat.h>
#include <string.h>

int etag_is_equal(const buffer *etag, const char *line, int weak_ok) {
	enum {
		START = 0,
		CHECK,
		CHECK_QUOTED,
		SKIP,
		SKIP_QUOTED,
		TAIL
	} state = START;

	const char *current;
	const char *tok_start;
	const char *tok = NULL;
	int matched;

	if ('*' == line[0] && '\0' == line[1]) {
		return 1;
	}

	if (!etag || buffer_string_is_empty(etag)) return 0;
	tok_start = etag->ptr;

	if ('W' == tok_start[0]) {
		if (!weak_ok || '/' != tok_start[1]) return 0; /* bad etag */
		tok_start = tok_start + 2;
	}

	if ('"' != tok_start[0]) return 0; /* bad etag */
	/* we start comparing after the first '"' */
	++tok_start;

	for (current = line; *current; ++current) {
		switch (state) {
		case START:
			/* wait for etag to start; ignore whitespace and ',' */
			switch (*current) {
			case 'W':
				/* weak etag always starts with 'W/"' */
				if ('/' != *++current) return 0; /* bad etag list */
				if ('"' != *++current) return 0; /* bad etag list */
				if (!weak_ok) {
					state = SKIP;
				} else {
					state = CHECK;
					tok = tok_start;
				}
				break;
			case '"':
				/* strong etag starts with '"' */
				state = CHECK;
				tok = tok_start;
				break;
			case ' ':
			case ',':
			case '\t':
			case '\r':
			case '\n':
				break;
			default:
				return 0; /* bad etag list */
			}
			break;
		case CHECK:
			/* compare etags (after the beginning '"')
			 * quoted-pairs must match too (i.e. quoted in both strings):
			 * > (RFC 2616:) both validators MUST be identical in every way
			 */
			matched = *tok && *tok == *current;
			++tok;
			switch (*current) {
			case '\\':
				state = matched ? CHECK_QUOTED : SKIP_QUOTED;
				break;
			case '"':
				if (*tok)  {
					/* bad etag - string should end after '"' */
					return 0;
				}
				if (matched) {
					/* matching etag: strings were equal */
					return 1;
				}

				state = TAIL;
				break;
			default:
				if (!matched) {
					/* strings not matching, skip remainder of etag */
					state = SKIP;
				}
				break;
			}
			break;
		case CHECK_QUOTED:
			if (!*tok || *tok != *current) {
				/* strings not matching, skip remainder of etag */
				state = SKIP;
				break;
			}
			++tok;
			state = CHECK;
			break;
		case SKIP:
			/* wait for final (not quoted) '"' */
			switch (*current) {
			case '\\':
				state = SKIP_QUOTED;
				break;
			case '"':
				state = TAIL;
				break;
			}
			break;
		case SKIP_QUOTED:
			state = SKIP;
			break;
		case TAIL:
			/* search for ',', ignore white space */
			switch (*current) {
			case ',':
				state = START;
				break;
			case ' ':
			case '\t':
			case '\r':
			case '\n':
				break;
			default:
				return 0; /* bad etag list */
			}
			break;
		}
	}
	/* no matching etag found */
	return 0;
}

int etag_create(buffer *etag, const struct stat *st, etag_flags_t flags) {
	if (0 == flags) return 0;

	buffer_clear(etag);

	if (flags & ETAG_USE_INODE) {
		buffer_append_int(etag, st->st_ino);
		buffer_append_string_len(etag, CONST_STR_LEN("-"));
	}
	
	if (flags & ETAG_USE_SIZE) {
		buffer_append_int(etag, st->st_size);
		buffer_append_string_len(etag, CONST_STR_LEN("-"));
	}
	
	if (flags & ETAG_USE_MTIME) {
		buffer_append_int(etag, st->st_mtime);
	      #ifdef st_mtime /* use high-precision timestamp if available */
	      #if defined(__APPLE__) && defined(__MACH__)
		buffer_append_int(etag, st->st_mtimespec.tv_nsec);
	      #else
		buffer_append_int(etag, st->st_mtim.tv_nsec);
	      #endif
	      #endif
	}

	return 0;
}

int etag_mutate(buffer *mut, buffer *etag) {
	size_t i, len;
	uint32_t h;

	len = buffer_string_length(etag);
	for (h=0, i=0; i < len; ++i) h = (h<<5)^(h>>27)^(etag->ptr[i]);

	buffer_copy_string_len(mut, CONST_STR_LEN("\""));
	buffer_append_int(mut, h);
	buffer_append_string_len(mut, CONST_STR_LEN("\""));

	return 0;
}
