/*
 * ************************************************************************************************************
 * Author: Marcin Załęczny
 * Copyright (C)2010 Marcin Załęczny <mzaleczny@hotmail.com>
 * License: The GNU Lesser General Public License, version 3.0 (LGPLv3)
 * Library Name: mzstd
 * Version: 1.07
 * First release date:   2010-11-08
 * Current release date: 2011-03-04
 *
 *
 * This file is part of mzstd library
 * 
 * mzstd library is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * mzstd library 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 Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 * ************************************************************************************************************
 */

#include "mzBase64.h"
#include "mzstdGlobals.h"
#include "mzFileUtils.h"
#include <windows.h>

VOID CBase64::_commonInit() {
    this->setRealClassName(L"CBase64");
    this->CHAR_63 = L'+';
    this->CHAR_64 = L'/';
    this->CHAR_PAD = L'=';
}

CBase64::CBase64() {
    this->_commonInit();
}

WINAPI CBase64::~CBase64() {
}

LONG CBase64::encode(const UCHAR*inData, LONG dataLength, CString& outCode) {
	outCode = L"";

	// output buffer which holds code during conversation
	LONG len = this->getCodeLength(dataLength);
    WCHAR* out = new WCHAR[len * 2 + 1 * 2];

	// charachers used by Base64
	static const WCHAR alpha[] = 
	{
		L'A', L'B', L'C', L'D', L'E', L'F', L'G', L'H', L'I', L'J', L'K', L'L', L'M', L'N', L'O', L'P', L'Q', L'R', L'S', L'T', L'U', L'V', L'W', L'X', L'Y', L'Z',
		L'a', L'b', L'c', L'd', L'e', L'f', L'g', L'h', L'i', L'j', L'k', L'l', L'm', L'n', L'o', L'p', L'q', L'r', L's', L't', L'u', L'v', L'w', L'x', L'y', L'z',
		L'0', L'1', L'2', L'3', L'4', L'5', L'6', L'7', L'8', L'9', this->CHAR_63,this->CHAR_64
	};

	// mask - first six bits
	const LONG mask = 0x3F;
    
	// used as temp 24-bits buffer
	union
	{
		UCHAR bytes[4];
		ULONG block;
	} buffer;

	// coversation is done by taking three bytes at time of input data LONG temp
	// then four six-bits values are extracted, converted to base64 characters
	// and at the end they are written to output buffer
    LONG i, j, left, real_len = 0;
	for (i = 0, j = 0, left = dataLength; i < dataLength; i += 3, j += 4, left -= 3) {
		//------------------------
		// filling temp buffer

		// get first byte and puts it at MSB position in temp buffer
		buffer.bytes[2] = inData[i];

		// more data left?
		if (left > 1) {
			// get second byte and puts it at middle position in temp buffer
			buffer.bytes[1] = inData[i + 1];
			// more data left?
			if (left > 2) {
				// get third byte and puts it at LSB position in temp buffer
				buffer.bytes[0] = inData[i + 2];
            } else {
				// zero-padding of input data (last bytes)
				buffer.bytes[0] = 0;
            }
		} else {
			// zero-padding of input data (last two bytes)
			buffer.bytes[1] = 0;
			buffer.bytes[0] = 0;
		}

		//------------------------
		// constructing code from temp buffer
		// and putting it in output buffer

		// extract first and second six-bit value from temp buffer
		// and convert is to base64 character
		out[j] = alpha[(buffer.block >> 18) & mask];
		out[j + 1] = alpha[(buffer.block >> 12) & mask];
        real_len = j + 1 + 1;
		// more data left?
		if (left > 1) {
			// extract third six-bit value from temp buffer
			// and convert it to base64 character
			out[j + 2] = alpha[(buffer.block >> 6) & mask];
            real_len = j + 2 + 1;
			// more data left?
			if (left > 2) {
				// extract forth six-bit value from temp buffer
				// and convert it to base64 character
				out[j + 3] = alpha[buffer.block & mask];
                real_len = j + 3 + 1;
            } else {
				// pad output code
				out[j + 3] = CHAR_PAD;
                real_len = j + 3 + 1;
            }
		} else {
			// pad output code
			out[j + 2] = CHAR_PAD;
			out[j + 3] = CHAR_PAD;
            real_len = j + 3 + 1;
		}
	}

    out[real_len] = NULL;
	outCode = out;
	delete[] out;
	return real_len;
}

