/*
    PIP - Platform Independent Primitives
    Byte array
    Ivan Pelipenko peri4ko@yandex.ru, Andrey Bychkov work.a.b@yandex.ru

    This program 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.

    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 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 "pibytearray.h"

#include "picrc.h"
#include "pistringlist.h"

#include <iostream>

//! \class PIByteArray pibytearray.h
//! \~\details
//! \~english
//! %PIByteArray used to store raw bytes.
//! It can be constructed from any data and size.
//! You can use %PIByteArray as binary stream
//! to serialize/deserialize any objects and data.
//! See details \ref iostream.
//! This class use PIDeque<uchar> and provide some handle function
//! to manipulate it.
//! \~russian
//! %PIByteArray используется для хранения байтов.
//! Он может быть сконструирован из любых даных.
//! Можно использовать %PIByteArray как потоковый объект
//! для сериализации/десериализации любых типов и данных.
//! Подробнее \ref iostream.
//! Этот класс использует PIDeque<uchar> и предоставляет набор
//! удобных методов для работы с байтами.
//!
//! \~english \section PIByteArray_sec0 Usage
//! \~russian \section PIByteArray_sec0 Использование
//! \~english
//! %PIByteArray subclass PIBinaryStream and can be used to store custom data and manipulate it.
//! Store operators places data at the end of array, restore operators takes data from the beginning
//! of array.
//! In addition there are Hex and Base64 convertions.
//! \~russian
//! %PIByteArray наследован от PIBinaryStream и может быть использован для сохранения любых данных и работы с ними.
//! Операторы сохранения добавляют данные в конец массива, а операторы извлечения
//! берут данные из его начала.
//! Также есть методы для преобразования в Hex и Base64.
//!
//! \~english \section PIByteArray_sec1 Attention
//! \~russian \section PIByteArray_sec1 Внимание
//! \~english
//! 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().
//! \~russian
//! Потоковый оператор для типа %PIByteArray сохраняет его как контейнер,
//! а не просто добавляет его содержимое в конец. Этот оператор полезен для управляемой
//! упаковки произвольных данных в виде %PIByteArray.
//! Для добавления содержимого одного байтового массива к другому используется
//! метод \a append().
//! \~\snippet pibytearray.cpp 3
//!


static const uchar base64Table[64]     = {0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, 0x50,
                                          0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66,
                                          0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76,
                                          0x77, 0x78, 0x79, 0x7a, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x2b, 0x2f};

static const uchar base64InvTable[256] = {
	0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,
	0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x3E,
	0x0,  0x0,  0x0,  0x3F, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,
	0x1,  0x2,  0x3,  0x4,  0x5,  0x6,  0x7,  0x8,  0x9,  0xA,  0xB,  0xC,  0xD,  0xE,  0xF,  0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16,
	0x17, 0x18, 0x19, 0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26,
	0x27, 0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F, 0x30, 0x31, 0x32, 0x33, 0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,
	0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,
	0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,
	0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,
	0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,
	0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,
	0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0};


// clang-format off
struct base64HelpStruct {
	base64HelpStruct() {v = 0;}
	inline void setBytes(const uchar * r, int size = 3) {
		v = 0;
		switch (size) {
		case 3: v |= r[2];
		case 2: v |= r[1] << 8;
		case 1: v |= r[0] << 16;
		}
	}
	inline void getBytes(uchar * r) {
		r[0] = (v >> 16) & 0xFF;
		r[1] = (v >> 8)  & 0xFF;
		r[2] =  v        & 0xFF;
	}
	inline void setAscii(const uchar * r, int size = 4) {
		v = 0;
		switch (size) {
		case 4: v |= (base64InvTable[r[3]] & 0x3F);
		case 3: v |= (base64InvTable[r[2]] & 0x3F) << 6;
		case 2: v |= (base64InvTable[r[1]] & 0x3F) << 12;
		case 1: v |= (base64InvTable[r[0]] & 0x3F) << 18;
		}
	}
	inline void getAscii(uchar * r) {
		r[0] = base64Table[(v >> 18) & 0x3F];
		r[1] = base64Table[(v >> 12) & 0x3F];
		r[2] = base64Table[(v >> 6)  & 0x3F];
		r[3] = base64Table[ v        & 0x3F];
	}
	uint v;
};
// clang-format on


bool PIByteArray::startsWith(const PIByteArray & o) const {
	if (o.isEmpty()) return false;
	if (size() < o.size()) return false;
	return piCompareBinary(data(), o.data(), o.size());
}


bool PIByteArray::endsWith(const PIByteArray & o) const {
	if (o.isEmpty()) return false;
	if (size() < o.size()) return false;
	return piCompareBinary(data(size() - o.size()), o.data(), o.size());
}


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


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


PIByteArray PIByteArray::toBase64() const {
	if (isEmpty()) return PIByteArray();
	base64HelpStruct hs;
	PIByteArray ret;
	const int sz = (size_s() / 3) * 3;
	int ri       = -1;
	uchar t[4];
	ret.resize(((size_s() - 1) / 3 + 1) * 4);
	for (int i = 0; i < sz; i += 3) {
		hs.setBytes(data(i));
		hs.getAscii(t);
		ret[++ri] = (t[0]);
		ret[++ri] = (t[1]);
		ret[++ri] = (t[2]);
		ret[++ri] = (t[3]);
	}
	const int der = size_s() % 3;
	switch (der) {
	case 1:
		hs.setBytes(data(sz), 1);
		hs.getAscii(t);
		ret[++ri] = (t[0]);
		ret[++ri] = (t[1]);
		ret[++ri] = ('=');
		ret[++ri] = ('=');
		break;
	case 2:
		hs.setBytes(data(sz), 2);
		hs.getAscii(t);
		ret[++ri] = (t[0]);
		ret[++ri] = (t[1]);
		ret[++ri] = (t[2]);
		ret[++ri] = ('=');
		break;
	default: break;
	}
	return ret;
}


PIByteArray PIByteArray::fromBase64(const PIByteArray & base64) {
	if (base64.isEmpty()) return PIByteArray();
	base64HelpStruct hs;
	PIByteArray ret;
	const int sz = base64.size_s();
	if (sz < 4) return PIByteArray();
	int ind = -1;
	uchar t[4];
	ret.resize(sz / 4 * 3);
	for (int i = 0; i < sz / 4 * 4; i += 4) {
		hs.setAscii(base64.data(i));
		hs.getBytes(t);
		ret[++ind] = (t[0]);
		ret[++ind] = (t[1]);
		ret[++ind] = (t[2]);
	}
	if (base64.back() == '=') ret.pop_back();
	if (sz > 1)
		if (base64[sz - 2] == '=') ret.pop_back();
	return ret;
}


PIByteArray PIByteArray::fromBase64(const PIString & base64) {
	return fromBase64(base64.toByteArray());
}


PIByteArray & PIByteArray::compressRLE(uchar threshold) {
	PIByteArray t;
	uchar fb;
	uchar clen;
	const uchar 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;
	uchar 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;
}


//! \~\details
//! \~english
//! This is simple sum of all bytes, if "inverse" then add 1 and inverse.
//! Pseudocode:
//! \~russian
//! Это простая сумма всех байтов, если "inverse", то ещё добавляется 1 и инвертируется результат.
//! Псевдокод:
//! \~\code
//! for (i)
//!		sum += at(i);
//! if (inverse) return ~(sum + 1);
//! else return sum;
//! \endcode
uchar PIByteArray::checksumPlain8(bool inverse) const {
	uchar c      = 0;
	const int sz = size_s();
	for (int i = 0; i < sz; ++i)
		c += at(i);
	if (inverse) c = ~(c + 1);
	return c;
}


uchar PIByteArray::checksumCRC8() const {
	return standardCRC_8().calculate(data(), size());
}

ushort PIByteArray::checksumCRC16() const {
	return standardCRC_16().calculate(data(), size());
}

uint PIByteArray::checksumCRC32() const {
	return standardCRC_32().calculate(data(), size());
}


//! \~\details
//! \~english
//! This is sum of all bytes multiplied by index+1, if inverse then add 1 and inverse.
//! Pseudocode:
//! \~russian
//! Это простая сумма всех байтов, умноженных на индекс+1, если "inverse", то ещё добавляется 1 и инвертируется результат.
//! Псевдокод:
//! \~\code
//! for (i)
//!		sum += at(i) * (i + 1);
//! if (inverse) return ~(sum + 1);
//! else return sum;
//! \endcode
uint PIByteArray::checksumPlain32(bool inverse) const {
	uint c       = 0;
	const int sz = size_s();
	for (int i = 0; i < sz; ++i)
		c += at(i) * (i + 1);
	if (inverse) c = ~(c + 1);
	return c;
}


uint PIByteArray::hash() const {
	return piHashData(data(), size_s());
}


PIString PIByteArray::toString(int base) const {
	PIString ret;
	const 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::fromAscii(hex);
}


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


PIByteArray PIByteArray::fromHex(PIString str) {
	const 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;
}


PIByteArray PIByteArray::fromAscii(const char * str) {
	PIByteArray ret;
	int ind = 0;
	while (str[ind] != 0) {
		ret.append(str[ind]);
		++ind;
	}
	return ret;
}


PICout operator<<(PICout s, const PIByteArray & ba) {
	s.space();
	s.saveAndSetControls(0);
	s << "{";
	for (uint i = 0; i < ba.size(); ++i) {
		s << ba[i];
		if (i < ba.size() - 1) s << ", ";
	}
	s << "}";
	s.restoreControls();
	return s;
}


#ifdef PIP_STD_IOSTREAM
std::ostream & operator<<(std::ostream & s, const PIByteArray & ba) {
	s << "{";
	for (uint i = 0; i < ba.size(); ++i) {
		s << ba[i];
		if (i < ba.size() - 1) s << ", ";
	}
	s << "}";
	return s;
}
#endif
