// SPDX-License-Identifier: Apache-2.0

#include <stdint.h>

void hex_encode(char *output, uint8_t *input, uint32_t length)
{
    for (int i = 0; i < length; i++)
    {
        uint8_t h = (input[i] >> 4);
        *output++ = h > 9 ? h + 'a' - 10 : '0' + h;
        uint8_t l = (input[i] & 0x0f);
        *output++ = l > 9 ? l + 'a' - 10 : '0' + l;
    }
}

void hex_encode_rev(char *output, uint8_t *input, uint32_t length)
{
    for (int i = length - 1; i >= 0; i--)
    {
        uint8_t h = (input[i] >> 4);
        *output++ = h > 9 ? h + 'a' - 10 : '0' + h;
        uint8_t l = (input[i] & 0x0f);
        *output++ = l > 9 ? l + 'a' - 10 : '0' + l;
    }
}

char *uint2hex(char *output, uint8_t *input, uint32_t length)
{
    // first count how many characters
    while (length > 1 && input[length - 1] == 0)
        length--;

    *output++ = '0';
    *output++ = 'x';

    uint8_t h = (input[length - 1] >> 4);
    if (h > 0)
        *output++ = h > 9 ? h + 'a' - 10 : '0' + h;
    uint8_t l = (input[length - 1] & 0x0f);
    *output++ = l > 9 ? l + 'a' - 10 : '0' + l;

    while (--length)
    {
        uint8_t h = (input[length - 1] >> 4);
        *output++ = h > 9 ? h + 'a' - 10 : '0' + h;
        uint8_t l = (input[length - 1] & 0x0f);
        *output++ = l > 9 ? l + 'a' - 10 : '0' + l;
    }

    return output;
}

char *uint2bin(char *output, uint8_t *input, uint32_t length)
{
    // first count how many bytes
    while (length > 1 && input[length - 1] == 0)
        length--;

    *output++ = '0';
    *output++ = 'b';

    uint8_t v = input[length - 1];

    int i = 8;

    while (i > 0 && !(v & 0x80))
    {
        v <<= 1;
        i--;
    }

    while (i--)
    {
        *output++ = v & 0x80 ? '1' : '0';
        v <<= 1;
    }

    while (--length)
    {
        uint8_t v = input[length - 1];
        for (i = 0; i < 8; i++)
        {
            *output++ = v & 0x80 ? '1' : '0';
            v <<= 1;
        }
    }

    return output;
}

char *uint2dec(char *output, uint64_t val)
{
    char buf[20];
    int len = 0;

    // first generate the digits in left-to-right
    do
    {
        buf[len++] = val % 10;
        val /= 10;
    } while (val);

    // now copy them in to right-to-left
    while (len--)
    {
        *output++ = buf[len] + '0';
    }

    return output;
}

extern int udivmod128(const __uint128_t *dividend, const __uint128_t *divisor, __uint128_t *remainder,
                      __uint128_t *quotient);

char *uint128dec(char *output, __uint128_t val128)
{
    // we want 1e19, how to declare such a constant in clang?
    const __uint128_t billion = 10000000000;
    const __uint128_t divisor = billion * 1000000000;
    __uint128_t q, r;
    char buf[40];
    int len = 0;

    // first do the first 19 digits

    // divisor is never zero so we can ignore return value
    udivmod128(&val128, &divisor, &r, &q);

    uint64_t val = r;

    do
    {
        buf[len++] = val % 10;
        val /= 10;
    } while (val);

    /// next 19 digits
    udivmod128(&q, &divisor, &r, &q);

    val = r;

    if (val)
    {
        // add 0s
        while (len < 19)
        {
            buf[len++] = 0;
        }

        do
        {
            buf[len++] = val % 10;
            val /= 10;
        } while (val);
    }

    val = q;

    if (val)
    {
        // add 0s
        while (len < 38)
        {
            buf[len++] = 0;
        }

        do
        {
            buf[len++] = val % 10;
            val /= 10;
        } while (val);
    }

    // now copy them in to right-to-left
    while (len--)
    {
        *output++ = buf[len] + '0';
    }

    return output;
}

typedef unsigned _BitInt(256) uint256_t;

extern int udivmod256(const uint256_t *dividend, const uint256_t *divisor, uint256_t *remainder, uint256_t *quotient);

char *uint256dec(char *output, uint256_t *val256)
{
    // we want 1e19, how to declare such a constant in clang?
    const uint256_t n1e10 = 10000000000;
    const uint256_t n1e9 = 1000000000;
    uint256_t divisor = n1e10 * n1e9;
    uint256_t q = *val256, r;
    char buf[80];
    int len = 0;

    // do the first digits
    for (int digits = 0; digits < 76; digits += 19)
    {
        // divisor is never zero so we can ignore return value
        udivmod256(&q, &divisor, &r, &q);

        uint64_t val = r;

        // add 0s
        while (len < digits)
        {
            buf[len++] = 0;
        }

        do
        {
            buf[len++] = val % 10;
            val /= 10;
        } while (val);

        if (q == (uint256_t)0)
        {
            break;
        }
    }

    uint64_t val = q;

    if (val)
    {
        do
        {
            buf[len++] = val % 10;
            val /= 10;
        } while (val);
    }

    // now copy them in to right-to-left
    while (len--)
    {
        *output++ = buf[len] + '0';
    }

    return output;
}

static const char b58digits[] = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";

// https://github.com/bitcoin/libbase58/blob/b1dd03fa8d1be4be076bb6152325c6b5cf64f678/base58.c inspired this code.
void base58_encode_solana_address(uint8_t *data, uint32_t data_len, uint8_t *output, uint32_t output_len)
{
    uint32_t j, carry, zero_count = 0;

    while (zero_count < data_len && !data[zero_count])
        ++zero_count;

    for (uint32_t i = zero_count, high = output_len - 1; i < data_len; i++, high = j)
    {
        for (carry = data[i], j = output_len - 1; (j > high) || carry; --j)
        {
            carry += 256 * output[j];
            output[j] = carry % 58;
            carry /= 58;
            if (!j)
            {
                break;
            }
        }
    }

    for (j = 0; j < output_len; j++)
    {
        output[j] = b58digits[output[j]];
    }
}
