import { assert } from "../../assert.js";

const base58_alphabet = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz';

const scratch = new Uint8Array(256);

/**
 * Using bitcoin-inspired base58 encoding
 * Primarily intended for UUID compression
 * @param {Uint8Array} source
 * @param {number} offset
 * @param {number} byte_count
 * @returns {string}
 */
export function base58_encode(
    source,
    offset,
    byte_count
) {

    // ported from the bitcoin's C codebase: https://github.com/bitcoin/bitcoin/blob/08a7316c144f9f2516db8fa62400893f4358c5ae/src/base58.cpp#L71

    if (byte_count === 0) {
        return "";
    }

    // Skip & count leading zeroes.
    let zeroes = 0
    let length = 0
    let pbegin = offset
    const pend = offset + byte_count

    while (pbegin !== pend && source[pbegin] === 0) {
        pbegin++
        zeroes++
    }

    // Allocate enough space in big-endian base58 representation.
    const size = ((pend - pbegin) * 138 / 100 + 1) >>> 0; /// log(256) / log(58), rounded up.

    // use scratch if possible to avoid allocation
    const b58 = (size <= scratch.length) ? scratch : new Uint8Array(size);

    // Process the bytes.
    while (pbegin !== pend) {
        let carry = source[pbegin]

        // Apply "b58 = b58 * 256 + ch".
        let i = 0

        for (
            let it1 = size - 1;
             (carry !== 0 || i < length) && (it1 !== -1);
             it1--, i++
        ) {

            carry += (256 * b58[it1]) >>> 0;
            b58[it1] = (carry % 58) >>> 0;
            carry = (carry / 58) >>> 0;

        }

        assert.equal(carry, 0, 'carry must be zero');

        length = i;
        pbegin++;
    }

    // Skip leading zeroes in base58 result.
    let it2 = size - length
    while (it2 !== size && b58[it2] === 0) {
        it2++
    }

    // Translate the result into a string.
    let str = base58_alphabet.charAt(0).repeat(zeroes)
    for (; it2 < size; ++it2) {
        str += base58_alphabet.charAt(b58[it2])
    }

    if (b58 === scratch) {
        // clear out scratch
        scratch.fill(0);
    }

    return str;
}