/*
 * Copyright (C) 2022, Tencent.
 *
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Distribution License v. 1.0 which is available at
 * https://www.eclipse.org/org/documents/edl-v10.php.
 *
 * SPDX-License-Identifier: BSD-3-Clause
 */

package org.eclipse.jgit.internal.storage.commitgraph;

import org.eclipse.jgit.annotations.NonNull;
import org.eclipse.jgit.internal.JGitText;
import org.eclipse.jgit.lib.AnyObjectId;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.util.NB;

/**
 * The index which are used by the commit-graph to:
 * <ul>
 * <li>Get the object in commit-graph by using a specific position.</li>
 * <li>Get the position of a specific object in commit-graph.</li>
 * </ul>
 */
class GraphObjectIndex {

	private static final int FANOUT = 256;

	private final int hashLength;

	private final int[] fanoutTable;

	private final byte[] oidLookup;

	private final long commitCnt;

	/**
	 * Initialize the GraphObjectIndex.
	 *
	 * @param hashLength
	 *            length of object hash.
	 * @param oidFanout
	 *            content of OID Fanout Chunk.
	 * @param oidLookup
	 *            content of OID Lookup Chunk.
	 * @throws CommitGraphFormatException
	 *             commit-graph file's format is different from we expected.
	 */
	GraphObjectIndex(int hashLength, @NonNull byte[] oidFanout,
			@NonNull byte[] oidLookup) throws CommitGraphFormatException {
		this.hashLength = hashLength;
		this.oidLookup = oidLookup;

		int[] table = new int[FANOUT];
		long uint32;
		for (int k = 0; k < table.length; k++) {
			uint32 = NB.decodeUInt32(oidFanout, k * 4);
			if (table[k] > Integer.MAX_VALUE) {
				throw new CommitGraphFormatException(
						JGitText.get().commitGraphFileIsTooLargeForJgit);
			}
			table[k] = (int) uint32;
		}
		this.fanoutTable = table;
		this.commitCnt = table[FANOUT - 1];
	}

	/**
	 * Find the position in the commit-graph of the specified id.
	 *
	 * @param id
	 *            the id for which the commit-graph position will be found.
	 * @return the commit-graph position or -1 if the object was not found.
	 */
	int findGraphPosition(AnyObjectId id) {
		int levelOne = id.getFirstByte();
		int high = fanoutTable[levelOne];
		int low = 0;
		if (levelOne > 0) {
			low = fanoutTable[levelOne - 1];
		}
		while (low < high) {
			int mid = (low + high) >>> 1;
			int pos = objIdOffset(mid);
			int cmp = id.compareTo(oidLookup, pos);
			if (cmp < 0) {
				high = mid;
			} else if (cmp == 0) {
				return mid;
			} else {
				low = mid + 1;
			}
		}
		return -1;
	}

	/**
	 * Get the object at the commit-graph position.
	 *
	 * @param graphPos
	 *            the position in the commit-graph of the object.
	 * @return the ObjectId or null if it's not found.
	 */
	ObjectId getObjectId(int graphPos) {
		if (graphPos < 0 || graphPos >= commitCnt) {
			return null;
		}
		return ObjectId.fromRaw(oidLookup, objIdOffset(graphPos));
	}

	long getCommitCnt() {
		return commitCnt;
	}

	private int objIdOffset(int pos) {
		return hashLength * pos;
	}
}
