#include "clar_libgit2.h"
#include "object.h"
#include "signature.h"
#include "tag.h"

static void assert_tag_parses(const char *data, size_t datalen,
	const char *expected_oid,
	const char *expected_name,
	const char *expected_tagger,
	const char *expected_message)
{
	git_tag *tag;

	if (!datalen)
		datalen = strlen(data);

	cl_git_pass(git_object__from_raw((git_object **) &tag, data, datalen, GIT_OBJECT_TAG));
	cl_assert_equal_i(tag->type, GIT_OBJECT_TAG);

	if (expected_oid) {
		git_oid oid;
		cl_git_pass(git_oid_fromstr(&oid, expected_oid));
		cl_assert_equal_oid(&oid, &tag->target);
	}

	if (expected_name)
		cl_assert_equal_s(expected_name, tag->tag_name);
	else
		cl_assert_equal_s(tag->message, NULL);

	if (expected_tagger) {
		git_signature *tagger;
		cl_git_pass(git_signature_from_buffer(&tagger, expected_tagger));
		cl_assert_equal_s(tagger->name, tag->tagger->name);
		cl_assert_equal_s(tagger->email, tag->tagger->email);
		cl_assert_equal_i(tagger->when.time, tag->tagger->when.time);
		cl_assert_equal_i(tagger->when.offset, tag->tagger->when.offset);
		cl_assert_equal_i(tagger->when.sign, tag->tagger->when.sign);
		git_signature_free(tagger);
	} else {
		cl_assert_equal_s(tag->tagger, NULL);
	}

	if (expected_message)
		cl_assert_equal_s(expected_message, tag->message);
	else
		cl_assert_equal_s(tag->message, NULL);

	git_object__free(&tag->object);
}

static void assert_tag_fails(const char *data, size_t datalen)
{
	git_object *object;
	if (!datalen)
		datalen = strlen(data);
	cl_git_fail(git_object__from_raw(&object, data, datalen, GIT_OBJECT_TAG));
}

void test_object_tag_parse__valid_tag_parses(void)
{
	const char *tag =
		"object a8d447f68076d1520f69649bb52629941be7031f\n"
		"type tag\n"
		"tag tagname\n"
		"tagger Taggy Mr. Taggart <taggy@taggart.com>\n"
		"\n"
		"Message";
	assert_tag_parses(tag, 0,
		"a8d447f68076d1520f69649bb52629941be7031f",
		"tagname",
		"Taggy Mr. Taggart <taggy@taggart.com>",
		"Message");
}

void test_object_tag_parse__missing_tagger_parses(void)
{
	const char *tag =
		"object a8d447f68076d1520f69649bb52629941be7031f\n"
		"type tag\n"
		"tag tagname\n"
		"\n"
		"Message";
	assert_tag_parses(tag, 0,
		"a8d447f68076d1520f69649bb52629941be7031f",
		"tagname",
		NULL,
		"Message");
}

void test_object_tag_parse__missing_message_parses(void)
{
	const char *tag =
		"object a8d447f68076d1520f69649bb52629941be7031f\n"
		"type tag\n"
		"tag tagname\n"
		"tagger Taggy Mr. Taggart <taggy@taggart.com>\n";
	assert_tag_parses(tag, 0,
		"a8d447f68076d1520f69649bb52629941be7031f",
		"tagname",
		"Taggy Mr. Taggart <taggy@taggart.com>",
		NULL);
}

void test_object_tag_parse__unknown_field_parses(void)
{
	const char *tag =
		"object a8d447f68076d1520f69649bb52629941be7031f\n"
		"type tag\n"
		"tag tagname\n"
		"tagger Taggy Mr. Taggart <taggy@taggart.com>\n"
		"foo bar\n"
		"frubble frabble\n"
		"\n"
		"Message";
	assert_tag_parses(tag, 0,
		"a8d447f68076d1520f69649bb52629941be7031f",
		"tagname",
		"Taggy Mr. Taggart <taggy@taggart.com>",
		"Message");
}

void test_object_tag_parse__missing_object_fails(void)
{
	const char *tag =
		"type tag\n"
		"tag tagname\n"
		"tagger Taggy Mr. Taggart <taggy@taggart.com>\n"
		"\n"
		"Message";
	assert_tag_fails(tag, 0);
}

void test_object_tag_parse__malformatted_object_fails(void)
{
	const char *tag =
		"object a8d447f68076d15xxxxxxxxxxxxxxxx41be7031f\n"
		"type tag\n"
		"tag tagname\n"
		"tagger Taggy Mr. Taggart <taggy@taggart.com>\n"
		"\n"
		"Message";
	assert_tag_fails(tag, 0);
}

void test_object_tag_parse__missing_type_fails(void)
{
	const char *tag =
		"object a8d447f68076d1520f69649bb52629941be7031f\n"
		"tag tagname\n"
		"tagger Taggy Mr. Taggart <taggy@taggart.com>\n"
		"\n"
		"Message";
	assert_tag_fails(tag, 0);
}

void test_object_tag_parse__invalid_type_fails(void)
{
	const char *tag =
		"object a8d447f68076d1520f69649bb52629941be7031f\n"
		"type garbage\n"
		"tag tagname\n"
		"tagger Taggy Mr. Taggart <taggy@taggart.com>\n"
		"\n"
		"Message";
	assert_tag_fails(tag, 0);
}

void test_object_tag_parse__missing_tagname_fails(void)
{
	const char *tag =
		"object a8d447f68076d1520f69649bb52629941be7031f\n"
		"type tag\n"
		"tagger Taggy Mr. Taggart <taggy@taggart.com>\n"
		"\n"
		"Message";
	assert_tag_fails(tag, 0);
}

void test_object_tag_parse__misformatted_tagger_fails(void)
{
	const char *tag =
		"object a8d447f68076d1520f69649bb52629941be7031f\n"
		"type tag\n"
		"tag Tag\n"
		"tagger taggy@taggart.com>\n"
		"\n"
		"Message";
	assert_tag_fails(tag, 0);
}

void test_object_tag_parse__missing_message_fails(void)
{
	const char *tag =
		"object a8d447f68076d1520f69649bb52629941be7031f\n"
		"type tag\n"
		"tag Tag\n"
		"tagger taggy@taggart.com>\n";
	assert_tag_fails(tag, 0);
}

void test_object_tag_parse__no_oob_read_when_searching_message(void)
{
	const char *tag =
		"object a8d447f68076d1520f69649bb52629941be7031f\n"
		"type tag\n"
		"tag \n"
		"tagger <>\n"
		" \n\n"
		"Message";
	/*
	 * The OOB read previously resulted in an OOM error. We
	 * thus want to make sure that the resulting error is the
	 * expected one.
	 */
	assert_tag_fails(tag, strlen(tag) - strlen("\n\nMessage"));
	cl_assert(strstr(git_error_last()->message, "tag contains no message"));
}