<?php

declare(strict_types=1);
namespace Tests\git2;
use git2\git;
use git2\git_error_code;
use git2\git_filemode_t;
use git2\git_index;
use git2\git_index_add_option_t;
use git2\git_index_capability_t;
use git2\git_index_conflict_iterator;
use git2\git_index_entry;
use git2\git_index_iterator;
use git2\git_index_stage_t;
use git2\git_oid;
use git2\git_strarray;

final class IndexTest extends GitTestCase
{

	public function testNew(): void
	{
		$this->assertOK(git::index_new($index));
		$this->assertInstanceOf(git_index::class, $index);
		$this->assertNull(git::index_path($index));
		$this->assertFalse(git::index_has_conflicts($index));
	}

	public function testWriteTree(): void
	{
		$dir = $this->mkdir('write_tree');
		$this->assertOK(git::repository_init($repo, $dir, false));
		$this->assertOK(git::repository_index($index, $repo));
		$this->assertOK(git::index_write_tree($treeId, $index));
		$this->assertInstanceOf(git_oid::class, $treeId);
		$this->assertSame('4b825dc642cb6eb9a060e54bf8d69288fbee4904', git::oid_tostr_s($treeId));
	}

	public function testOpen(): void
	{
		$dir = $this->mkdir('open');
		$this->assertOK(git::repository_init($repo, $dir, true));
		$this->assertOK(git::index_open($index, $dir . '/index'));
		$this->assertInstanceOf(git_index::class, $index);
		$this->assertSame(0, git::index_entrycount($index));
		$this->assertSame(git_error_code::ERROR, git::index_open($index, $dir . '/description'));
		$this->assertNull(git::index_owner($index));
		$this->assertSame(realpath($dir . '/index'), realpath(git::index_path($index)));
		$this->assertOK(git::index_read($index, false));
		$this->assertOK(git::index_read($index, true));
	}

	public function testAddRemovePath(): void
	{
		$dir = $this->mkdir('add_remove');
		$this->assertOK(git::repository_init($repo, $dir, false));
		$this->assertOK(git::repository_index($index, $repo));
		$this->assertTrue(mkdir($dir . '/dir'));
		$this->assertGreaterThan(0, file_put_contents($dir . '/dir/foo', 'foo'));
		$this->assertSame(0, git::index_entrycount($index));
		$this->assertOK(git::index_add_bypath($index, 'dir/foo'));
		$this->assertSame(1, git::index_entrycount($index));
		$this->assertOK(git::index_remove_bypath($index, 'dir/foo'));
		$this->assertSame(0, git::index_entrycount($index));
		$this->assertOK(git::index_add_bypath($index, 'dir/foo'));
		$this->assertSame('19102815663d23f8b75a47e7a01965dcdc96468c', git::oid_tostr_s(git::index_get_bypath($index, 'dir/foo', 0)->id));
		$this->assertSame('19102815663d23f8b75a47e7a01965dcdc96468c', git::oid_tostr_s(git::index_get_byindex($index, 0)->id));
		$this->assertSame(1, git::index_entrycount($index));
		$this->assertOK(git::index_remove_directory($index, 'dir', git_index_stage_t::NORMAL));
		$this->assertSame(0, git::index_entrycount($index));
		$this->assertOK(git::index_write($index));
		$this->assertSame(2, git::index_version($index));
		$this->assertOK(git::index_set_version($index, 3));
		$this->assertSame(3, git::index_version($index));
		$this->assertSame('39d890139ee5356c7ef572216cebcd27aa41f9df', git::oid_tostr_s(git::index_checksum($index)));
		$this->assertOK(git::index_clear($index));
	}

	public function testCaps(): void
	{
		$caps = git_index_capability_t::IGNORE_CASE | git_index_capability_t::NO_SYMLINKS;
		$this->assertOK(git::index_new($index));
		$this->assertSame(0, git::index_caps($index));
		$this->assertOK(git::index_set_caps($index, $caps));
		$this->assertSame($caps, git::index_caps($index));
	}

