<?php

declare(strict_types=1);
namespace Tests\git2;
use git2\git;
use git2\git_checkout_options;
use git2\git_index;
use git2\git_merge_analysis_t;
use git2\git_merge_file_favor_t;
use git2\git_merge_file_flag_t;
use git2\git_merge_file_input;
use git2\git_merge_file_options;
use git2\git_merge_file_result;
use git2\git_merge_flag_t;
use git2\git_merge_options;
use git2\git_oidarray;
use git2\git_repository_state_t;
use git2\git_reset_t;

final class MergeTest extends GitTestCase
{

	public function testMerge(): void
	{
		$dir = $this->mkdir('merge');
		git::repository_init($repo, $dir, false);
		git::signature_new($sig, 'John Doe', 'john.doe@example.com', 1640263000, 60);
		git::repository_index($index, $repo);

		file_put_contents($dir . '/foo', 'foo');
		git::index_add_bypath($index, 'foo');
		git::index_write_tree($treeId, $index);
		git::tree_lookup($tree, $repo, $treeId);
		git::commit_create_v($id1, $repo, 'HEAD', $sig, $sig, null, "A", $tree);

		git::commit_lookup($commit1, $repo, $id1);
		git::branch_create($b1, $repo, 'b1', $commit1, false);

		file_put_contents($dir . '/bar', 'bar');
		git::index_add_bypath($index, 'bar');
		git::index_write_tree($treeId, $index);
		git::tree_lookup($tree, $repo, $treeId);
		git::commit_create_v($id2, $repo, null, $sig, $sig, null, "B", $tree, $commit1);

		git::commit_lookup($commit2, $repo, $id2);
		git::branch_create($b2, $repo, 'b2', $commit2, false);

		$this->assertOK(git::checkout_options_init($checkoutOpts, git_checkout_options::VERSION));
		git::repository_set_head($repo, 'refs/heads/b1');

		$this->assertOK(git::reset($repo, $commit1, git_reset_t::HARD, $checkoutOpts));

		git::annotated_commit_from_revspec($ref, $repo, 'refs/heads/b2');

		$this->assertOK(git::merge_analysis($analysisOut, $preferenceOut, $repo, [$ref]));
		$this->assertSame(git_merge_analysis_t::NORMAL | git_merge_analysis_t::FASTFORWARD, $analysisOut);
		$this->assertSame(0, $preferenceOut);

		$this->assertOK(git::reference_lookup($head, $repo, 'refs/heads/b1'));
		$this->assertOK(git::merge_analysis_for_ref($analysisOut, $preferenceOut, $repo, $head, [$ref]));
		$this->assertSame(git_merge_analysis_t::NORMAL | git_merge_analysis_t::FASTFORWARD, $analysisOut);
		$this->assertSame(0, $preferenceOut);

		$this->assertFileDoesNotExist($dir . '/bar');
		$this->assertOK(git::merge_options_init($mergeOpts, git_merge_options::VERSION));
		$this->assertOK(git::merge($repo, [$ref], $mergeOpts, $checkoutOpts));
		$this->assertSame(git_repository_state_t::MERGE, git::repository_state($repo));
		$this->assertFalse(git::index_has_conflicts($index));
		$this->assertFileExists($dir . '/bar');

		$this->assertOK(git::merge_base($base, $repo, $id1, $id2));
		$this->assertSame('1edb9fa8bc346ffbae31fa3ff07205e45705bfff', git::oid_tostr_s($base));

		$this->assertOK(git::merge_base_many($baseMany, $repo, [$id1, $id2]));
		$this->assertSame('1edb9fa8bc346ffbae31fa3ff07205e45705bfff', git::oid_tostr_s($baseMany));

		$this->assertOK(git::merge_base_octopus($baseOctopus, $repo, [$id1, $id2]));
		$this->assertSame('1edb9fa8bc346ffbae31fa3ff07205e45705bfff', git::oid_tostr_s($baseOctopus));

		$this->assertOK(git::merge_bases($bases, $repo, $id1, $id2));
		$this->assertInstanceOf(git_oidarray::class, $bases);
		$this->assertCount(1, $bases);
		$this->assertSame('1edb9fa8bc346ffbae31fa3ff07205e45705bfff', git::oid_tostr_s($bases[0]));

		$this->assertOK(git::merge_bases_many($bases, $repo, [$id1, $id2]));
		$this->assertInstanceOf(git_oidarray::class, $bases);
		$this->assertCount(1, $bases);
		$this->assertSame('1edb9fa8bc346ffbae31fa3ff07205e45705bfff', git::oid_tostr_s($bases[0]));

		$this->assertOK(git::merge_commits($mergeCommits, $repo, $commit1, $commit2, $mergeOpts));
		$this->assertInstanceOf(git_index::class, $mergeCommits);
		$this->assertSame(2, git::index_entrycount($mergeCommits));
	}

