/*
 * Copyright (c) 2020, Google LLC  and others
 *
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Distribution License v. 1.0 which is available at
 * http://www.eclipse.org/org/documents/edl-v10.php.
 *
 * SPDX-License-Identifier: BSD-3-Clause
 */
package org.eclipse.jgit.internal.storage.dfs;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.Collections;
import java.util.Set;

import org.eclipse.jgit.api.GarbageCollectCommand;
import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.junit.TestRepository;
import org.eclipse.jgit.lib.NullProgressMonitor;
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.transport.FetchResult;
import org.eclipse.jgit.transport.RefSpec;
import org.eclipse.jgit.transport.TransportBundleStream;
import org.eclipse.jgit.transport.URIish;
import org.junit.Before;
import org.junit.Test;

public class DfsBundleWriterTest {
	private TestRepository<InMemoryRepository> git;

	private InMemoryRepository repo;

	@Before
	public void setUp() throws IOException {
		DfsRepositoryDescription desc = new DfsRepositoryDescription("test");
		git = new TestRepository<>(new InMemoryRepository(desc));
		repo = git.getRepository();
	}

	@Test
	public void makeBundle_containsUnreferencedObject() throws Exception {
		RevCommit commit0 = git.commit().message("0").create();
		RevCommit commit1 = git.commit().message("1").parent(commit0).create();
		git.update("master", commit1);

		RevCommit commit2 = git.commit().message("0").create();

		byte[] bundle = makeBundle();
		try (Repository newRepo = new InMemoryRepository(
				new DfsRepositoryDescription("copy"))) {
			fetchFromBundle(newRepo, bundle);
			Ref ref = newRepo.exactRef("refs/heads/master");
			assertNotNull(ref);
			assertEquals(commit1.toObjectId(), ref.getObjectId());

			// Unreferenced objects are included as well.
			assertTrue(newRepo.getObjectDatabase().has(commit2));
		}
	}

	@Test
	public void makeBundle_containsObjectInGcRestPack() throws Exception {
		RevCommit commit0 = git.commit().message("0").create();
		RevCommit commit1 = git.commit().message("1").parent(commit0).create();
		git.update("master", commit1);

		RevCommit commit2 = git.commit().message("0").create();

		// This moves unreachable commit2 to GC_REST pack.
		GarbageCollectCommand gc = Git.wrap(repo).gc();
		gc.call();

		byte[] bundle = makeBundle();
		try (Repository newRepo = new InMemoryRepository(
				new DfsRepositoryDescription("copy"))) {
			fetchFromBundle(newRepo, bundle);
			Ref ref = newRepo.exactRef("refs/heads/master");
			assertNotNull(ref);
			assertEquals(commit1.toObjectId(), ref.getObjectId());

			// Unreferenced objects in GC_REST pack are included as well.
			assertTrue(newRepo.getObjectDatabase().has(commit2));
		}
	}

	private byte[] makeBundle() throws IOException {
		ByteArrayOutputStream out = new ByteArrayOutputStream();
		DfsBundleWriter.writeEntireRepositoryAsBundle(
				NullProgressMonitor.INSTANCE, out, repo);
		return out.toByteArray();
	}

	private static FetchResult fetchFromBundle(Repository newRepo,
			byte[] bundle) throws Exception {
		URIish uri = new URIish("in-memory://");
		ByteArrayInputStream in = new ByteArrayInputStream(bundle);
		RefSpec rs = new RefSpec("refs/heads/*:refs/heads/*");
		Set<RefSpec> refs = Collections.singleton(rs);
		try (TransportBundleStream transport = new TransportBundleStream(
				newRepo, uri, in)) {
			return transport.fetch(NullProgressMonitor.INSTANCE, refs);
		}
	}
}
