/*
 * Copyright (c) 2013 Martin Sucha
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * - Redistributions of source code must retain the above copyright
 *   notice, this list of conditions and the following disclaimer.
 * - Redistributions in binary form must reproduce the above copyright
 *   notice, this list of conditions and the following disclaimer in the
 *   documentation and/or other materials provided with the distribution.
 * - The name of the author may not be used to endorse or promote products
 *   derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

/** @addtogroup uri
 * @{
 */
/**
 * @file
 */

#include <assert.h>
#include <stdlib.h>
#include <str.h>
#include <ctype.h>
#include <errno.h>

#include "uri.h"
#include "ctype.h"

static char *cut_str(const char *start, const char *end)
{
	size_t size = end - start;
	return str_ndup(start, size);
}

uri_t *uri_parse(const char *str)
{
	uri_t *uri = malloc(sizeof(uri_t));
	if (uri == NULL)
		return NULL;
	memset(uri, 0, sizeof(uri_t));
	
	/* scheme ":" */
	const char *scheme = str;
	while (*str != 0 && *str != ':') str++;
	if (*str == 0) {
		uri_destroy(uri);
		return NULL;
	}
	uri->scheme = cut_str(scheme, str);
	
	/* skip the colon */
	str++;
	
	if (*str == '/' && str[1] == '/') {
		/* "//" [user-info [":" user-credential] "@"] host [":" port] */
		str++;
		str++;
		const char *authority_start = str;
	
		char *host_or_user_info = NULL;
		char *port_or_user_credential = NULL;
	
		while (*str != 0 && *str != '?' && *str != '#' && *str != '@'
			&& *str != ':' && *str != '/')
			str++;
	
		host_or_user_info = cut_str(authority_start, str);
		if (*str == ':') {
			str++;
			const char *second_part = str;
			while (*str != 0 && *str != '?' && *str != '#' &&
				*str != '@' && *str != '/') str++;
			port_or_user_credential = cut_str(second_part, str);
		}
	
		if (*str == '@') {
			uri->user_info = host_or_user_info;
			uri->user_credential = port_or_user_credential;
		
			str++;
			const char *host_start = str;
			while (*str != 0 && *str != '?' && *str != '#'
				&& *str != ':' && *str != '/') str++;
			uri->host = cut_str(host_start, str);
		
			if (*str == ':') {
				str++;
				const char *port_start = str;
				while (*str != 0 && *str != '?' && *str != '#' && *str != '/')
					str++;
				uri->port = cut_str(port_start, str);
			}
		}
		else {
			uri->host = host_or_user_info;
			uri->port = port_or_user_credential;
		}
	}
	
	const char *path_start = str;
	while (*str != 0 && *str != '?' && *str != '#') str++;
	uri->path = cut_str(path_start, str);
	
	if (*str == '?') {
		str++;
		const char *query_start = str;
		while (*str != 0 && *str != '#') str++;
		uri->query = cut_str(query_start, str);
	}
	
	if (*str == '#') {
		str++;
		const char *fragment_start = str;
		while (*str != 0) str++;
		uri->fragment = cut_str(fragment_start, str);
	}
	
	assert(*str == 0);
	return uri;
}

/** Parse the URI scheme
 *
 * @param endptr The position of the first character after the scheme
 *               is stored there.
 * @return EOK on success
 */
int uri_scheme_parse(const char *str, const char **endptr)
{
	if (*str == 0) {
		*endptr = str;
		return ELIMIT;
	}
	
	if (!isalpha(*str)) {
		*endptr = str;
		return EINVAL;
	}
	
	while (isalpha(*str) || isdigit(*str) ||
	    *str == '+' || *str == '-' || *str == '.') {
		str++;
	}
	
	*endptr = str;
	return EOK;
}

/** Determine if the URI scheme is valid
 *
 */
bool uri_scheme_validate(const char *str)
{
	const char *endptr = NULL;
	if (uri_scheme_parse(str, &endptr) != EOK)
		return false;
	return *endptr == 0;
}

int uri_percent_parse(const char *str, const char **endptr,
    uint8_t *decoded)
{
	*endptr = str;
	if (str[0] == 0 || str[1] == 0 || str[2] == 0)
		return ELIMIT;
	
	if (str[0] != '%' || !is_hexdig(str[1]) || !is_hexdig(str[2]))
		return EINVAL;
	
	if (decoded != NULL) {
		int rc = str_uint8_t(str + 1, NULL, 16, true, decoded);
		if (rc != EOK)
			return rc;
	}
	
	*endptr = str + 3;
	return EOK;
}

int uri_user_info_parse(const char *str, const char **endptr)
{
	while (*str != 0) {
		while (is_unreserved(*str) || is_subdelim(*str) || *str == ':') str++;
		if (*str == 0)
			break;
		int rc = uri_percent_parse(str, &str, NULL);
		if (rc != EOK) {
			*endptr = str;
			return rc;
		}
	}
	
	*endptr = str;
	return EOK;
}

bool uri_user_info_validate(const char *str)
{
	const char *endptr = NULL;
	if (uri_user_info_parse(str, &endptr) != EOK)
		return false;
	return *endptr == 0;
}

int uri_port_parse(const char *str, const char **endptr)
{
	if (*str == 0)
		return ELIMIT;
	if (!isdigit(*str))
		return EINVAL;
	while (isdigit(*str)) str++;
	*endptr = str;
	return EOK;
}

bool uri_port_validate(const char *str)
{
	const char *endptr = NULL;
	if (uri_port_parse(str, &endptr) != EOK)
		return false;
	return *endptr == 0;
}

bool uri_validate(uri_t *uri)
{
	if (uri->scheme && !uri_scheme_validate(uri->scheme))
		return false;
	
	if (uri->user_info && !uri_user_info_validate(uri->user_info))
		return false;
	
	if (uri->user_credential && !uri_user_info_validate(uri->user_credential))
		return false;
	
	if (uri->port && !uri_port_validate(uri->port))
		return false;
	
	return true;
}

void uri_destroy(uri_t *uri)
{
	free(uri->scheme);
	free(uri->user_info);
	free(uri->user_credential);
	free(uri->host);
	free(uri->port);
	free(uri->path);
	free(uri->query);
	free(uri->fragment);
	free(uri);
}

/** @}
 */