	public function testMergeFile(): void
	{
		$this->assertOK(git::merge_file_input_init($a, git_merge_file_input::VERSION));

		$this->assertInstanceOf(git_merge_file_input::class, $a);
		$this->assertSame(git_merge_file_input::VERSION, $a->version);
		$this->assertSame(0, $a->size);
		$this->assertNull($a->path);
		$this->assertSame(0, $a->mode);
		$this->assertNull($a->contents);

		$a->mode     = 0644;
		$a->path     = 'foo';
		$a->contents = "a\nc\n";

		$this->assertSame(0644, $a->mode);
		$this->assertSame('foo', $a->path);
		$this->assertSame(4, $a->size);
		$this->assertSame("a\nc\n", $a->contents);

		$this->assertOK(git::merge_file_input_init($b, git_merge_file_input::VERSION));
		$this->assertInstanceOf(git_merge_file_input::class, $b);
		$b->mode     = 0644;
		$b->path     = 'foo';
		$b->contents = "a\nb\nc\n";

		$this->assertOK(git::merge_file_input_init($c, git_merge_file_input::VERSION));
		$this->assertInstanceOf(git_merge_file_input::class, $c);
		$c->mode     = 0644;
		$c->path     = 'foo';
		$c->contents = "a\nc\nd\n";

		$this->assertOK(git::merge_file($result, $a, $b, $c, null));
		$this->assertInstanceOf(git_merge_file_result::class, $result);
		$this->assertTrue($result->automergeable);
		$this->assertSame(0644, $result->mode);
		$this->assertSame('foo', $result->path);
		$this->assertSame("a\nb\nc\nd\n", $result->contents);
	}

	public function testMergeFileFromIndex(): void
	{
		$dir = $this->mkdir('merge_file_from_index');
		git::repository_init($repo, $dir, false);
		git::repository_index($index, $repo);

		file_put_contents($dir . '/a', "a\nc\n");
		file_put_contents($dir . '/b', "a\nb\nc\n");
		file_put_contents($dir . '/c', "a\nc\nd\n");

		$this->assertOK(git::index_add_bypath($index, 'a'));
		$this->assertOK(git::index_add_bypath($index, 'b'));
		$this->assertOK(git::index_add_bypath($index, 'c'));

		$this->assertNotNull($a = git::index_get_bypath($index, 'a', 0));
		$this->assertNotNull($b = git::index_get_bypath($index, 'b', 0));
		$this->assertNotNull($c = git::index_get_bypath($index, 'c', 0));

		$this->assertOK(git::merge_file_from_index($result, $repo, $a, $b, $c, null));
		$this->assertInstanceOf(git_merge_file_result::class, $result);
		$this->assertSame("a\nb\nc\nd\n", $result->contents);
		$this->assertNull($result->path);
	}

