package com.atlassian.stash.internal.commit.graph.cache;

import com.atlassian.bitbucket.i18n.I18nService;
import com.atlassian.bitbucket.repository.Repository;
import com.atlassian.bitbucket.scm.ScmService;
import com.atlassian.util.contentcache.ContentProvider;
import org.apache.commons.codec.DecoderException;
import org.apache.commons.codec.binary.Hex;

/**
 * Utility methods to convert data from or to the index.
 * <p>
 * Commit IDs are converted between raw 20 byte arrays and 40 character hexidecimal strings.
 * <p>
 * Integers are encoded as unsigned, variable length, little-endian byte arrays,
 * where the first bit in each byte is a continuation flag.
 * 0-127 are encoded as a single byte in the range 0x00-0x7F, 128 is encoded as
 * [0x80, 0x01], 129 is encoded as [0x81, 0x01], and so on.
 *
 * @since 2.11
 */
public final class CachedCommitGraphUtils {

    private static final int COMMIT_ID_LENGTH = 40;

    public static byte[] getCommitIdAsByteArray(String commitId) {
        if (commitId.length() != COMMIT_ID_LENGTH) {
            throw new IllegalArgumentException("Invalid commit ID: " + commitId);
        }

        try {
            return Hex.decodeHex(commitId.toCharArray());
        } catch (DecoderException e) {
            throw new IllegalArgumentException(e);
        }
    }

    public static String getByteArrayAsCommitId(byte[] commitId) {
        if (commitId.length != (COMMIT_ID_LENGTH / 2)) {
            throw new IllegalArgumentException("Invalid commit ID (length is " + commitId.length + ')');
        }

        return new String(Hex.encodeHex(commitId));
    }

    public static byte[] getCountAsByteArray(int count) {
        if (count < 0) {
            throw new IllegalArgumentException("Count cannot be negative");
        } else if (count == 0) {
            return new byte[1];
        }

        int length = arrayCountSize(count);
        byte[] buffer = new byte[length];
        for (int i = 0; i < buffer.length; i++) {
            buffer[i] = (byte) (count & 0x7F);
            count >>>= 7;
            if (count > 0) {
                buffer[i] |= 0x80;
            }
        }
        return buffer;
    }
    public static int getByteArrayAsCount(byte[] count) {
        return getByteArrayAsCount(count, 0, count.length);
    }

    public static int getByteArrayAsCount(byte[] count, int offset, int length) {
        if (length == 0) {
            throw new IllegalArgumentException("Count must be a non-zero length array");
        }

        int r = 0;
        for (int i = offset; i < length; i++) {
            r += (count[i] & 0x7F) << (i * 7);
        }

        return r;
    }

    public static ContentProvider getTimedScmContentProvider(Repository repository,
                                                             ScmService scmService,
                                                             I18nService i18nService) {
        return new TimedContentProvider(repository.toString() + ": TraversalScmContentProvider",
                                        new TraversalScmContentProvider(scmService, repository, i18nService));
    }

    public static boolean isLastByteInCountArray(int b) {
        return (b & 0x80) != 0x80;
    }

    private static int arrayCountSize(int count) {
        int highestBit = Integer.SIZE - Integer.numberOfLeadingZeros(count);
        int required = highestBit / 7;
        return (highestBit % 7) == 0 ? required : required + 1;
    }
}
