<?php

declare(strict_types=1);
namespace Tests\git2;
use git2\git;
use git2\git_error_code;
use git2\git_filemode_t;
use git2\git_object;
use git2\git_object_t;
use git2\git_tree;
use git2\git_tree_entry;
use git2\git_tree_update;
use git2\git_tree_update_t;
use git2\git_treewalk_mode;

final class TreeTest extends GitTestCase
{

	public function testLookup(): void
	{
		$dir = $this->mkdir('lookup');
		$this->assertOK(git::repository_init($repo, $dir, false));
		$this->assertOK(git::repository_index($index, $repo));
		$this->assertOK(git::index_write_tree($treeId, $index));

		$this->assertOK(git::tree_lookup($tree, $repo, $treeId));
		$this->assertInstanceOf(git_tree::class, $tree);

		$this->assertOK(git::tree_lookup_prefix($treePrefix, $repo, $treeId, 7));
		$this->assertInstanceOf(git_tree::class, $treePrefix);

		$this->assertSame('4b825dc642cb6eb9a060e54bf8d69288fbee4904', git::oid_tostr_s(git::tree_id($tree)));
		$this->assertSame($repo, git::tree_owner($tree));
	}

	public function testCreateUpdated(): void
	{
		$dir = $this->mkdir('create_updated');
		touch($dir . '/foo');
		touch($dir . '/bar');
		touch($dir . '/fuz');
		git::repository_init($repo, $dir, false);
		git::repository_index($index, $repo);
		git::index_add_bypath($index, 'foo');
		git::index_add_bypath($index, 'bar');
		git::index_add_bypath($index, 'fuz');
		git::index_write_tree($treeId, $index);
		git::tree_lookup($tree, $repo, $treeId);

		$update1 = new git_tree_update;
		$update1->action = git_tree_update_t::REMOVE;
		$update1->filemode = git_filemode_t::BLOB;
		$update1->path = 'foo';

		$update2 = new git_tree_update;
		$update2->action = git_tree_update_t::REMOVE;
		$update2->filemode = git_filemode_t::BLOB;
		$update2->path = 'bar';

		$this->assertOK(git::tree_create_updated(
			$oid,
			$repo,
			$tree,
			[$update1, $update2],
		));

		$this->assertOK(git::tree_lookup($result, $repo, $oid));
		$this->assertSame(1, git::tree_entrycount($result));
		$entry = git::tree_entry_byindex($result, 0);
		$this->assertSame('fuz', git::tree_entry_name($entry));
	}

	public function testDup(): void
	{
		$dir = $this->mkdir('dup');
		touch($dir . '/foo');
		git::repository_init($repo, $dir, false);
		git::repository_index($index, $repo);
		git::index_add_bypath($index, 'foo');
		git::index_write_tree($treeId, $index);
		git::tree_lookup($tree, $repo, $treeId);

		$this->assertOK(git::tree_dup($dup, $tree));
		$this->assertSame(1, git::tree_entrycount($dup));
	}

	public function testEntryBy(): void
	{
		$dir = $this->mkdir('entry_by');
		touch($dir . '/foo');
		git::repository_init($repo, $dir, false);
		git::repository_index($index, $repo);
		git::index_add_bypath($index, 'foo');
		git::index_write_tree($treeId, $index);
		git::tree_lookup($tree, $repo, $treeId);

		$this->assertNull(git::tree_entry_byindex($tree, 1));
		$this->assertSame(
			'e69de29bb2d1d6434b8b29ae775ad8c2e48c5391',
			git::oid_tostr_s(git::tree_entry_id(git::tree_entry_byindex($tree, 0)))
		);

		$this->assertNull(git::tree_entry_byname($tree, 'bar'));
		$this->assertSame(
			'e69de29bb2d1d6434b8b29ae775ad8c2e48c5391',
			git::oid_tostr_s(git::tree_entry_id(git::tree_entry_byname($tree, 'foo')))
		);

		git::oid_fromstr($oid1, '0000000000000000000000000000000000000000');
		git::oid_fromstr($oid2, 'e69de29bb2d1d6434b8b29ae775ad8c2e48c5391');
		$this->assertNull(git::tree_entry_byid($tree, $oid1));
		$this->assertSame(
			'e69de29bb2d1d6434b8b29ae775ad8c2e48c5391',
			git::oid_tostr_s(git::tree_entry_id(git::tree_entry_byid($tree, $oid2)))
		);

		$this->assertSame(git_error_code::ENOTFOUND, git::tree_entry_bypath($_, $tree, 'bar'));
		$this->assertOK(git::tree_entry_bypath($entry, $tree, 'foo'));
		$this->assertSame(
			'e69de29bb2d1d6434b8b29ae775ad8c2e48c5391',
			git::oid_tostr_s(git::tree_entry_id($entry))
		);

		git::tree_entry_bypath($byPath, $tree, 'foo');
		$byIndex = git::tree_entry_byindex($tree, 0);
		$byName  = git::tree_entry_byname($tree, 'foo');

		$this->assertSame($byIndex, $byName);
		$this->assertNotSame($byPath, $byIndex);
	}