	public function testMergeTrees(): void
	{
		$dir = $this->mkdir('merge_trees');
		git::repository_init($repo, $dir, false);
		git::repository_index($index, $repo);

		file_put_contents($dir . '/foo', "a\n");
		file_put_contents($dir . '/bar', "b\n");

		git::index_add_bypath($index, 'foo');
		git::index_write_tree($treeId, $index);
		git::tree_lookup($tree, $repo, $treeId);
		git::tree_dup($dup, $tree);

		git::index_add_bypath($index, 'bar');
		git::index_write_tree($treeId2, $index);
		git::tree_lookup($tree2, $repo, $treeId2);

		$this->assertOK(git::merge_trees($result, $repo, null, $tree, $tree2, null));
		$this->assertInstanceOf(git_index::class, $result);
		$this->assertSame(2, git::index_entrycount($result));
	}

	public function testMergeFileOpts(): void
	{
		$this->assertOK(git::merge_file_options_init($opts, git_merge_file_options::VERSION));
		$this->assertInstanceOf(git_merge_file_options::class, $opts);
		$this->assertSame(git_merge_file_options::VERSION, $opts->version);
		$this->assertSame(0, $opts->flags);
		$this->assertSame(0, $opts->marker_size);
		$this->assertNull($opts->ancestor_label);
		$this->assertNull($opts->our_label);
		$this->assertNull($opts->their_label);
		$this->assertSame(git_merge_file_favor_t::NORMAL, $opts->favor);

		$opts->flags         |= git_merge_file_flag_t::IGNORE_WHITESPACE;
		$opts->marker_size    = 100;
		$opts->ancestor_label = 'ancestor';
		$opts->our_label      = 'our';
		$opts->their_label    = 'their';
		$opts->favor          = git_merge_file_favor_t::UNION;

		$this->assertSame(git_merge_file_flag_t::IGNORE_WHITESPACE, $opts->flags);
		$this->assertSame(100, $opts->marker_size);
		$this->assertSame('ancestor', $opts->ancestor_label);
		$this->assertSame('our', $opts->our_label);
		$this->assertSame('their', $opts->their_label);
		$this->assertSame(git_merge_file_favor_t::UNION, $opts->favor);

		$opts->ancestor_label = null;
		$this->assertNull($opts->ancestor_label);

		$opts->ancestor_label = '';
		$this->assertSame('', $opts->ancestor_label);
	}

	public function testMergeOpts(): void
	{
		$this->assertOK(git::merge_options_init($opts, git_merge_options::VERSION));
		$this->assertInstanceOf(git_merge_options::class, $opts);
		$this->assertSame(git_merge_options::VERSION, $opts->version);
		$this->assertSame(git_merge_flag_t::FIND_RENAMES, $opts->flags);
		$this->assertSame(0, $opts->rename_threshold);
		$this->assertSame(0, $opts->target_limit);
		$this->assertSame(0, $opts->recursion_limit);
		$this->assertSame(0, $opts->file_flags);
		$this->assertNull($opts->metric);
		$this->assertNull($opts->default_driver);
		$this->assertSame(git_merge_file_favor_t::NORMAL, $opts->file_favor);

		$opts->flags           |= git_merge_flag_t::FAIL_ON_CONFLICT;
		$opts->rename_threshold = 1;
		$opts->target_limit     = 2;
		$opts->recursion_limit  = 3;
		$opts->file_flags       = git_merge_file_flag_t::IGNORE_WHITESPACE;
		$opts->file_favor       = git_merge_file_favor_t::UNION;

		$this->assertSame(git_merge_flag_t::FIND_RENAMES | git_merge_flag_t::FAIL_ON_CONFLICT, $opts->flags);
		$this->assertSame(1, $opts->rename_threshold);
		$this->assertSame(2, $opts->target_limit);
		$this->assertSame(3, $opts->recursion_limit);
		$this->assertSame(git_merge_file_flag_t::IGNORE_WHITESPACE, $opts->file_flags);
		$this->assertSame(git_merge_file_favor_t::UNION, $opts->file_favor);
	}

}