//! \~\file pibitarray.h
//! \~\ingroup Types
//! \~\brief
//! \~english Packed bit array
//! \~russian Упакованный массив битов
/*
    PIP - Platform Independent Primitives
    Packed bit array
    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/>.
*/

#ifndef PIBITARRAY_H
#define PIBITARRAY_H

#include "pivector.h"


//! \~\ingroup Types
//! \~\brief
//! \~english The %PIBitArray class provides a space-efficient array of bits.
//! \~russian Класс %PIBitArray представляет собой компактный массив битов.
class PIP_EXPORT PIBitArray {
	template<typename P>
	friend PIBinaryStream<P> & operator>>(PIBinaryStream<P> & s, PIBitArray & v);
	template<typename P>
	friend PIBinaryStream<P> & operator<<(PIBinaryStream<P> & s, const PIBitArray & v);

public:
	//! \~english Constructs a bit array with `size` bits.
	//! \~russian Создает битовый массив размером `size` бит.
	PIBitArray(const int & size = 0) { resize(size); }

	//! \~english Constructs an 8-bit array initialized from `val`.
	//! \~russian Создает 8-битный массив, инициализированный из `val`.
	PIBitArray(uchar val) {
		resize(sizeof(val) * 8);
		data_[0] = val;
	}

	//! \~english Constructs a 16-bit array initialized from `val`.
	//! \~russian Создает 16-битный массив, инициализированный из `val`.
	PIBitArray(ushort val) {
		resize(sizeof(val) * 8);
		memcpy(data(), &val, sizeof(val));
	}

	//! \~english Constructs a 32-bit array initialized from `val`.
	//! \~russian Создает 32-битный массив, инициализированный из `val`.
	PIBitArray(uint val) {
		resize(sizeof(val) * 8);
		memcpy(data(), &val, sizeof(val));
	}

	//! \~english Constructs a 64-bit array initialized from `val`.
	//! \~russian Создает 64-битный массив, инициализированный из `val`.
	PIBitArray(ullong val) {
		resize(sizeof(val) * 8);
		memcpy(data(), &val, sizeof(val));
	}

	//! \~english Constructs a bit array from `bytes` bytes of raw data.
	//! \~russian Создает битовый массив из `bytes` байт исходных данных.
	PIBitArray(const uchar * data, uint bytes) {
		resize(bytes * 8);
		memcpy(this->data(), data, bytes);
	}


	//! \~english Returns a size in bits.
	//! \~russian Возвращает размер в битах.
	uint bitSize() const { return size_; }

	//! \~english Returns a size in bytes.
	//! \~russian Возвращает размер в байтах.
	uint byteSize() const { return bytesInBits(size_); }

	//! \~english Resize this array to "size" bits.
	//! \~russian Изменяет размер массива до "size" битов.
	PIBitArray & resize(const uint & size) {
		size_ = size;
		data_.resize(bytesInBits(size_));
		return *this;
	}


	//! \~english Set bit with index "index" to 0.
	//! \~russian Устанавливает бит с индексом "index" в 0.
	PIBitArray & clearBit(const uint & index) {
		data_[index / 8] &= ~(1 << (index % 8));
		return *this;
	}

	//! \~english Set bit with index "index" to 1.
	//! \~russian Устанавливает бит с индексом "index" в 1.
	PIBitArray & setBit(const uint & index) {
		data_[index / 8] |= (1 << (index % 8));
		return *this;
	}

	//! \~english Set bit with index "index" to "value".
	//! \~russian Устанавливает бит с индексом "index" в "value".
	PIBitArray & writeBit(const uint & index, const bool & value) {
		if (value)
			setBit(index);
		else
			clearBit(index);
		return *this;
	}

	//! \~english Set bit with index "index" to "value".
	//! \~russian Устанавливает бит с индексом "index" в "value".
	PIBitArray & writeBit(const uint & index, const uchar & value) { return writeBit(index, value > 0); }


	//! \~english Add bit with value "value" to the end of array.
	//! \~russian Добавляет бит со значением "value" в конец массива.
	PIBitArray & push_back(const bool & value) {
		resize(size_ + 1);
		writeBit(size_ - 1, value);
		return *this;
	}

	//! \~english Add bit with value "value" to the end of array.
	//! \~russian Добавляет бит со значением "value" в конец массива.
	PIBitArray & push_back(const uchar & value) { return push_back(value > 0); }

	//! \~english Insert bit with value "value" after "index" position.
	//! \~russian Вставляет бит со значением "value" после положения "index".
	PIBitArray & insert(const uint & index, const bool & value) {
		resize(size_ + 1);
		uint fi = byteSize() - 1, si = index / 8, ti = index % 8;
		uchar c = data_[si];
		for (uint i = fi; i > si; --i) {
			data_[i] <<= 1;
			if ((0x80 & data_[i - 1]) == 0x80)
				data_[i] |= 1;
			else
				data_[i] &= 0xFE;
		}
		data_[si] &= (0xFF >> (7 - ti));
		data_[si] |= ((c << 1) & (0xFF << (ti)));
		if (value)
			data_[si] |= (1 << ti);
		else
			data_[si] &= ~(1 << ti);
		return *this;
	}

	//! \~english Insert bit with value "value" after "index" position.
	//! \~russian Вставляет бит со значением "value" после положения "index".
	PIBitArray & insert(const uint & index, const uchar & value) { return insert(index, value > 0); }

