/*
    PIP - Platform Independent Primitives
    Simple packet wrap aroud any PIIODevice
    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/>.
*/

#ifdef __GNUC__
#  pragma GCC diagnostic push
#  pragma GCC diagnostic ignored "-Wnonnull"
#endif
#include "pistreampacker.h"

#include "piiodevice.h"
#include "pitranslator.h"
#ifdef __GNUC__
#  pragma GCC diagnostic pop
#endif

/** \class PIStreamPacker
 * \brief Simple packet wrap aroud any PIIODevice
 *
 * \section PIStreamPacker_synopsis Synopsis
 * %PIStreamPacker provides simple pack/unpack logic for any data packets.
 *
 * When you call \a send() function data splited into several
 * parts, \a packetSign() prepended to first part and \a sendRequest()
 * event raised several times.
 *
 * When your device receive some data, call \a received() function.
 * \a packetReceiveEvent() event will be raised when packet will be
 * collected.
 *
 * Use \a assignDevice() to connect device to this %PIStreamPacker.
 *
 */


PIStreamPacker::PIStreamPacker(PIIODevice * dev): PIObject() {
	packet_size = -1;
	if (dev) assignDevice(dev);
}

void PIStreamPacker::clear() {
	packet.clear();
	packet_size = -1;
	stream.clear();
}


void PIStreamPacker::send(const PIByteArray & data) {
	if (data.isEmpty()) return;
	PIByteArray cd = cryptData(data);
	// piCout << "crypt" << data.size() << "->" << cd.size() << key().size();
	PIByteArray hdr, part;
	hdr << packet_sign;
	if (crypt_size) {
		PIByteArray crsz;
		crsz << int(cd.size_s());
		hdr.append(cryptData(crsz));
	} else
		hdr << int(cd.size_s());
	cd.insert(0, hdr);
	if (max_packet_size <= 0) return;
	int pcnt = (cd.size_s() - 1) / max_packet_size + 1, pst = 0;
	for (int i = 0; i < pcnt; ++i) {
		if (i == pcnt - 1)
			part = PIByteArray(cd.data(pst), cd.size_s() - pst);
		else
			part = PIByteArray(cd.data(pst), max_packet_size);
		// piCout << "send" << part.size();
		sendRequest(part);
		pst += max_packet_size;
	}
}


void PIStreamPacker::received(const uchar * readed, ssize_t size) {
	if (size <= 0) return;
	received(PIByteArray(readed, size));
}


void PIStreamPacker::received(const PIByteArray & data) {
	stream.append(data);
	// piCout << "rec" << data.size();
	while (!stream.isEmpty()) {
		int hdr_size = sizeof(packet_sign) + sizeCryptedSize();
		if (packet_size < 0) {
			if (stream.size_s() < hdr_size) return;
			ushort sign(0);
			memcpy(&sign, stream.data(), 2);
			if (sign != packet_sign) {
				if (aggressive_optimization)
					stream.clear();
				else
					stream.pop_front();
				continue;
			}
			int sz = -1;
			if (crypt_size) {
				PIByteArray crsz(sizeCryptedSize());
				memcpy(crsz.data(), stream.data(2), crsz.size());
				crsz = decryptData(crsz);
				if (crsz.size() < sizeof(sz)) {
					if (aggressive_optimization)
						stream.clear();
					else
						stream.pop_front();
					continue;
				}
				crsz >> sz;
			} else {
				memcpy(&sz, stream.data(2), sizeCryptedSize());
			}
			if (sz < 0) {
				if (aggressive_optimization)
					stream.clear();
				else
					stream.pop_front();
				continue;
			}
			stream.remove(0, hdr_size);
			packet.clear();
			packet_size = sz;
			if (packet_size == 0)
				packet_size = -1;
			else
				startPacketReceive(packet_size);
			continue;
		} else {
			int ps = piMini(stream.size_s(), packet_size - packet.size_s());
			packet.append(stream.data(), ps);
			stream.remove(0, ps);
			if (packet.size_s() == packet_size) {
				PIByteArray cd = decryptData(packet);
				// piCout << "decrypt" << packet.size() << "->" << cd.size() << key().size();
				if (!cd.isEmpty()) {
					endPacketReceive();
					packetReceived(cd);
					packetReceiveEvent(cd);
				}
				packet.clear();
				packet_size = -1;
			}
		}
	}
}


void PIStreamPacker::assignDevice(PIIODevice * dev) {
	if (!dev) {
		piCoutObj << "Error! device is NULL";
		return;
	}
	if (!dev->infoFlags()[PIIODevice::Reliable]) {
		piCoutObj << "Warning! Not recommended to use with non-reliable device"_tr("PIStreamPacker") << dev;
	}
	CONNECT2(void, const uchar *, ssize_t, dev, threadedReadEvent, this, received);
	CONNECT1(void, PIByteArray, this, sendRequest, dev, write);
}

uint PIStreamPacker::sizeCryptedSize() {
	return sizeof(int) + (crypt_size ? cryptSizeAddition() : 0);
}
