// intellectual property is bullshit bgdc

#include "base64.h"

#include <stdbool.h>
#include <stddef.h>
#include <inttypes.h>

#include <avrstuff/pgmspace.h>

static __flash const uint8_t enc_lut[] =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";

// ascii 0x20 through 0x7F
static __flash const uint8_t dec_lut[] =
{
	//     !   "   #   $   %   &   '
	  -1, -1, -1, -1, -1, -1, -1, -1,

	// (   )   *   +   ,   -   .   /
	  -1, -1, -1, 62, -1, -1, -1, 63,

	// 0   1   2   3   4   5   6   7
	  52, 53, 54, 55, 56, 57, 58, 59,

	// 8   9   :   ;   <   =   >   ?
	  60, 61, -1, -1, -1,  0, -1, -1,

	// @   A   B   C   D   E   F   G
	  -1,  0,  1,  2,  3,  4,  5,  6,

	// H   I   J   K   L   M   N   O
	   7,  8,  9, 10, 11, 12, 13, 14,

	// P   Q   R   S   T   U   V   W
	  15, 16, 17, 18, 19, 20, 21, 22,

	// X   Y   Z   [   \   ]   ^   _
	  23, 24, 25, -1, -1, -1, -1, -1,

	// `   a   b   c   d   e   f   g
	  -1, 26, 27, 28, 29, 30, 31, 32,

	// h   i   j   k   l   m   n   o
	  33, 34, 35, 36, 37, 38, 39, 40,

	// p   q   r   s   t   u   v   w
	  41, 42, 43, 44, 45, 46, 47, 48,

	// x   y   z   {   |   }   ~   del
	  49, 50, 51, -1, -1, -1, -1, -1
};

#define DECODE(c) dec_lut[((c) - 0x20) & 0x3F]

static inline uint8_t divmod64(uint32_t * x)
{
	uint8_t mod = *x & 0x3F;
	*x >>= 6;
	return mod;
}

size_t base64_encode(uint8_t * dest, uint8_t const * src, size_t len)
{
	size_t total = 0;

	while (len >= 3)
	{
		uint32_t n = (
			((uint32_t) src[0] << 16uL) |
			((uint32_t) src[1] << 8uL) |
			((uint32_t) src[2] << 0uL));

		dest[3] = enc_lut[divmod64(&n)];
		dest[2] = enc_lut[divmod64(&n)];
		dest[1] = enc_lut[divmod64(&n)];
		dest[0] = enc_lut[n]; // Guaranteed < 64

		src += 3;
		dest += 4;
		len -= 3;
		total += 4;
	}

	if (len)
	{
		uint32_t n = 0;
		n = (uint32_t) src[0] << 16uL;
		if (len > 1) n |= ((uint32_t) src[1] << 8uL);
		if (len > 2) n |= ((uint32_t) src[2] << 0uL);

		divmod64(&n); // dropping at least one
		dest[3] = '=';
		dest[2] = enc_lut[divmod64(&n)];
		dest[1] = enc_lut[divmod64(&n)];
		dest[0] = enc_lut[n]; // Guaranteed < 64

		if (len == 1) dest[2] = '=';
		total += 4;
	}

	return total;
}

size_t base64_decode(uint8_t * dest, uint8_t const * src, size_t len)
{
	size_t total = 0;

	// Splitting the loops this way ensures the final block (possibly
	// containing =) is always processed by the last section but still
	// tolerates extra garbage (whitespace) on the end.

	while (len >= 7)
	{
		uint32_t n = 0;
		n |= (uint32_t) DECODE(src[0]) << 18uL;
		n |= (uint32_t) DECODE(src[1]) << 12uL;
		n |= (uint32_t) DECODE(src[2]) << 6uL;
		n |= (uint32_t) DECODE(src[3]);

		dest[0] = (n >> 16uL) & 0xFF;
		dest[1] = (n >> 8uL) & 0xFF;
		dest[2] = (n >> 0uL) & 0xFF;

		src += 4;
		dest += 3;
		len -= 4;
		total += 3;
	}

	if (len >= 4)
	{

		uint32_t n = 0;
		n |= (uint32_t) DECODE(src[0]) << 18uL;
		n |= (uint32_t) DECODE(src[1]) << 12uL;
		n |= (uint32_t) DECODE(src[2]) << 6uL;
		n |= (uint32_t) DECODE(src[3]);

		dest[0] = (n >> 16uL) & 0xFF;
		dest[1] = (n >> 8uL) & 0xFF;
		dest[2] = (n >> 0uL) & 0xFF;

		int neq = !!(src[3] == '=') + !!(src[2] == '=');

		src += 4;
		dest += 3 - neq;
		len -= 4;
		total += 3 - neq;
	}

	return total;
}
