/*
    PIP - Platform Independent Primitives
    Byte array
	Copyright (C) 2016  Ivan Pelipenko peri4ko@gmail.com

    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 3 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, see <http://www.gnu.org/licenses/>.
*/

#include "pibytearray.h"
#include "pistring.h"

/*! \class PIByteArray
 * \brief Byte array
 * \details This class based on PIDeque<uchar> and provide some handle function
 * to manipulate it.
 * 
 * \section PIByteArray_sec0 Usage
 * %PIByteArray can be used to store custom data and manipulate it. There are many
 * stream operators to store/restore common types to byte array. Store operators
 * places data at the end of array, restore operators takes data from the beginning
 * of array.
 * In addition there are Base 64 convertions and checksums:
 * * plain 8-bit
 * * plain 32-bit
 * 
 * One of the major usage of %PIByteArray is stream functions. You can form binary
 * packet from many types (also dynamic types, e.g. PIVector) with one line:
 * \snippet pibytearray.cpp 0
 * 
 * Or you can descibe stream operator of your own type and store/restore vectors of
 * your type:
 * \snippet pibytearray.cpp 1
 * 
 * For store/restore custom data blocks there is PIByteArray::RawData class. Stream
 * operators of this class simply store/restore data block to/from byte array.
 * \snippet pibytearray.cpp 2
 * 
 * \section PIByteArray_sec1 Attention
 * Stream operator of %PIByteArray store byte array as vector, not simply append
 * content of byte array. This operators useful to transmit custom data as %PIByteArray
 * packed into parent byte array, e.g. to form packet from %PIByteArray.
 * To append one byte array to another use funtion \a append().
 * \snippet pibytearray.cpp 3
 * 
 * 
 */


PIByteArray &PIByteArray::convertToBase64() {
	return *this = toBase64();
}


PIByteArray &PIByteArray::convertFromBase64() {
	return *this = fromBase64(*this);
}


PIByteArray PIByteArray::toBase64() const {
	const char alphabet[] = "ABCDEFGH" "IJKLMNOP" "QRSTUVWX" "YZabcdef"
							"ghijklmn" "opqrstuv" "wxyz0123" "456789+/";
	const char padchar = '=';
	int padlen = 0;
	PIByteArray tmp((size() * 4) / 3 + 3);
	int i = 0;
	uchar *out = tmp.data();
	while (i < size_s()) {
		int chunk = 0;
		chunk |= int((*this)[i++]) << 16;
		if (i == size()) {
			padlen = 2;
		} else {
			chunk |= int((*this)[i++]) << 8;
			if (i == size()) padlen = 1;
			else chunk |= int((*this)[i++]);
		}
		int j = (chunk & 0x00fc0000) >> 18;
		int k = (chunk & 0x0003f000) >> 12;
		int l = (chunk & 0x00000fc0) >> 6;
		int m = (chunk & 0x0000003f);
		*out++ = alphabet[j];
		*out++ = alphabet[k];
		if (padlen > 1) *out++ = padchar;
		else *out++ = alphabet[l];
		if (padlen > 0) *out++ = padchar;
		else *out++ = alphabet[m];
	}
	tmp.resize(out - tmp.data());
	return tmp;
}


PIByteArray PIByteArray::fromBase64(const PIByteArray &base64) {
	unsigned int buf = 0;
	int nbits = 0;
	PIByteArray tmp((base64.size() * 3) / 4);
	int offset = 0;
	for (int i = 0; i < base64.size(); ++i) {
		int ch = base64.at(i);
		int d;
		if (ch >= 'A' && ch <= 'Z') d = ch - 'A';
		else if (ch >= 'a' && ch <= 'z') d = ch - 'a' + 26;
		else if (ch >= '0' && ch <= '9') d = ch - '0' + 52;
		else if (ch == '+') d = 62;
		else if (ch == '/') d = 63;
		else d = -1;
		if (d != -1) {
			buf = (buf << 6) | d;
			nbits += 6;
			if (nbits >= 8) {
				nbits -= 8;
				tmp[offset++] = buf >> nbits;
				buf &= (1 << nbits) - 1;
			}
		}
	}
	tmp.resize(offset);
	return tmp;
}