	//! \~english Add bit with value "value" to the beginning of array.
	//! \~russian Добавляет бит со значением "value" в начало массива.
	PIBitArray & push_front(const bool & value) { return insert(0, value); }

	//! \~english Add bit with value "value" to the beginning of array.
	//! \~russian Добавляет бит со значением "value" в начало массива.
	PIBitArray & push_front(const uchar & value) { return push_front(value > 0); }

	//! \~english Remove one bit from the end of array.
	//! \~russian Удаляет один бит с конца массива.
	PIBitArray & pop_back() {
		if (size_ == 0) return *this;
		return resize(size_ - 1);
	}

	//! \~english Remove one bit from the beginning of array.
	//! \~russian Удаляет один бит с начала массива.
	PIBitArray & pop_front() {
		if (size_ == 0) return *this;
		uint fi = byteSize() - 1;
		for (uint i = 0; i < fi; ++i) {
			data_[i] >>= 1;
			if ((1 & data_[i + 1]) == 1)
				data_[i] |= 0x80;
			else
				data_[i] &= 0x7F;
		}
		data_[fi] >>= 1;
		resize(size_ - 1);
		return *this;
	}

	//! \~english Add bits "ba" to the end of array.
	//! \~russian Добавляет биты "ba" в конец массива.
	PIBitArray & append(const PIBitArray & ba) {
		for (uint i = 0; i < ba.bitSize(); ++i)
			push_back(ba[i]);
		return *this;
	}


	//! \~english Returns pointer to bytes data of array.
	//! \~russian Возвращает указатель на байтовое содержимое массива.
	uchar * data() { return data_.data(); }

	//! \~english Returns data of array as \b uchar.
	//! \~russian Возвращает байтовое содержимое массива как \b uchar.
	uchar toUChar() {
		if (size_ == 0) return 0;
		return data_[0];
	}

	//! \~english Returns data of array as \b ushort.
	//! \~russian Возвращает байтовое содержимое массива как \b ushort.
	ushort toUShort() {
		ushort t = 0;
		memcpy(&t, data(), piMin<uint>(byteSize(), sizeof(t)));
		return t;
	}

	//! \~english Returns data of array as \b uint.
	//! \~russian Возвращает байтовое содержимое массива как \b uint.
	uint toUInt() {
		uint t = 0;
		memcpy(&t, data(), piMin<uint>(byteSize(), sizeof(t)));
		return t;
	}

	//! \~english Returns data of array as \b ullong.
	//! \~russian Возвращает байтовое содержимое массива как \b ullong.
	ullong toULLong() {
		ullong t = 0;
		memcpy(&t, data(), piMin<uint>(byteSize(), sizeof(t)));
		return t;
	}


	//! \~english Returns bit value at index "index".
	//! \~russian Возвращает значение бита с индексом "index".
	bool at(const uint & index) const { return (1 & (data_[index / 8] >> (index % 8))) == 1 ? true : false; }

	//! \~english Returns bit value at index "index".
	//! \~russian Возвращает значение бита с индексом "index".
	bool operator[](const uint & index) const { return at(index); }

	//! \~english Similar to \a append().
	//! \~russian Аналог \a append().
	void operator+=(const PIBitArray & ba) { append(ba); }

	//! \~english Returns if two bit arrays are similar.
	//! \~russian Возвращает равны ли для битовых массива.
	bool operator==(const PIBitArray & ba) const {
		if (bitSize() != ba.bitSize()) return false;
		for (uint i = 0; i < bitSize(); ++i)
			if (at(i) != ba[i]) return false;
		return true;
	}

	//! \~english Returns if two bit arrays are not similar.
	//! \~russian Возвращает не равны ли для битовых массива.
	bool operator!=(const PIBitArray & ba) const { return !(*this == ba); }

	//! \~english Resize and set values from "val".
	//! \~russian Изменяет размер и устанавливает значения из "val".
	void operator=(const uchar & val) {
		resize(sizeof(val) * 8);
		data_[0] = val;
	}

	//! \~english Resize and set values from "val".
	//! \~russian Изменяет размер и устанавливает значения из "val".
	void operator=(const ushort & val) {
		resize(sizeof(val) * 8);
		memcpy(data(), &val, sizeof(val));
	}

	//! \~english Resize and set values from "val".
	//! \~russian Изменяет размер и устанавливает значения из "val".
	void operator=(const uint & val) {
		resize(sizeof(val) * 8);
		memcpy(data(), &val, sizeof(val));
	}

	//! \~english Resize and set values from "val".
	//! \~russian Изменяет размер и устанавливает значения из "val".
	void operator=(const ullong & val) {
		resize(sizeof(val) * 8);
		memcpy(data(), &val, sizeof(val));
	}

private:
	static uint bytesInBits(const uint & bits) { return (bits + 7) / 8; }

	PIVector<uchar> data_;
	uint size_;
};

#ifdef PIP_STD_IOSTREAM
// std::ostream & operator <<(std::ostream & s, const PIBitArray & ba);
#endif

//! \relatesalso PICout
//! \~english Output operator to \a PICout
//! \~russian Оператор вывода в \a PICout
PIP_EXPORT PICout operator<<(PICout s, const PIBitArray & ba);

#endif // PIBITARRAY_H