	public function testAdd(): void
	{
		$entry = new git_index_entry;
		$this->assertSame(0, $entry->ctime->seconds);
		$this->assertSame(0, $entry->ctime->nanoseconds);
		$this->assertSame(0, $entry->mtime->seconds);
		$this->assertSame(0, $entry->mtime->nanoseconds);
		$this->assertSame(0, $entry->dev);
		$this->assertSame(git_filemode_t::UNREADABLE, $entry->mode);
		$this->assertTrue(git::oid_is_zero($entry->id));
		$this->assertNull($entry->path);

		$entry->ctime->seconds     = 1640263000;
		$entry->ctime->nanoseconds = 1234;
		$entry->mtime->seconds     = 1640264000;
		$entry->mtime->nanoseconds = 5678;
		$entry->file_size          = 3;
		$entry->mode               = git_filemode_t::BLOB;
		$entry->path               = 'foo';

		$this->assertSame(1640263000, $entry->ctime->seconds);
		$this->assertSame(1234, $entry->ctime->nanoseconds);
		$this->assertSame(1640264000, $entry->mtime->seconds);
		$this->assertSame(5678, $entry->mtime->nanoseconds);
		$this->assertSame(3, $entry->file_size);
		$this->assertSame(git_filemode_t::BLOB, $entry->mode);
		$this->assertSame('foo', $entry->path);

		git::index_new($index);
		$this->assertOK(git::index_add($index, $entry));
		$this->assertSame(0, git::index_entry_stage($entry));

		$this->assertOK(git::index_find($atPos, $index, 'foo'));
		$this->assertSame(0, $atPos);

		$this->assertOK(git::index_find_prefix($atPos, $index, 'f'));
		$this->assertSame(0, $atPos);
	}

	public function testAddFromBuffer(): void
	{
		$entry = new git_index_entry;
		$entry->ctime->seconds     = 1640263000;
		$entry->ctime->nanoseconds = 1234;
		$entry->mtime->seconds     = 1640264000;
		$entry->mtime->nanoseconds = 5678;
		$entry->file_size          = 3;
		$entry->mode               = git_filemode_t::BLOB;
		$entry->path               = 'foo';

		$dir = $this->mkdir('add_from_buffer');
		git::repository_init($repo, $dir, false);
		git::repository_index($index, $repo);
		$this->assertOK(git::index_add_from_buffer($index, $entry, 'foo'));
		$this->assertFalse(git::index_entry_is_conflict($entry));
	}

	public function testAddAll(): void
	{
		$dir = $this->mkdir('add_all');
		git::repository_init($repo, $dir, false);
		git::repository_index($index, $repo);
		touch($dir . '/foo');
		touch($dir . '/bar');

		$matches = [];

		$this->assertOK(git::index_add_all(
			$index,
			git_strarray::from(['*']),
			git_index_add_option_t::DEFAULT,
			function(string $path, string $matched_pathspec) use(&$matches): int {
				$matches[] = [$path, $matched_pathspec];
				return 0;
			},
		));

		$this->assertSame([['bar', '*'], ['foo', '*']], $matches);
	}

	public function testConflictAdd(): void
	{
		$our                     = new git_index_entry;
		$our->ctime->seconds     = 1640263000;
		$our->ctime->nanoseconds = 1234;
		$our->mtime->seconds     = 1640264000;
		$our->mtime->nanoseconds = 5678;
		$our->file_size          = 3;
		$our->mode               = git_filemode_t::BLOB;
		$our->path               = 'foo';

		$their                     = new git_index_entry;
		$their->ctime->seconds     = 1640263000;
		$their->ctime->nanoseconds = 1234;
		$their->mtime->seconds     = 1640264000;
		$their->mtime->nanoseconds = 5678;
		$their->file_size          = 3;
		$their->mode               = git_filemode_t::BLOB;
		$their->path               = 'foo';

		git::index_new($index);
		$this->assertOK(git::index_conflict_add($index, null, $our, $their));
		$this->assertOK(git::index_conflict_get($gAncestor, $gOur, $gTheir, $index, 'foo'));
		$this->assertNull($gAncestor);
		$this->assertNotNull($gOur);
		$this->assertNotNull($gTheir);

		$this->assertOK(git::index_conflict_iterator_new($iterator, $index));
		$this->assertInstanceOf(git_index_conflict_iterator::class, $iterator);
		$this->assertOK(git::index_conflict_next($iAncestor, $iOur, $iTheir, $iterator));
		$this->assertNull($iAncestor);
		$this->assertNotNull($iOur);
		$this->assertNotNull($iTheir);
		$this->assertSame(git_error_code::ITEROVER, git::index_conflict_next($iAncestor, $iOur, $iTheir, $iterator));

		$this->assertOK(git::index_conflict_remove($index, 'foo'));
		$this->assertOK(git::index_conflict_cleanup($index));
	}

