/*
 * Copyright (C) 2009-2011 the libgit2 contributors
 *
 * This file is part of libgit2, distributed under the GNU GPL v2 with
 * a Linking Exception. For full terms see the included COPYING file.
 */
#include "common.h"
#include "path.h"
#include "posix.h"

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

/*
 * Based on the Android implementation, BSD licensed.
 * Check http://android.git.kernel.org/
 */
int git_path_basename_r(char *buffer, size_t bufflen, const char *path)
{
	const char *endp, *startp;
	int len, result;

	/* Empty or NULL string gets treated as "." */
	if (path == NULL || *path == '\0') {
		startp = ".";
		len		= 1;
		goto Exit;
	}

	/* Strip trailing slashes */
	endp = path + strlen(path) - 1;
	while (endp > path && *endp == '/')
		endp--;

	/* All slashes becomes "/" */
	if (endp == path && *endp == '/') {
		startp = "/";
		len	= 1;
		goto Exit;
	}

	/* Find the start of the base */
	startp = endp;
	while (startp > path && *(startp - 1) != '/')
		startp--;

	len = endp - startp +1;

Exit:
	result = len;
	if (buffer == NULL) {
		return result;
	}
	if (len > (int)bufflen-1) {
		len	= (int)bufflen-1;
		result = GIT_ENOMEM;
	}

	if (len >= 0) {
		memmove(buffer, startp, len);
		buffer[len] = 0;
	}
	return result;
}

/*
 * Based on the Android implementation, BSD licensed.
 * Check http://android.git.kernel.org/
 */
int git_path_dirname_r(char *buffer, size_t bufflen, const char *path)
{
	const char *endp;
	int result, len;

	/* Empty or NULL string gets treated as "." */
	if (path == NULL || *path == '\0') {
		path = ".";
		len = 1;
		goto Exit;
	}

	/* Strip trailing slashes */
	endp = path + strlen(path) - 1;
	while (endp > path && *endp == '/')
		endp--;

	/* Find the start of the dir */
	while (endp > path && *endp != '/')
		endp--;

	/* Either the dir is "/" or there are no slashes */
	if (endp == path) {
		path = (*endp == '/') ? "/" : ".";
		len = 1;
		goto Exit;
	}

	do {
		endp--;
	} while (endp > path && *endp == '/');

	len = endp - path +1;

#ifdef GIT_WIN32
	/* Mimic unix behavior where '/.git' returns '/': 'C:/.git' will return
		'C:/' here */

	if (len == 2 && isalpha(path[0]) && path[1] == ':') {
		len = 3;
		goto Exit;
	}
#endif

Exit:
	result = len;
	if (len+1 > GIT_PATH_MAX) {
		return GIT_ENOMEM;
	}
	if (buffer == NULL)
		return result;

	if (len > (int)bufflen-1) {
		len	= (int)bufflen-1;
		result = GIT_ENOMEM;
	}

	if (len >= 0) {
		memmove(buffer, path, len);
		buffer[len] = 0;
	}
	return result;
}


char *git_path_dirname(const char *path)
{
	char *dname = NULL;
	int len;

	len = (path ? strlen(path) : 0) + 2;
	dname = (char *)git__malloc(len);
	if (dname == NULL)
		return NULL;

	if (git_path_dirname_r(dname, len, path) < GIT_SUCCESS) {
		git__free(dname);
		return NULL;
	}

	return dname;
}

char *git_path_basename(const char *path)
{
	char *bname = NULL;
	int len;

	len = (path ? strlen(path) : 0) + 2;
	bname = (char *)git__malloc(len);
	if (bname == NULL)
		return NULL;

	if (git_path_basename_r(bname, len, path) < GIT_SUCCESS) {
		git__free(bname);
		return NULL;
	}

	return bname;
}


const char *git_path_topdir(const char *path)
{
	size_t len;
	int i;

	assert(path);
	len = strlen(path);

	if (!len || path[len - 1] != '/')
		return NULL;

	for (i = len - 2; i >= 0; --i)
		if (path[i] == '/')
			break;

	return &path[i + 1];
}

void git_path_join_n(char *buffer_out, int count, ...)
{
	va_list ap;
	int i;
	char *buffer_start = buffer_out;

	va_start(ap, count);
	for (i = 0; i < count; ++i) {
		const char *path;
		int len;

		path = va_arg(ap, const char *);

		assert((i == 0) || path != buffer_start);

		if (i > 0 && *path == '/' && buffer_out > buffer_start && buffer_out[-1] == '/')
			path++;

		if (!*path)
			continue;

		len = strlen(path);
		memmove(buffer_out, path, len);
		buffer_out = buffer_out + len;

		if (i < count - 1 && buffer_out[-1] != '/')
			*buffer_out++ = '/';
	}
	va_end(ap);

	*buffer_out = '\0';
}

int git_path_root(const char *path)
{
	int offset = 0;

#ifdef GIT_WIN32
	/* Does the root of the path look like a windows drive ? */
	if (isalpha(path[0]) && (path[1] == ':'))
		offset += 2;
#endif

	if (*(path + offset) == '/')
		return offset;

	return -1;	/* Not a real error. Rather a signal than the path is not rooted */
}

int git_path_prettify(char *path_out, const char *path, const char *base)
{
	char *result;

	if (base == NULL || git_path_root(path) >= 0) {
		result = p_realpath(path, path_out);
	} else {
		char aux_path[GIT_PATH_MAX];
		git_path_join(aux_path, base, path);
		result = p_realpath(aux_path, path_out);
	}

	return result ? GIT_SUCCESS : GIT_EOSERR;
}

int git_path_prettify_dir(char *path_out, const char *path, const char *base)
{
	size_t end;

	if (git_path_prettify(path_out, path, base) < GIT_SUCCESS)
		return GIT_EOSERR;

	end = strlen(path_out);

	if (end && path_out[end - 1] != '/') {
		path_out[end] = '/';
		path_out[end + 1] = '\0';
	}

	return GIT_SUCCESS;
}
