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

#include "ODDataBarExpandedBitDecoder.h"

#include "BitArray.h"
#include "Error.h"
#include "GTIN.h"

#include <cmath>

namespace ZXing::OneD::DataBar {

constexpr char GS = 29; // FNC1

static std::string DecodeGeneralPurposeBits(BitArrayView& bits)
{
	enum State { NUMERIC, ALPHA, ISO_IEC_646 };
	State state = NUMERIC;
	std::string res;

	auto decode5Bits = [](State& state, std::string& res, BitArrayView& bits) {
		int v = bits.readBits(5);
		if (v == 4) {
			state = state == ALPHA ? ISO_IEC_646 : ALPHA;
		} else if (v == 15) { // FNC1 + latch to Numeric
			res.push_back(GS);
			state = NUMERIC;
			// Allow for some generators incorrectly placing a numeric latch "000" after an FNC1
			if (bits.size() >= 7 && bits.peakBits(7) < 8)
				bits.skipBits(3);
		} else {
			res.push_back(v + 43);
		}
	};

	auto isPadding = [](State state, BitArrayView& bits) {
		bool res = (state == NUMERIC) ? bits.size() < 4
									  : bits.size() < 5 && (0b00100 >> (5 - bits.size()) == bits.peakBits(bits.size()));
		if (res)
			bits.skipBits(bits.size());
		return res;
	};

	while(bits.size() >= 3) {
		switch(state) {
		case NUMERIC:
			if (isPadding(state, bits))
				break;
			if (bits.size() < 7) {
				int v = bits.readBits(4);
				if (v > 0)
					res.push_back(ToDigit(v - 1));
			} else if (bits.peakBits(4) == 0) {
				bits.skipBits(4);
				state = ALPHA;
			} else {
				int v = bits.readBits(7);
				for (int digit : {(v - 8) / 11, (v - 8) % 11})
					res.push_back(digit == 10 ? GS : ToDigit(digit));
			}
			break;
		case ALPHA:
			if (isPadding(state, bits))
				break;
			if (bits.peakBits(1) == 1) {
				constexpr char const* lut58to62 = R"(*,-./)";
				int v = bits.readBits(6);
				if (v < 58)
					res.push_back(v + 33);
				else if (v < 63)
					res.push_back(lut58to62[v - 58]);
				else
					throw FormatError();
			} else if (bits.peakBits(3) == 0) {
				bits.skipBits(3);
				state = NUMERIC;
			} else {
				decode5Bits(state, res, bits);
			}
			break;
		case ISO_IEC_646:
			if (isPadding(state, bits))
				break;
			if (bits.peakBits(3) == 0) {
				bits.skipBits(3);
				state = NUMERIC;
			} else {
				int v = bits.peakBits(5);
				if (v < 16) {
					decode5Bits(state, res, bits);
				} else if (v < 29) {
					v = bits.readBits(7);
					res.push_back(v < 90 ? v + 1 : v + 7);
				} else {
					constexpr char const* lut232to252 = R"(!"%&'()*+,-./:;<=>?_ )";
					v = bits.readBits(8);
					if (v < 232 || 252 < v)
						throw FormatError();
					res.push_back(lut232to252[v - 232]);
				}
			}
			break;
		}
	}

	// in NUMERIC encodation there might be a trailing FNC1 that needs to be ignored
	if (res.size() && res.back() == GS)
		res.pop_back();

	return res;
}

static std::string DecodeCompressedGTIN(std::string prefix, BitArrayView& bits)
{
	for (int i = 0; i < 4; ++i)
		prefix.append(ToString(bits.readBits(10), 3));

	prefix.push_back(GTIN::ComputeCheckDigit(prefix.substr(2)));

	return prefix;
}

static std::string DecodeAI01GTIN(BitArrayView& bits)
{
	return DecodeCompressedGTIN("019", bits);
}

static std::string DecodeAI01AndOtherAIs(BitArrayView& bits)
{
	bits.skipBits(2); // Variable length symbol bit field

	auto header = DecodeCompressedGTIN("01" + std::to_string(bits.readBits(4)), bits);
	auto trailer = DecodeGeneralPurposeBits(bits);

	return header + trailer;
}

static std::string DecodeAnyAI(BitArrayView& bits)
{
	bits.skipBits(2); // Variable length symbol bit field

	return DecodeGeneralPurposeBits(bits);
}

static std::string DecodeAI013103(BitArrayView& bits)
{
	std::string buffer = DecodeAI01GTIN(bits);
	buffer.append("3103");
	buffer.append(ToString(bits.readBits(15), 6));

	return buffer;
}

static std::string DecodeAI01320x(BitArrayView& bits)
{
	std::string buffer = DecodeAI01GTIN(bits);
	int weight = bits.readBits(15);
	buffer.append(weight < 10000 ? "3202" : "3203");
	buffer.append(ToString(weight < 10000 ? weight : weight - 10000, 6));

	return buffer;
}

static std::string DecodeAI0139yx(BitArrayView& bits, char y)
{
	bits.skipBits(2); // Variable length symbol bit field

	std::string buffer = DecodeAI01GTIN(bits);
	buffer.append("39");
	buffer.push_back(y);
	buffer.append(std::to_string(bits.readBits(2)));

	if (y == '3')
		buffer.append(ToString(bits.readBits(10), 3));

	auto trailer = DecodeGeneralPurposeBits(bits);
	if (trailer.empty())
		return {};

	return buffer + trailer;
}

static std::string DecodeAI013x0x1x(BitArrayView& bits, const char* aiPrefix, const char* dateCode)
{
	std::string buffer = DecodeAI01GTIN(bits);
	buffer.append(aiPrefix);

	int weight = bits.readBits(20);
	buffer.append(std::to_string(weight / 100000));
	buffer.append(ToString(weight % 100000, 6));

	int date = bits.readBits(16);
	if (date != 38400) {
		buffer.append(dateCode);

		int day = date % 32;
		date /= 32;
		int month = date % 12 + 1;
		date /= 12;
		int year = date;

		buffer.append(ToString(year, 2));
		buffer.append(ToString(month, 2));
		buffer.append(ToString(day, 2));
	}

	return buffer;
}

std::string DecodeExpandedBits(const BitArray& _bits)
{
	auto bits = BitArrayView(_bits);
	bits.readBits(1); // skip linkage bit

	if (bits.peakBits(1) == 1)
		return DecodeAI01AndOtherAIs(bits.skipBits(1));

	if (bits.peakBits(2) == 0)
		return DecodeAnyAI(bits.skipBits(2));

	switch (bits.peakBits(4)) {
	case 4: return DecodeAI013103(bits.skipBits(4));
	case 5: return DecodeAI01320x(bits.skipBits(4));
	}

	switch (bits.peakBits(5)) {
	case 12: return DecodeAI0139yx(bits.skipBits(5), '2');
	case 13: return DecodeAI0139yx(bits.skipBits(5), '3');
	}

	switch (bits.readBits(7)) {
	case 56: return DecodeAI013x0x1x(bits, "310", "11");
	case 57: return DecodeAI013x0x1x(bits, "320", "11");
	case 58: return DecodeAI013x0x1x(bits, "310", "13");
	case 59: return DecodeAI013x0x1x(bits, "320", "13");
	case 60: return DecodeAI013x0x1x(bits, "310", "15");
	case 61: return DecodeAI013x0x1x(bits, "320", "15");
	case 62: return DecodeAI013x0x1x(bits, "310", "17");
	case 63: return DecodeAI013x0x1x(bits, "320", "17");
	}

	return {};
}

} // namespace ZXing::OneD::DataBar
