#include <panda/encode/base64.h>
#include <cstdint>

namespace panda { namespace encode {

using std::int64_t;

static const int EQ = 254; /* padding */
static const int XX = 255; /* illegal char */
static const int INVALID = XX;

static const char basis64[]    = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
static const char basis64url[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
static const unsigned char index64[256] = {
    XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
    XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
    XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,62, XX,62,XX,63,
    52,53,54,55, 56,57,58,59, 60,61,XX,XX, XX,EQ,XX,XX,
    XX, 0, 1, 2,  3, 4, 5, 6,  7, 8, 9,10, 11,12,13,14,
    15,16,17,18, 19,20,21,22, 23,24,25,XX, XX,XX,XX,63,
    XX,26,27,28, 29,30,31,32, 33,34,35,36, 37,38,39,40,
    41,42,43,44, 45,46,47,48, 49,50,51,XX, XX,XX,XX,XX,

    XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
    XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
    XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
    XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
    XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
    XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
    XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
    XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
};

size_t encode_base64 (const string_view source, char* dest, bool url_mode, bool use_pad) {
    char* ptr = dest;
    const char* basis = url_mode ? basis64url : basis64;
    const char* str = source.data();

    for (int64_t len = source.length(); len > 0; len -= 3) {
        unsigned char c1 = *str++;
        unsigned char c2 = len > 1 ? *str++ : '\0';
        *ptr++ = basis[c1>>2];
        *ptr++ = basis[((c1 & 0x3)<< 4) | ((c2 & 0xF0) >> 4)];
        if (len > 2) {
            unsigned char c3 = *str++;
            *ptr++ = basis[((c2 & 0xF) << 2) | ((c3 & 0xC0) >>6)];
            *ptr++ = basis[c3 & 0x3F];
        } else if (len == 2) {
            *ptr++ = basis[(c2 & 0xF) << 2];
            if (use_pad) *ptr++ = '=';
        } else if (use_pad) {
            *ptr++ = '=';
            *ptr++ = '=';
        }
    }
    return ptr - dest;
}

size_t decode_base64 (const string_view source, char* dest) {
    const unsigned char* str = (const unsigned char*)source.data();
    const unsigned char* const end = str + source.length();
    char* ptr = dest;

    while (str < end) {
        unsigned char c[4];
        int i = 0;
        do {
            unsigned char uc = index64[*str++];
            if (uc != INVALID) c[i++] = uc;
            if (str == end) {
                if (i < 4) {
                    if (i < 2) goto thats_it;
                    if (i == 2) c[2] = EQ;
                    c[3] = EQ;
                }
                break;
            }
        } while (i < 4);

        if (c[0] == EQ || c[1] == EQ) break;
        *ptr++ = (c[0] << 2) | ((c[1] & 0x30) >> 4);
        if (c[2] == EQ) break;
        *ptr++ = ((c[1] & 0x0F) << 4) | ((c[2] & 0x3C) >> 2);
        if (c[3] == EQ) break;
        *ptr++ = ((c[2] & 0x03) << 6) | c[3];
    }

    thats_it:
    return ptr - dest;
}

}}