/*

Miranda IM: the free IM client for Microsoft* Windows*

Copyright 2000-2009 Miranda ICQ/IM project,
all portions of this codebase are copyrighted to the people
listed in contributors.txt.

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
*/

#include "..\..\core\commonheaders.h"

#include "JSON_Base64.h"
#include "JSONDefs.h"

#ifdef JSON_BINARY  //if this is not needed, don't waste space compiling it

static const json_char * chars64 = JSON_TEXT("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/");
#ifdef JSON_UNICODE
	static std::string BASE64_EMPTY_STRING;
#else
	#define BASE64_EMPTY_STRING EMPTY_STRING
#endif

json_string JSONBase64::json_encode64(const unsigned char * binary, size_t bytes){
	size_t misaligned = bytes % 3;
	size_t aligned = (bytes - misaligned) / 3;  //divide by three once and inc is faster than add 3 each time
	json_string result;
	result.reserve((size_t)(((float)bytes) * 1.37f) + 4);

	//do all of the ones that are 3 byte aligned
	for (size_t i=0; i < aligned; ++i){
		result += chars64[(binary[0] & 0xFC) >> 2];
		result += chars64[((binary[0] & 0x03) << 4) + ((binary[1] & 0xF0) >> 4)];
		result += chars64[((binary[1] & 0x0F) << 2) + ((binary[2] & 0xC0) >> 6)];
		result += chars64[binary[2] & 0x3F];
		binary += 3;
	}

	if (misaligned){
		//copy the rest into a temporary buffer
		unsigned char temp[3];
		for (unsigned int i=0; i < misaligned; ++i){
			temp[i] = *binary++;
		}
		for (unsigned int i = (unsigned int)misaligned; i < 3; ++i){
			temp[i] = '\0';
		}

		//now do the final three bytes
		result += chars64[(temp[0] & 0xFC) >> 2];
		result += chars64[((temp[0] & 0x03) << 4) + ((temp[1] & 0xF0) >> 4)];
		if (misaligned == 2){
			result += chars64[((temp[1] & 0x0F) << 2) + ((temp[2] & 0xC0) >> 6)];
			result += JSON_TEXT('=');
		} else {
			result += JSON_TEXT(" = = ");
		}
	}
	JSON_ASSERT((size_t)(((float)bytes) * 1.37f) + 4 >= result.length(), JSON_TEXT("Didn't reserve enough space for a one-time go"));
	return result;
}

inline json_char toBinary(json_char c){
	if (c == JSON_TEXT('+')) {
		return JSON_TEXT('>');
	} else if (c == JSON_TEXT('/')) {
		return JSON_TEXT('?');
	} else if (c < JSON_TEXT(':')) {
		return c + JSON_TEXT('\x04');
	} else if (c < JSON_TEXT('[')) {
		return c - JSON_TEXT('\x41');
	}
	return c - 71;
}

/*
 Must be a std::string because it's binary, and chars must be 1 byte
 */
std::string JSONBase64::json_decode64(const json_string & encoded){
	const size_t length = encoded.length();
	#if defined JSON_DEBUG || defined JSON_SAFE
		size_t pos = encoded.find_first_not_of(chars64);
		if (pos != json_string::npos){
			JSON_ASSERT_SAFE(encoded[pos] == JSON_TEXT('='), JSON_TEXT("Not Base64"), return BASE64_EMPTY_STRING;);
			if (pos != length - 1){
				JSON_ASSERT_SAFE(pos == length - 2, JSON_TEXT("Not Base64"), return BASE64_EMPTY_STRING;);
				JSON_ASSERT_SAFE(encoded[pos + 1] == JSON_TEXT('='), JSON_TEXT("Not Base64"), return BASE64_EMPTY_STRING;);
			}
		}
	#endif
	const json_char * runner = encoded.c_str();
	size_t aligned = length / 4; //don't do the last ones as they might be = padding
	std::string result;
	if (aligned){
		--aligned;
		result.reserve((size_t)((float)length / 1.37) + 1);

		//first do the ones that can not have any padding
		for (unsigned int i=0; i < aligned; ++i){
			const json_char second = toBinary(runner[1]);
			const json_char third = toBinary(runner[2]);
			result += (toBinary(runner[0]) << 2) + ((second & 0x30) >> 4);
			result += ((second & 0xf) << 4) + ((third & 0x3c) >> 2);
			result += ((third & 0x3) << 6) + toBinary(runner[3]);
			runner += 4;
		}

		//now do the ones that might have padding, the first two characters can not be padding, so do them quickly
		const char second = toBinary(runner[1]);
		result += (toBinary(runner[0]) << 2) + ((second & 0x30) >> 4);
		if (runner[2] != '=') {  //not two = pads
			const char third = toBinary(runner[2]);
			result += ((second & 0xf) << 4) + ((third & 0x3c) >> 2);
			if (runner[3] != '=') {  //no padding
				result += ((third & 0x3) << 6) + toBinary(runner[3]);
			}
		}
	}
	return result;
}

#endif