LONG CBase64::decode(const CString& inCode, UCHAR* outData) {
    LONG codeLength = inCode.length();

	// used as temp 24-bits buffer
	union
	{
		UCHAR bytes[4];
		ULONG block;
	} buffer;
	buffer.block = 0;

	// number of decoded bytes
	LONG j = 0;

    LONG m;
    WCHAR x;
    LONG val;
	for (LONG i = 0; i < codeLength; i++) {
		// position in temp buffer
		m = i % 4;

        x = inCode.getCharAt(i);
		val = 0;

		// converts base64 character to six-bit value
		if (x >= L'A' && x <= L'Z') {
			val = x - L'A';
        } else if (x >= L'a' && x <= L'z') {
			val = x - L'a' + L'Z' - L'A' + 1;
        } else if (x >= L'0' && x <= L'9') {
			val = x - L'0' + (L'Z' - L'A' + 1) * 2;
        } else if (x == this->CHAR_63) {
			val = 62;
        } else if (x == this->CHAR_64) {
			val = 63;
        }

		// padding chars are not decoded and written to output buffer
		if (x != this->CHAR_PAD) { 
			buffer.block |= val << (3 - m) * 6;
        } else {
			m--;
        }

		// temp buffer is full or end of code is reached
		// flushing temp buffer
		if (m == 3 || x == this->CHAR_PAD) {
			// writes byte from temp buffer (combined from two six-bit values) to output buffer
			outData[j++] = buffer.bytes[2];
			// more data left?
			if (x != this->CHAR_PAD || m > 1) {
				// writes byte from temp buffer (combined from two six-bit values) to output buffer
				outData[j++] = buffer.bytes[1];
				// more data left?
				if (x != this->CHAR_PAD || m > 2) {
					// writes byte from temp buffer (combined from two six-bit values) to output buffer
					outData[j++] = buffer.bytes[0];
                }
			}

			// restarts temp buffer
			buffer.block = 0;
		}

		// when padding CHAR is reached it is the end of code
		if (x == CHAR_PAD) {
			break;
        }
	}

	return j;
}

// Returns maximum size of decoded data based on size of Base64 code.
LONG CBase64::getDataLength(LONG codeLength) {
	return codeLength - codeLength / 4;
}

// Returns maximum length of Base64 code based on size of uncoded data.
LONG CBase64::getCodeLength(LONG dataLength) {
	LONG len = dataLength + dataLength / 3 + (LONG)(dataLength % 3 != 0);

	// output code size must be multiple of 4 bytes
	if (len % 4) {
		len += 4 - len % 4;
    }

	return len;
}

LONG CBase64::encodeFileToVar(CString fname, CString& outCode) {
    DWORD fsize = GetFileSize(fname);
    BYTE* buffer = new BYTE[fsize];
    ReadBinaryFile(fname, buffer);
    LONG len = this->encode(buffer, fsize, outCode);
    delete[] buffer;
    return len;
}

LONG CBase64::encodeFile(CString fname, CString outputFName) {
    if (outputFName.length() < 1) {
        outputFName = fname;
    }
    CString outCode;
    LONG len = this->encodeFileToVar(fname, outCode);
    WriteFile(outputFName, outCode);
    return len;
}

LONG CBase64::decodeFileToVar(CString fname, UCHAR* outData) {
    CString inCode;
    inCode = ReadFile(fname);
    LONG len = this->decode(inCode, outData);
    return len;
}

LONG CBase64::decodeFile(CString fname, CString outputFName) {
    if (outputFName.length() < 1) {
        outputFName = fname;
    }
    CString inCode;
    inCode = ReadFile(fname);
    LONG bufSize = this->getDataLength(inCode.length()) + 1;
    BYTE* buffer = new BYTE[bufSize];
    LONG len = this->decode(inCode, buffer);
    WriteFile(outputFName, buffer, len);
    delete[] buffer;
    return len;
}
