/*
 * The MIT License
 *
 * Copyright (c) 2014 GitHub, Inc
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

#include "rugged.h"

extern VALUE rb_mRugged;
extern VALUE rb_cRuggedRepo;
extern VALUE rb_cRuggedObject;
extern VALUE rb_cRuggedReference;
VALUE rb_cRuggedBranch;

static inline VALUE rugged_branch_new(VALUE owner, git_reference *ref)
{
	return rugged_ref_new(rb_cRuggedBranch, owner, ref);
}

/*
 *  call-seq:
 *    branch.head? -> true or false
 *
 *  Returns +true+ if the branch is pointed at by +HEAD+, +false+ otherwise.
 */
static VALUE rb_git_branch_head_p(VALUE self)
{
	git_reference *branch;
	Data_Get_Struct(self, git_reference, branch);
	return git_branch_is_head(branch) ? Qtrue : Qfalse;
}

/*
 *  call-seq:
 *    branch.name -> string
 *
 *  Returns the name of +branch+.
 *
 *  See Rugged::Reference#canonical_name if you need the fully qualified
 *  name of the underlying reference.
 */
static VALUE rb_git_branch_name(VALUE self)
{
	git_reference *branch;
	const char *branch_name;
	Data_Get_Struct(self, git_reference, branch);

	rugged_exception_check(git_branch_name(&branch_name, branch));

	return rb_str_new_utf8(branch_name);
}

static VALUE rb_git_branch__remote_name(VALUE rb_repo, const char *canonical_name)
{
	git_repository *repo;
	git_buf remote_name = { NULL };
	int error;
	VALUE result = Qnil;

	Data_Get_Struct(rb_repo, git_repository, repo);

	if ((error = git_branch_remote_name(&remote_name, repo, canonical_name)) == GIT_OK)
		result = rb_enc_str_new(remote_name.ptr, remote_name.size, rb_utf8_encoding());

	git_buf_free(&remote_name);
	rugged_exception_check(error);

	return result;
}

/*
 *  call-seq:
 *    branch.remote_name -> string
 *
 *  Get the name of the remote the branch belongs to.
 *
 *  If +branch+ is a remote branch, the name of the remote it belongs to is
 *  returned. If +branch+ is a tracking branch, the name of the remote
 *  of the tracked branch is returned.
 *
 *  Otherwise, +nil+ is returned.
 */
static VALUE rb_git_branch_remote_name(VALUE self)
{
	git_reference *branch, *remote_ref;
	int error = 0;

	Data_Get_Struct(self, git_reference, branch);

	if (git_reference_is_remote(branch)) {
		remote_ref = branch;
	} else {
		error = git_branch_upstream(&remote_ref, branch);

		if (error == GIT_ENOTFOUND)
			return Qnil;

		rugged_exception_check(error);
	}

	return rb_git_branch__remote_name(
			rugged_owner(self),
			git_reference_name(remote_ref));
}

/*
 *  call-seq:
 *    branch.upstream -> branch
 *
 *  Returns the remote tracking branch, or +nil+ if the branch is
 *  remote or has no tracking branch.
 */
static VALUE rb_git_branch_upstream(VALUE self)
{
	git_reference *branch, *upstream_branch;
	int error;

	Data_Get_Struct(self, git_reference, branch);

	if (git_reference_is_remote(branch))
		return Qnil;

	error = git_branch_upstream(&upstream_branch, branch);

	if (error == GIT_ENOTFOUND)
		return Qnil;

	rugged_exception_check(error);

	return rugged_branch_new(rugged_owner(self), upstream_branch);
}

/*
 *  call-seq:
 *    branch.upstream = branch
 *
 *  Set the upstream configuration for a given local branch.
 *
 *  Takes a local or remote Rugged::Branch instance or a Rugged::Reference
 *  pointing to a branch.
 */
static VALUE rb_git_branch_set_upstream(VALUE self, VALUE rb_branch)
{
	git_reference *branch, *target_branch;
	const char *target_branch_name;

	Data_Get_Struct(self, git_reference, branch);
	if (!NIL_P(rb_branch)) {
		if (!rb_obj_is_kind_of(rb_branch, rb_cRuggedReference))
			rb_raise(rb_eTypeError, "Expecting a Rugged::Reference instance");

		Data_Get_Struct(rb_branch, git_reference, target_branch);

		rugged_exception_check(
			git_branch_name(&target_branch_name, target_branch)
		);
	} else {
		target_branch_name = NULL;
	}

	rugged_exception_check(
		git_branch_set_upstream(branch, target_branch_name)
	);

	return rb_branch;
}

void Init_rugged_branch(void)
{
	rb_cRuggedBranch = rb_define_class_under(rb_mRugged, "Branch", rb_cRuggedReference);

	rb_define_method(rb_cRuggedBranch, "head?", rb_git_branch_head_p, 0);
	rb_define_method(rb_cRuggedBranch, "name", rb_git_branch_name, 0);
	rb_define_method(rb_cRuggedBranch, "remote_name", rb_git_branch_remote_name, 0);
	rb_define_method(rb_cRuggedBranch, "upstream", rb_git_branch_upstream, 0);
	rb_define_method(rb_cRuggedBranch, "upstream=", rb_git_branch_set_upstream, 1);
}
