/*
	PIP - Platform Independent Primitives
	Binary markup serializator
	Ivan Pelipenko peri4ko@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 "pichunkstream.h"

/*! \class PIChunkStream
 * \brief Class for binary serialization
 * 
 * \section PIChunkStream_sec0 Synopsis
 * This class provides very handly mechanism to store and restore values to and from
 * \a PIByteArray. The main advantage of using this class is that your binary data
 * become independent from order and collection of your values.
 * 
 * \section PIChunkStream_sec1 Mechanism
 * %PIChunkStream works with items called "chunk". Chunk is an ID and any value that
 * can be stored and restored to \a PIByteArray with stream operators << and >>.
 * You can place chunks to stream and read chunks from stream.
 * 
 * To construct %PIChunkStream for writing data use any constructor. Empty constructor
 * creates internal empty buffer that can be accessed by function \a data().
 * Non-empty constructor works with given byte array.
 * 
 * To read chunks from byte array use function \a read() that returns ID of
 * next chunk. Then you can get value of this chunk with function \a getData(),
 * but you should definitely know type of this value. You can read from byte array
 * while \a atEnd() if false.
 * 
 * \section PIChunkStream_sec2 Examples
 *
 * Using simple operator and cascade serialization:
 *
 * Prepare your structs to work with %PIChunkStream:
 * \snippet pichunkstream.cpp struct
 * Old-style writing to %PIChunkStream:
 * \snippet pichunkstream.cpp write
 * Fastest reading from %PIChunkStream:
 * \snippet pichunkstream.cpp read
 *
 * And next code show how to serialize your struct with %PIChunkStream:
 * \snippet pichunkstream.cpp write_new
 *
 * ... and deserialize:
 * \snippet pichunkstream.cpp read_new
 */


void PIChunkStream::setSource(const PIByteArray & data) {
	data_ = const_cast<PIByteArray*>(&data);
	_init();
}


void PIChunkStream::setSource(PIByteArray * data) {
	data_ = (data ? data : &tmp_data);
	_init();
}


int PIChunkStream::read() {
	switch (version_) {
	case Version_1:
		(*data_) >> last_id >> last_data;
		break;
	case Version_2:
		last_id = readVInt(*data_);
		last_data.resize(readVInt(*data_));
		//piCout << last_id << last_data.size();
		(*data_) >> PIByteArray::RawData(last_data.data(), last_data.size_s());
		break;
	default: break;
	}
	return last_id;
}


int PIChunkStream::peekVInt(Version version_, PIByteArray * data_, int pos, PIByteArray & hdr, uint & ret) {
	switch (version_) {
	case Version_1:
		memcpy(&ret, data_->data(pos), 4);
		return 4;
	case Version_2: {
		hdr.resize(4);
		hdr.fill(uchar(0));
		memcpy(hdr.data(), data_->data(pos), piMini(4, data_->size_s() - pos));
		uchar hsz = 0;
		ret = readVInt(hdr, &hsz);
		return hsz;
		}
	default: break;
	}
	return 0;
}


void PIChunkStream::readAll() {
	data_map.clear();
	if (!data_) return;
	int pos = 0, sz = data_->size_s();
	uint csz = 0, cid = 0;
	PIByteArray hdr;
	while (pos < sz) {
		pos += peekVInt((Version)version_, data_, pos, hdr, cid);
		pos += peekVInt((Version)version_, data_, pos, hdr, csz);
		data_map[cid] = PIPair<int, int>(pos, csz);
		pos += csz;
	}
}


PIChunkStream::~PIChunkStream() {
}


bool PIChunkStream::extract(PIByteArray & data, bool read_all) {
	if (data.size_s() < 4) return false;
	data >> tmp_data;
	if (tmp_data.size_s() < 4) return false;
	data_ = &tmp_data;
	_init();
	if (read_all)
		readAll();
	return true;
}


void PIChunkStream::_init() {
	last_id = -1;
	last_data.clear();
	if (!data_->isEmpty()) {
		uchar v = data_->at(0);
		if ((v & 0x80) == 0x80) {
			v &= 0x7f;
			switch (v) {
			case 2:  version_ = (uchar)Version_2; data_->pop_front(); break;
			default: version_ = Version_1; break;
			}
		} else
			version_ = Version_1;
	}
}


uint PIChunkStream::readVInt(PIByteArray & s, uchar * bytes_cnt) {
	if (s.isEmpty()) return 0;
	uchar bytes[4]; s >> bytes[0];
	uchar abc = 0;
	for (abc = 0; abc < 3; ++abc) {
		uchar mask = (0x80 >> abc);
		if ((bytes[0] & mask) == mask) {
			bytes[0] &= (mask - 1);
			s >> bytes[abc + 1];
		} else break;
	}
	if (bytes_cnt) *bytes_cnt = (abc + 1);
	uint ret = 0;
	for (int i = 0; i <= abc; ++i) {
		ret += (bytes[i] << (8 * ((int)abc - i)));
	}
	return ret;
}


void PIChunkStream::writeVInt(PIByteArray & s, uint val) {
	if (val > 0xfffffff) return;
	if (val <= 0x7f) {
		s << uchar(val);
		return;
	}
	if (val <= 0x3fff) {
		s << uchar((val >> 8) | 0x80) << uchar(val & 0xff);
		return;
	}
	if (val <= 0x1fffff) {
		s << uchar((val >> 16) | 0xc0) << uchar((val >> 8) & 0xff) << uchar(val & 0xff);
		return;
	}
	s << uchar((val >> 24) | 0xe0) << uchar((val >> 16) & 0xff) << uchar((val >> 8) & 0xff) << uchar(val & 0xff);
}