PIByteArray & PIByteArray::compressRLE(uchar threshold) {
	PIByteArray t;
	uchar fb, clen, mlen = 255 - threshold;
	for (uint i = 0; i < size();) {
		fb = at(i);
		clen = 1;
		while (at(++i) == fb) {
			++clen;
			if (clen == mlen)
				break;
		}
		if (clen > 1) {
			t.push_back(threshold + clen);
			t.push_back(fb);
			continue;
		}
		if (fb >= threshold) {
			t.push_back(threshold + 1);
			t.push_back(fb);
		} else
			t.push_back(fb);
	}
	*this = t;
	return *this;
}


PIByteArray & PIByteArray::decompressRLE(uchar threshold) {
	PIByteArray t;
	uchar fb, clen;
	for (uint i = 0; i < size(); ++i) {
		fb = at(i);
		if (fb >= threshold) {
			clen = fb - threshold;
			fb = at(++i);
			for (uint j = 0; j < clen; ++j)
				t.push_back(fb);
			continue;
		} else
			t.push_back(fb);
	}
	*this = t;
	return *this;
}


uchar PIByteArray::checksumPlain8() const {
	uchar c = 0;
	int sz = size_s();
	for (int i = 0; i < sz; ++i)
		c += at(i);
	c = ~(c + 1);
	return c;
}


uint PIByteArray::checksumPlain32() const {
	uint c = 0;
	int sz = size_s();
	for (int i = 0; i < sz; ++i)
		c += at(i) * (i + 1);
	c = ~(c + 1);
	return c;
}


PIString PIByteArray::toString(int base) const {
	PIString ret;
	int sz = size_s();
	for (int i = 0; i < sz; ++i) {
		if (i > 0) ret += " ";
		if (base == 2) ret += "b";
		if (base == 8) ret += "0";
		if (base == 16) ret += "0x";
		ret += PIString::fromNumber(at(i), base);
	}
	return ret;
}


PIString PIByteArray::toHex() const {
	PIByteArray hex(size() * 2);
	uchar *hexData = hex.data();
	const uchar *d = data();
	for (int i = 0; i < size_s(); ++i) {
		int j = (d[i] >> 4) & 0xf;
		if (j <= 9) hexData[i*2] = (j + '0');
		else hexData[i*2] = (j + 'a' - 10);
		j = d[i] & 0xf;
		if (j <= 9) hexData[i*2+1] = (j + '0');
		else hexData[i*2+1] = (j + 'a' - 10);
	}
	return PIString(hex);
}


PIByteArray PIByteArray::fromString(PIString str) {
	PIByteArray ret;
	if (str.trim().isEmpty()) return ret;
	str.replaceAll("\n", " ").replaceAll("\t", " ").replaceAll("  ", " ");
	PIStringList bl(str.split(" "));
	bool ok(false);
	piForeachC (PIString & b, bl) {
		int bv = b.toInt(-1, &ok);
		if (ok) ret << uchar(bv);
	}
	return ret;
}


PIByteArray PIByteArray::fromHex(PIString str) {
	PIByteArray hexEncoded = str.toByteArray();
	PIByteArray res((hexEncoded.size() + 1)/ 2);
	uchar *result = res.data() + res.size();
	bool odd_digit = true;
	for (int i = hexEncoded.size() - 1; i >= 0; --i) {
		int ch = hexEncoded.at(i);
		int tmp;
		if (ch >= '0' && ch <= '9') tmp = ch - '0';
		else if (ch >= 'a' && ch <= 'f') tmp = ch - 'a' + 10;
		else if (ch >= 'A' && ch <= 'F') tmp = ch - 'A' + 10;
		else continue;
		if (odd_digit) {
			--result;
			*result = tmp;
			odd_digit = false;
		} else {
			*result |= tmp << 4;
			odd_digit = true;
		}
	}
	res.remove(0, result - res.data());
	return res;
}
