/*
* Copyright 2016 Nu-book Inc.
* Copyright 2016 ZXing authors
* Copyright 2017 Axel Waggershauser
*/
// SPDX-License-Identifier: Apache-2.0

#pragma once

#include "Range.h"
#include "ZXAlgorithms.h"

#include <algorithm>
#include <cassert>
#include <cstdint>
#include <stdexcept>
#include <type_traits>
#include <vector>

namespace ZXing {

class ByteArray;

/**
* A simple, fast array of bits.
*/
class BitArray
{
	std::vector<uint8_t> _bits;

	friend class BitMatrix;

	// Nothing wrong to support it, just to make it explicit, instead of by mistake.
	// Use copy() below.
	BitArray(const BitArray &) = default;

public:

	using Iterator = std::vector<uint8_t>::const_iterator;

	BitArray() = default;

	explicit BitArray(int size) : _bits(size, 0) {}

	BitArray(BitArray&& other) noexcept = default;
	BitArray& operator=(BitArray&& other) noexcept = default;
	BitArray& operator=(const BitArray &) = delete;

	BitArray copy() const { return *this; }

	int size() const noexcept { return Size(_bits); }

	int sizeInBytes() const noexcept { return (size() + 7) / 8; }

	bool get(int i) const { return _bits.at(i) != 0; }
	void set(int i, bool val) { _bits.at(i) = val; }

	// If you know exactly how may bits you are going to iterate
	// and that you access bit in sequence, iterator is faster than get().
	// However, be extremely careful since there is no check whatsoever.
	// (Performance is the reason for the iterator to exist in the first place.)
	Iterator iterAt(int i) const noexcept { return {_bits.cbegin() + i}; }
	Iterator begin() const noexcept { return _bits.cbegin(); }
	Iterator end() const noexcept { return _bits.cend(); }

	/**
	* Appends the least-significant bits, from value, in order from most-significant to
	* least-significant. For example, appending 6 bits from 0x000001E will append the bits
	* 0, 1, 1, 1, 1, 0 in that order.
	*
	* @param value {@code int} containing bits to append
	* @param numBits bits from value to append
	*/
	void appendBits(int value, int numBits)
	{
		assert(numBits >= 1 && numBits <= 32);

		for (; numBits; --numBits)
			_bits.push_back((value >> (numBits-1)) & 1);
	}

	void appendBits(uint32_t value, int numBits)
	{
		appendBits(static_cast<int>(value), numBits);
	}

	void appendBit(bool bit) { _bits.push_back(bit); }

	void appendBitArray(const BitArray& other) { _bits.insert(_bits.end(), other.begin(), other.end()); }

	/**
	* Reverses all bits in the array.
	*/
	void reverse() { std::reverse(_bits.begin(), _bits.end()); }

	void bitwiseXOR(const BitArray& other);

	/**
	* @param bitOffset first bit to extract
	* @param numBytes how many bytes to extract (-1 == until the end, padded with '0')
	* @return Bytes are written most-significant bit first.
	*/
	ByteArray toBytes(int bitOffset = 0, int numBytes = -1) const;

	using Range = ZXing::Range<Iterator>;
	Range range() const { return {begin(), end()}; }

	friend bool operator==(const BitArray& a, const BitArray& b) { return a._bits == b._bits; }
};

template <typename T = int, typename = std::enable_if_t<std::is_integral_v<T>>>
T ToInt(const BitArray& bits, int pos = 0, int count = 8 * sizeof(T))
{
	assert(0 <= count && count <= 8 * (int)sizeof(T));
	assert(0 <= pos && pos + count <= bits.size());

	count = std::min(count, bits.size());
	int res = 0;
	auto it = bits.iterAt(pos);
	for (int i = 0; i < count; ++i, ++it)
		AppendBit(res, *it);

	return res;
}

template <typename T = int, typename = std::enable_if_t<std::is_integral_v<T>>>
std::vector<T> ToInts(const BitArray& bits, int wordSize, int totalWords, int offset = 0)
{
	assert(totalWords >= bits.size() / wordSize);
	assert(wordSize <= 8 * (int)sizeof(T));

	std::vector<T> res(totalWords, 0);
	for (int i = offset; i < bits.size(); i += wordSize)
		res[(i - offset) / wordSize] = ToInt(bits, i, wordSize);

	return res;
}

class BitArrayView
{
	const BitArray& bits;
	BitArray::Iterator cur;

public:
	BitArrayView(const BitArray& bits) : bits(bits), cur(bits.begin()) {}

	BitArrayView& skipBits(int n)
	{
		if (cur + n > bits.end())
			throw std::out_of_range("BitArrayView::skipBits() out of range.");
		cur += n;
		return *this;
	}

	int peekBits(int n) const
	{
		assert(n <= 32);
		if (cur + n > bits.end())
			throw std::out_of_range("BitArrayView::peekBits() out of range.");
		int res = 0;
		for (auto i = cur; n > 0; --n, i++)
			AppendBit(res, *i);
		return res;
	}

	int readBits(int n)
	{
		int res = peekBits(n);
		cur += n;
		return res;
	}

	int size() const
	{
		return narrow_cast<int>(bits.end() - cur);
	}

	explicit operator bool() const { return size(); }
};

} // ZXing