	public function testEntryCmp(): void
	{
		$dir = $this->mkdir('entry_cmp');
		touch($dir . '/foo');
		touch($dir . '/bar');
		git::repository_init($repo, $dir, false);
		git::repository_index($index, $repo);
		git::index_add_bypath($index, 'foo');
		git::index_add_bypath($index, 'bar');
		git::index_write_tree($treeId, $index);
		git::tree_lookup($tree, $repo, $treeId);

		$foo = git::tree_entry_byname($tree, 'foo');
		$bar = git::tree_entry_byname($tree, 'bar');

		$this->assertSame(0, git::tree_entry_cmp($foo, $foo));
		$this->assertSame(0, git::tree_entry_cmp($bar, $bar));

		// Depends on the platform.
		$this->assertGreaterThan(0, git::tree_entry_cmp($foo, $bar));
		$this->assertLessThan(0, git::tree_entry_cmp($bar, $foo));
	}

	public function testEntryOwnerRetain(): void
	{
		$entry = (function(): git_tree_entry {
			$dir = $this->mkdir('entry_owner_retain');
			touch($dir . '/foo');
			git::repository_init($repo, $dir, false);
			git::repository_index($index, $repo);
			git::index_add_bypath($index, 'foo');
			git::index_write_tree($treeId, $index);
			git::tree_lookup($tree, $repo, $treeId);
			return git::tree_entry_byindex($tree, 0);
		})();

		$this->assertSame('foo', git::tree_entry_name($entry));
	}

	public function testEntryDup(): void
	{
		$entry = (function(): git_tree_entry {
			$dir = $this->mkdir('entry_dup');
			touch($dir . '/foo');
			git::repository_init($repo, $dir, false);
			git::repository_index($index, $repo);
			git::index_add_bypath($index, 'foo');
			git::index_write_tree($treeId, $index);
			git::tree_lookup($tree, $repo, $treeId);
			$src = git::tree_entry_byindex($tree, 0);
			$this->assertOK(git::tree_entry_dup($dest, $src));
			return $dest;
		})();

		$this->assertSame('foo', git::tree_entry_name($entry));
	}

	public function testEntry(): void
	{
		$dir = $this->mkdir('entry');
		touch($dir . '/foo');
		git::repository_init($repo, $dir, false);
		git::repository_index($index, $repo);
		git::index_add_bypath($index, 'foo');
		git::index_write_tree($treeId, $index);
		git::tree_lookup($tree, $repo, $treeId);
		$entry = git::tree_entry_byindex($tree, 0);
		$this->assertSame(git_filemode_t::BLOB, git::tree_entry_filemode($entry));
		$this->assertSame(git_filemode_t::BLOB, git::tree_entry_filemode_raw($entry));
		$this->assertSame(git_object_t::BLOB, git::tree_entry_type($entry));
	}

	public function testEntryToObject(): void
	{
		$dir = $this->mkdir('entry');
		touch($dir . '/foo');
		git::repository_init($repo, $dir, false);
		git::repository_index($index, $repo);
		git::index_add_bypath($index, 'foo');
		git::index_write_tree($treeId, $index);
		git::tree_lookup($tree, $repo, $treeId);
		$entry = git::tree_entry_byindex($tree, 0);
		$this->assertOK(git::tree_entry_to_object($object, $repo, $entry));
		$this->assertInstanceOf(git_object::class, $object);
		$this->assertSame('e69de29bb2d1d6434b8b29ae775ad8c2e48c5391', git::oid_tostr_s(git::object_id($object)));
	}

	public function testWalk(): void
	{
		$dir = $this->mkdir('walk');
		touch($dir . '/foo');
		git::repository_init($repo, $dir, false);
		git::repository_index($index, $repo);
		git::index_add_bypath($index, 'foo');
		git::index_write_tree($treeId, $index);
		git::tree_lookup($tree, $repo, $treeId);

		$this->assertOK(git::tree_walk($tree, git_treewalk_mode::PRE, function(string $root, git_tree_entry $entry): int {
			$this->assertSame('', $root);
			$this->assertSame('foo', git::tree_entry_name($entry));
			return 0;
		}));
	}

}