	public function testIterator(): void
	{
		$dir = $this->mkdir('iterator');
		git::repository_init($repo, $dir, false);
		git::repository_index($index, $repo);
		touch($dir . '/foo');
		touch($dir . '/bar');

		git::index_add_bypath($index, 'foo');
		git::index_add_bypath($index, 'bar');

		$this->assertOK(git::index_iterator_new($iterator, $index));
		$this->assertInstanceOf(git_index_iterator::class, $iterator);

		$this->assertOK(git::index_iterator_next($out, $iterator));
		$this->assertInstanceOf(git_index_entry::class, $out);
		$this->assertSame('bar', $out->path);

		$this->assertOK(git::index_iterator_next($out, $iterator));
		$this->assertInstanceOf(git_index_entry::class, $out);
		$this->assertSame('foo', $out->path);

		$this->assertSame(git_error_code::ITEROVER, git::index_iterator_next($out, $iterator));
	}

	public function testRemove(): void
	{
		$dir = $this->mkdir('remove');
		git::repository_init($repo, $dir, false);
		git::repository_index($index, $repo);
		touch($dir . '/foo');
		git::index_add_bypath($index, 'foo');

		$this->assertSame(git_error_code::ENOTFOUND, git::index_remove($index, 'bar', 0));
		$this->assertOK(git::index_remove($index, 'foo', 0));
	}

	public function testRemoveAll(): void
	{
		$dir = $this->mkdir('remove_all');
		git::repository_init($repo, $dir, false);
		git::repository_index($index, $repo);
		touch($dir . '/foo');
		touch($dir . '/bar');

		git::index_add_bypath($index, 'foo');
		git::index_add_bypath($index, 'bar');

		$matches = [];

		$this->assertOK(git::index_remove_all(
			$index,
			git_strarray::from(['*']),
			function(string $path, string $matched_pathspec) use(&$matches): int {
				$matches[] = [$path, $matched_pathspec];
				return 0;
			},
		));

		$this->assertSame([['bar', '*'], ['foo', '*']], $matches);
	}

	public function testUpdateAll(): void
	{
		$dir = $this->mkdir('update_all');
		git::repository_init($repo, $dir, false);
		git::repository_index($index, $repo);
		touch($dir . '/foo');
		touch($dir . '/bar');

		git::index_add_bypath($index, 'foo');
		git::index_add_bypath($index, 'bar');

		file_put_contents($dir . '/foo', 'foo');
		file_put_contents($dir . '/bar', 'bar');

		$matches = [];

		$this->assertOK(git::index_update_all(
			$index,
			git_strarray::from(['*']),
			function(string $path, string $matched_pathspec) use(&$matches): int {
				$matches[] = [$path, $matched_pathspec];
				return 0;
			},
		));

		$this->assertSame([['bar', '*'], ['foo', '*']], $matches);
	}

	public function testWriteTreeTo(): void
	{
		$dir = $this->mkdir('write_tree_to');
		touch($dir . '/foo');
		git::repository_init($repo, $dir, false);
		git::repository_index($index, $repo);
		git::index_add_bypath($index, 'foo');
		git::index_write_tree_to($treeId, $index, $repo);
		git::index_write_tree($treeId, $index);
		git::tree_lookup($tree, $repo, $treeId);
		$this->assertSame('4d5fcadc293a348e88f777dc0920f11e7d71441c', git::oid_tostr_s($treeId));
	}

	public function testReadTree(): void
	{
		$dir = $this->mkdir('read_tree');
		touch($dir . '/foo');
		git::repository_init($repo, $dir, false);
		git::repository_index($index, $repo);
		git::index_add_bypath($index, 'foo');
		git::index_write_tree_to($treeId, $index, $repo);
		git::index_write_tree($treeId, $index);
		git::tree_lookup($tree, $repo, $treeId);
		git::index_new($index2);
		git::index_read_tree($index2, $tree);
		$this->assertSame(1, git::index_entrycount($index2));
	}

}