/*
* Copyright 2016 Huy Cuong Nguyen
* Copyright 2016 ZXing authors
*/
// SPDX-License-Identifier: Apache-2.0

#include "DMHighLevelEncoder.h"

#include "ByteArray.h"
#include "CharacterSet.h"
#include "DMEncoderContext.h"
#include "TextEncoder.h"
#include "ZXAlgorithms.h"

#include <algorithm>
#include <array>
#include <cmath>
#include <cstddef>
#include <cstdint>
#include <functional>
#include <limits>
#include <numeric>
#include <stdexcept>
#include <string>

namespace ZXing::DataMatrix {

static const uint8_t PAD = 129;
static const uint8_t UPPER_SHIFT = 235;
static const uint8_t MACRO_05 = 236;
static const uint8_t MACRO_06 = 237;
static const uint8_t C40_UNLATCH = 254;
static const uint8_t X12_UNLATCH = 254;

static const std::wstring MACRO_05_HEADER = L"[)>\x1E""05\x1D";
static const std::wstring MACRO_06_HEADER = L"[)>\x1E""06\x1D";
static const std::wstring MACRO_TRAILER = L"\x1E\x04";

enum
{
	ASCII_ENCODATION,
	C40_ENCODATION,
	TEXT_ENCODATION,
	X12_ENCODATION,
	EDIFACT_ENCODATION,
	BASE256_ENCODATION,
};

static const uint8_t LATCHES[] = {
	0,	// ASCII mode, no latch needed
	230, // LATCH_TO_C40
	239, // LATCH_TO_TEXT
	238, // LATCH_TO_ANSIX12
	240, // LATCH_TO_EDIFACT
	231, // LATCH_TO_BASE256,
};

static bool IsDigit(int ch)
{
	return ch >= '0' && ch <= '9';
}

static bool IsExtendedASCII(int ch)
{
	return ch >= 128 && ch <= 255;
}

static bool IsNativeC40(int ch)
{
	return (ch == ' ') || (ch >= '0' && ch <= '9') || (ch >= 'A' && ch <= 'Z');
}

static bool IsNativeText(int ch)
{
	return (ch == ' ') || (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'z');
}

static bool IsX12TermSep(int ch)
{
	return (ch == '\r') //CR
		|| (ch == '*')
		|| (ch == '>');
}

static bool IsNativeX12(int ch)
{
	return IsX12TermSep(ch) || (ch == ' ') || (ch >= '0' && ch <= '9') || (ch >= 'A' && ch <= 'Z');
}

static bool IsNativeEDIFACT(int ch)
{
	return ch >= ' ' && ch <= '^';
}

static bool IsSpecialB256(int /*ch*/)
{
	return false; //TODO NOT IMPLEMENTED YET!!!
}


/*
* Converts the message to a byte array using the default encoding (cp437) as defined by the
* specification
*
* @param msg the message
* @return the byte array of the message
*/

/*
public static byte[] getBytesForMessage(String msg) {
return msg.getBytes(Charset.forName("cp437")); //See 4.4.3 and annex B of ISO/IEC 15438:2001(E)
}
*/

static uint8_t Randomize253State(uint8_t ch, int codewordPosition)
{
	int pseudoRandom = ((149 * codewordPosition) % 253) + 1;
	int tempVariable = ch + pseudoRandom;
	return narrow_cast<uint8_t>(tempVariable <= 254 ? tempVariable : (tempVariable - 254));
}

static int FindMinimums(const std::array<int, 6>& intCharCounts, int min, std::array<int, 6>& mins)
{
	mins.fill(0);
	for (int i = 0; i < 6; i++) {
		int current = intCharCounts[i];
		if (min > current) {
			min = current;
			mins.fill(0);
		}
		if (min == current) {
			mins[i]++;
		}
	}
	return min;
}

static int LookAheadTest(const std::string& msg, size_t startpos, int currentMode)
{
	if (startpos >= msg.length()) {
		return currentMode;
	}
	std::array<float, 6> charCounts;
	//step J
	if (currentMode == ASCII_ENCODATION) {
		charCounts = { 0, 1, 1, 1, 1, 1.25f };
	}
	else {
		charCounts = { 1, 2, 2, 2, 2, 2.25f };
		charCounts[currentMode] = 0;
	}

	std::array<int, 6> mins;
	std::array<int, 6> intCharCounts;
	int charsProcessed = 0;
	while (true) {
		//step K
		if ((startpos + charsProcessed) == msg.length()) {
			int min = std::numeric_limits<int>::max();
			std::transform(charCounts.begin(), charCounts.end(), intCharCounts.begin(),
						   [](float x) { return static_cast<int>(std::ceil(x)); });
			min = FindMinimums(intCharCounts, min, mins);
			int minCount = Reduce(mins);

			if (intCharCounts[ASCII_ENCODATION] == min) {
				return ASCII_ENCODATION;
			}
			if (minCount == 1 && mins[BASE256_ENCODATION] > 0) {
				return BASE256_ENCODATION;
			}
			if (minCount == 1 && mins[EDIFACT_ENCODATION] > 0) {
				return EDIFACT_ENCODATION;
			}
			if (minCount == 1 && mins[TEXT_ENCODATION] > 0) {
				return TEXT_ENCODATION;
			}
			if (minCount == 1 && mins[X12_ENCODATION] > 0) {
				return X12_ENCODATION;
			}
			return C40_ENCODATION;
		}

		int c = (uint8_t)msg.at(startpos + charsProcessed);
		charsProcessed++;

		//step L
		if (IsDigit(c)) {
			charCounts[ASCII_ENCODATION] += 0.5f;
		}
		else if (IsExtendedASCII(c)) {
			charCounts[ASCII_ENCODATION] = std::ceil(charCounts[ASCII_ENCODATION]);
			charCounts[ASCII_ENCODATION] += 2.0f;
		}
		else {
			charCounts[ASCII_ENCODATION] = std::ceil(charCounts[ASCII_ENCODATION]);
			charCounts[ASCII_ENCODATION] += 1.0f;
		}

		//step M
		if (IsNativeC40(c)) {
			charCounts[C40_ENCODATION] += 2.0f / 3.0f;
		}
		else if (IsExtendedASCII(c)) {
			charCounts[C40_ENCODATION] += 8.0f / 3.0f;
		}
		else {
			charCounts[C40_ENCODATION] += 4.0f / 3.0f;
		}

		//step N
		if (IsNativeText(c)) {
			charCounts[TEXT_ENCODATION] += 2.0f / 3.0f;
		}
		else if (IsExtendedASCII(c)) {
			charCounts[TEXT_ENCODATION] += 8.0f / 3.0f;
		}
		else {
			charCounts[TEXT_ENCODATION] += 4.0f / 3.0f;
		}

		//step O
		if (IsNativeX12(c)) {
			charCounts[X12_ENCODATION] += 2.0f / 3.0f;
		}
		else if (IsExtendedASCII(c)) {
			charCounts[X12_ENCODATION] += 13.0f / 3.0f;
		}
		else {
			charCounts[X12_ENCODATION] += 10.0f / 3.0f;
		}

		//step P
		if (IsNativeEDIFACT(c)) {
			charCounts[EDIFACT_ENCODATION] += 3.0f / 4.0f;
		}
		else if (IsExtendedASCII(c)) {
			charCounts[EDIFACT_ENCODATION] += 17.0f / 4.0f;
		}
		else {
			charCounts[EDIFACT_ENCODATION] += 13.0f / 4.0f;
		}

		// step Q
		if (IsSpecialB256(c)) {
			charCounts[BASE256_ENCODATION] += 4.0f;
		}
		else {
			charCounts[BASE256_ENCODATION] += 1.0f;
		}

		//step R
		if (charsProcessed >= 4) {
			std::transform(charCounts.begin(), charCounts.end(), intCharCounts.begin(),
						   [](float x) { return static_cast<int>(std::ceil(x)); });
			FindMinimums(intCharCounts, std::numeric_limits<int>::max(), mins);
			int minCount = Reduce(mins);

			if (intCharCounts[ASCII_ENCODATION] < intCharCounts[BASE256_ENCODATION]
				&& intCharCounts[ASCII_ENCODATION] < intCharCounts[C40_ENCODATION]
				&& intCharCounts[ASCII_ENCODATION] < intCharCounts[TEXT_ENCODATION]
				&& intCharCounts[ASCII_ENCODATION] < intCharCounts[X12_ENCODATION]
				&& intCharCounts[ASCII_ENCODATION] < intCharCounts[EDIFACT_ENCODATION]) {
				return ASCII_ENCODATION;
			}
			if (intCharCounts[BASE256_ENCODATION] < intCharCounts[ASCII_ENCODATION]
				|| (mins[C40_ENCODATION] + mins[TEXT_ENCODATION] + mins[X12_ENCODATION] + mins[EDIFACT_ENCODATION]) == 0) {
				return BASE256_ENCODATION;
			}
			if (minCount == 1 && mins[EDIFACT_ENCODATION] > 0) {
				return EDIFACT_ENCODATION;
			}
			if (minCount == 1 && mins[TEXT_ENCODATION] > 0) {
				return TEXT_ENCODATION;
			}
			if (minCount == 1 && mins[X12_ENCODATION] > 0) {
				return X12_ENCODATION;
			}
			if (intCharCounts[C40_ENCODATION] + 1 < intCharCounts[ASCII_ENCODATION]
				&& intCharCounts[C40_ENCODATION] + 1 < intCharCounts[BASE256_ENCODATION]
				&& intCharCounts[C40_ENCODATION] + 1 < intCharCounts[EDIFACT_ENCODATION]
				&& intCharCounts[C40_ENCODATION] + 1 < intCharCounts[TEXT_ENCODATION]) {
				if (intCharCounts[C40_ENCODATION] < intCharCounts[X12_ENCODATION]) {
					return C40_ENCODATION;
				}
				if (intCharCounts[C40_ENCODATION] == intCharCounts[X12_ENCODATION]) {
					size_t p = startpos + charsProcessed + 1;
					while (p < msg.length()) {
						int tc = msg.at(p);
						if (IsX12TermSep(tc)) {
							return X12_ENCODATION;
						}
						if (!IsNativeX12(tc)) {
							break;
						}
						p++;
					}
					return C40_ENCODATION;
				}
			}
		}
	}
}

static std::string ToHexString(int c)
{
	const char* digits = "0123456789abcdef";
	std::string val(4, '0');
	val[1] = 'x';
	val[2] = digits[(c >> 4) & 0xf];
	val[3] = digits[c & 0xf];
	return val;
}

namespace ASCIIEncoder {
	/**
	* Determines the number of consecutive characters that are encodable using numeric compaction.
	*
	* @param msg      the message
	* @param startpos the start position within the message
	* @return the requested character count
	*/
	static int DetermineConsecutiveDigitCount(const std::string& msg, int startpos)
	{
		auto begin = msg.begin() + startpos;
		return narrow_cast<int>(std::find_if_not(begin, msg.end(), IsDigit) - begin);
	}

	static uint8_t EncodeASCIIDigits(int digit1, int digit2)
	{
		if (IsDigit(digit1) && IsDigit(digit2)) {
			int num = (digit1 - '0') * 10 + (digit2 - '0');
			return static_cast<uint8_t>(num + 130);
		}
		return '?';
	}

	static void EncodeASCII(EncoderContext& context)
	{
		//step B
		int n = DetermineConsecutiveDigitCount(context.message(), context.currentPos());
		if (n >= 2) {
			context.addCodeword(EncodeASCIIDigits(context.currentChar(), context.nextChar()));
			context.setCurrentPos(context.currentPos() + 2);
		}
		else {
			int c = context.currentChar();
			int newMode = LookAheadTest(context.message(), context.currentPos(), ASCII_ENCODATION);
			if (newMode != ASCII_ENCODATION)
			{
				// the order here is the same as ENCODATION;
				context.addCodeword(LATCHES[newMode]);
				context.setNewEncoding(newMode);
			}
			else if (IsExtendedASCII(c)) {
				context.addCodeword(UPPER_SHIFT);
				context.addCodeword(static_cast<uint8_t>(c - 128 + 1));
				context.setCurrentPos(context.currentPos() + 1);
			}
			else {
				context.addCodeword(static_cast<uint8_t>(c + 1));
				context.setCurrentPos(context.currentPos() + 1);
			}
		}
	}

} // ASCIIEncoder

namespace C40Encoder {

	static int EncodeChar(int c, std::string& sb)
	{
		if (c == ' ') {
			sb.push_back('\3');
			return 1;
		}
		if (c >= '0' && c <= '9') {
			sb.push_back((char)(c - 48 + 4));
			return 1;
		}
		if (c >= 'A' && c <= 'Z') {
			sb.push_back((char)(c - 65 + 14));
			return 1;
		}
		if (c >= '\0' && c <= '\x1f') {
			sb.push_back('\0'); //Shift 1 Set
			sb.push_back(c);
			return 2;
		}
		if (c <= '/') {
			sb.push_back('\1'); //Shift 2 Set
			sb.push_back((char)(c - 33));
			return 2;
		}
		if (c <= '@') {
			sb.push_back('\1'); //Shift 2 Set
			sb.push_back((char)(c - 58 + 15));
			return 2;
		}
		if (c <= '_') {
			sb.push_back('\1'); //Shift 2 Set
			sb.push_back((char)(c - 91 + 22));
			return 2;
		}
		if (c <= '\x7f') {
			sb.push_back('\2'); //Shift 3 Set
			sb.push_back((char)(c - 96));
			return 2;
		}
		sb.append("\1\x1e"); //Shift 2, Upper Shift
		int len = 2;
		len += EncodeChar((char)(c - 0x80), sb);
		return len;
	}

	static int BacktrackOneCharacter(EncoderContext& context, std::string& buffer, std::string& removed, int lastCharSize,
									 std::function<int(int, std::string&)> encodeChar)
	{
		buffer.resize(buffer.size() - lastCharSize);
		context.setCurrentPos(context.currentPos() - 1);
		int c = context.currentChar();
		lastCharSize = encodeChar(c, removed);
		context.resetSymbolInfo(); //Deal with possible reduction in symbol size
		return lastCharSize;
	}

	static void EncodeToCodewords(EncoderContext& context, const std::string& sb, int startPos) {
		int c1 = sb.at(startPos);
		int c2 = sb.at(startPos + 1);
		int c3 = sb.at(startPos + 2);
		int v = (1600 * c1) + (40 * c2) + c3 + 1;
		context.addCodeword(narrow_cast<uint8_t>(v / 256));
		context.addCodeword(narrow_cast<uint8_t>(v % 256));
	}

	static void WriteNextTriplet(EncoderContext& context, std::string& buffer)
	{
		EncodeToCodewords(context, buffer, 0);
		buffer.erase(0, 3);
	}

	/**
	* Handle "end of data" situations
	*
	* @param context the encoder context
	* @param buffer  the buffer with the remaining encoded characters
	*/
	static void HandleEOD(EncoderContext& context, std::string& buffer)
	{
		int unwritten = (Size(buffer) / 3) * 2;
		int rest = Size(buffer) % 3;

		int curCodewordCount = context.codewordCount() + unwritten;
		auto symbolInfo = context.updateSymbolInfo(curCodewordCount);
		int available = symbolInfo->dataCapacity() - curCodewordCount;

		if (rest == 2) {
			buffer.push_back('\0'); //Shift 1
			while (buffer.length() >= 3) {
				WriteNextTriplet(context, buffer);
			}
			if (context.hasMoreCharacters()) {
				context.addCodeword(C40_UNLATCH);
			}
		}
		else if (available == 1 && rest == 1) {
			while (buffer.length() >= 3) {
				WriteNextTriplet(context, buffer);
			}
			if (context.hasMoreCharacters()) {
				context.addCodeword(C40_UNLATCH);
			}
			// else no unlatch
			context.setCurrentPos(context.currentPos() - 1);
		}
		else if (rest == 0) {
			while (buffer.length() >= 3) {
				WriteNextTriplet(context, buffer);
			}
			if (available > 0 || context.hasMoreCharacters()) {
				context.addCodeword(C40_UNLATCH);
			}
		}
		else {
			throw std::logic_error("Unexpected case. Please report!");
		}
		context.setNewEncoding(ASCII_ENCODATION);
	}

	static void EncodeC40(EncoderContext& context, std::function<int (int, std::string&)> encodeChar, int encodingMode)
	{
		//step C
		std::string buffer;
		while (context.hasMoreCharacters()) {
			int c = context.currentChar();
			context.setCurrentPos(context.currentPos() + 1);
			int lastCharSize = encodeChar(c, buffer);
			int unwritten = narrow_cast<int>(buffer.length() / 3) * 2;
			int curCodewordCount = context.codewordCount() + unwritten;
			auto symbolInfo = context.updateSymbolInfo(curCodewordCount);
			int available = symbolInfo->dataCapacity() - curCodewordCount;

			if (!context.hasMoreCharacters()) {
				//Avoid having a single C40 value in the last triplet
				std::string removed;
				if ((buffer.length() % 3) == 2 && available != 2) {
					lastCharSize = BacktrackOneCharacter(context, buffer, removed, lastCharSize, encodeChar);
				}
				while ((buffer.length() % 3) == 1 && ((lastCharSize <= 3 && available != 1) || lastCharSize > 3)) {
					lastCharSize = BacktrackOneCharacter(context, buffer, removed, lastCharSize, encodeChar);
				}
				break;
			}

			if ((buffer.length() % 3) == 0) {
				int newMode = LookAheadTest(context.message(), context.currentPos(), encodingMode);
				if (newMode != encodingMode) {
					// Return to ASCII encodation, which will actually handle latch to new mode
					context.setNewEncoding(ASCII_ENCODATION);
					break;
				}
			}
		}
		return HandleEOD(context, buffer);
	}

	static void EncodeC40(EncoderContext& context)
	{
		EncodeC40(context, EncodeChar, C40_ENCODATION);
	}

} // C40Encoder

namespace DMTextEncoder {

	static int EncodeChar(int c, std::string& sb)
	{
		if (c == ' ') {
			sb.push_back('\3');
			return 1;
		}
		if (c >= '0' && c <= '9') {
			sb.push_back((char)(c - 48 + 4));
			return 1;
		}
		if (c >= 'a' && c <= 'z') {
			sb.push_back((char)(c - 97 + 14));
			return 1;
		}
		if (c >= '\0' && c <= '\x1f') {
			sb.push_back('\0'); //Shift 1 Set
			sb.push_back(c);
			return 2;
		}
		if (c <= '/') {
			sb.push_back('\1'); //Shift 2 Set
			sb.push_back((char)(c - 33));
			return 2;
		}
		if (c <= '@') {
			sb.push_back('\1'); //Shift 2 Set
			sb.push_back((char)(c - 58 + 15));
			return 2;
		}
		if (c >= '[' && c <= '_') {
			sb.push_back('\1'); //Shift 2 Set
			sb.push_back((char)(c - 91 + 22));
			return 2;
		}
		if (c == '\x60') {
			sb.push_back('\2'); //Shift 3 Set
			sb.push_back((char)(c - 96));
			return 2;
		}
		if (c <= 'Z') {
			sb.push_back('\2'); //Shift 3 Set
			sb.push_back((char)(c - 65 + 1));
			return 2;
		}
		if (c <= '\x7f') {
			sb.push_back('\2'); //Shift 3 Set
			sb.push_back((char)(c - 123 + 27));
			return 2;
		}
		sb.append("\1\x1e"); //Shift 2, Upper Shift
		int len = 2;
		len += EncodeChar(c - 128, sb);
		return len;
	}

	static void EncodeText(EncoderContext& context)
	{
		C40Encoder::EncodeC40(context, EncodeChar, TEXT_ENCODATION);
	}

} // DMTextEncoder

namespace X12Encoder {

	static int EncodeChar(int c, std::string& sb)
	{
		switch (c) {
		case '\r': sb.push_back('\0'); break;
		case '*': sb.push_back('\1'); break;
		case '>': sb.push_back('\2'); break;
		case ' ': sb.push_back('\3'); break;
		default:
			if (c >= '0' && c <= '9') {
				sb.push_back((char)(c - 48 + 4));
			} else if (c >= 'A' && c <= 'Z') {
				sb.push_back((char)(c - 65 + 14));
			} else {
				throw std::invalid_argument("Illegal character: " + ToHexString(c));
			}
			break;
		}
		return 1;
	}

	static void HandleEOD(EncoderContext& context, std::string& buffer)
	{
		int codewordCount = context.codewordCount();
		auto symbolInfo = context.updateSymbolInfo(codewordCount);
		int available = symbolInfo->dataCapacity() - codewordCount;
		context.setCurrentPos(context.currentPos() - Size(buffer));
		if (context.remainingCharacters() > 1 || available > 1 || context.remainingCharacters() != available) {
			context.addCodeword(X12_UNLATCH);
		}
		if (context.newEncoding() < 0) {
			context.setNewEncoding(ASCII_ENCODATION);
		}
	}

	static void EncodeX12(EncoderContext& context)
	{
		//step C
		std::string buffer;
		while (context.hasMoreCharacters()) {
			int c = context.currentChar();
			context.setCurrentPos(context.currentPos() + 1);
			EncodeChar(c, buffer);
			size_t count = buffer.length();
			if ((count % 3) == 0) {
				C40Encoder::WriteNextTriplet(context, buffer);

				int newMode = LookAheadTest(context.message(), context.currentPos(), X12_ENCODATION);
				if (newMode != X12_ENCODATION) {
					// Return to ASCII encodation, which will actually handle latch to new mode
					context.setNewEncoding(ASCII_ENCODATION);
					break;
				}
			}
		}
		HandleEOD(context, buffer);
	}
	
} // X12Encoder

namespace EdifactEncoder {

	static void EncodeChar(int c, std::string& sb)
	{
		if (c >= ' ' && c <= '?') {
			sb.push_back(c);
		}
		else if (c >= '@' && c <= '^') {
			sb.push_back((char)(c - 64));
		}
		else {
			throw std::invalid_argument("Illegal character: " + ToHexString(c));
		}
	}

	static ByteArray EncodeToCodewords(const std::string& sb, int startPos)
	{
		int len = Size(sb) - startPos;
		if (len == 0) {
			throw std::invalid_argument("buffer must not be empty");
		}
		int c1 = sb.at(startPos);
		int c2 = len >= 2 ? sb.at(startPos + 1) : 0;
		int c3 = len >= 3 ? sb.at(startPos + 2) : 0;
		int c4 = len >= 4 ? sb.at(startPos + 3) : 0;

		int v = (c1 << 18) + (c2 << 12) + (c3 << 6) + c4;
		uint8_t cw1 = (v >> 16) & 255;
		uint8_t cw2 = (v >> 8) & 255;
		uint8_t cw3 = v & 255;
		ByteArray res;
		res.reserve(3);
		res.push_back(cw1);
		if (len >= 2) {
			res.push_back(cw2);
		}
		if (len >= 3) {
			res.push_back(cw3);
		}
		return res;
	}

	/**
	* Handle "end of data" situations
	*
	* @param context the encoder context
	* @param buffer  the buffer with the remaining encoded characters
	*/
	static void HandleEOD(EncoderContext& context, std::string& buffer)
	{
		try {
			size_t count = buffer.length();
			if (count == 0) {
				return; //Already finished
			}
			if (count == 1) {
				//Only an unlatch at the end
				int codewordCount = context.codewordCount();
				auto symbolInfo = context.updateSymbolInfo(codewordCount);
				int available = symbolInfo->dataCapacity() - codewordCount;
				int remaining = context.remainingCharacters();
				// The following two lines are a hack inspired by the 'fix' from https://sourceforge.net/p/barcode4j/svn/221/
				if (remaining > available)
					available = context.updateSymbolInfo(codewordCount+1)->dataCapacity() - codewordCount;
				if (remaining <= available && available <= 2) {
					return; //No unlatch
				}
			}

			if (count > 4) {
				throw std::invalid_argument("Count must not exceed 4");
			}
			int restChars = static_cast<int>(count - 1);
			auto encoded = EncodeToCodewords(buffer, 0);
			bool endOfSymbolReached = !context.hasMoreCharacters();
			bool restInAscii = endOfSymbolReached && restChars <= 2;

			if (restChars <= 2) {
				int codewordCount = context.codewordCount();
				auto symbolInfo = context.updateSymbolInfo(codewordCount + restChars);
				int available = symbolInfo->dataCapacity() - codewordCount;
				if (available >= 3) {
					restInAscii = false;
					context.updateSymbolInfo(codewordCount + Size(encoded));
					//available = context.symbolInfo.dataCapacity - context.getCodewordCount();
				}
			}

			if (restInAscii) {
				context.resetSymbolInfo();
				context.setCurrentPos(context.currentPos() - restChars);
			}
			else {
				for (uint8_t cw : encoded) {
					context.addCodeword(cw);
				}
			}
		}
		catch (...) {
			context.setNewEncoding(ASCII_ENCODATION);
			throw;
		}
		context.setNewEncoding(ASCII_ENCODATION);
	}

	static void EncodeEdifact(EncoderContext& context)
	{
		//step F
		std::string buffer;
		while (context.hasMoreCharacters()) {
			int c = context.currentChar();
			EncodeChar(c, buffer);
			context.setCurrentPos(context.currentPos() + 1);

			if (buffer.length() >= 4) {
				auto codewords = EncodeToCodewords(buffer, 0);
				for (uint8_t cw : codewords) {
					context.addCodeword(cw);
				}
				buffer.erase(0, 4);

				int newMode = LookAheadTest(context.message(), context.currentPos(), EDIFACT_ENCODATION);
				if (newMode != EDIFACT_ENCODATION) {
					// Return to ASCII encodation, which will actually handle latch to new mode
					context.setNewEncoding(ASCII_ENCODATION);
					break;
				}
			}
		}
		buffer.push_back(31); //Unlatch
		HandleEOD(context, buffer);
	}

} // EdifactEncoder

namespace Base256Encoder {

	static int Randomize255State(int ch, int codewordPosition)
	{
		int pseudoRandom = ((149 * codewordPosition) % 255) + 1;
		int tempVariable = ch + pseudoRandom;
		if (tempVariable <= 255) {
			return tempVariable;
		}
		else {
			return tempVariable - 256;
		}
	}

	static void EncodeBase256(EncoderContext& context)
	{
		std::string buffer;
		buffer.push_back('\0'); //Initialize length field
		while (context.hasMoreCharacters()) {
			int c = context.currentChar();
			buffer.push_back(c);

			context.setCurrentPos(context.currentPos() + 1);

			int newMode = LookAheadTest(context.message(), context.currentPos(), BASE256_ENCODATION);
			if (newMode != BASE256_ENCODATION) {
				// Return to ASCII encodation, which will actually handle latch to new mode
				context.setNewEncoding(ASCII_ENCODATION);
				break;
			}
		}
		int dataCount = Size(buffer) - 1;
		int lengthFieldSize = 1;
		int currentSize = context.codewordCount() + dataCount + lengthFieldSize;
		auto symbolInfo = context.updateSymbolInfo(currentSize);
		bool mustPad = (symbolInfo->dataCapacity() - currentSize) > 0;
		if (context.hasMoreCharacters() || mustPad) {
			if (dataCount <= 249) {
				buffer.at(0) = (char)dataCount;
			}
			else if (dataCount <= 1555) {
				buffer.at(0) = (char)((dataCount / 250) + 249);
				buffer.insert(1, 1, (char)(dataCount % 250));
			}
			else {
				throw std::invalid_argument("Message length not in valid ranges: " + std::to_string(dataCount));
			}
		}
		for (char c : buffer) {
			context.addCodeword(Randomize255State(c, context.codewordCount() + 1));
		}
	}

} // Base256Encoder

static bool StartsWith(const std::wstring& s, const std::wstring& ss)
{
	return s.length() > ss.length() && s.compare(0, ss.length(), ss) == 0;
}

static bool EndsWith(const std::wstring& s, const std::wstring& ss)
{
	return s.length() > ss.length() && s.compare(s.length() - ss.length(), ss.length(), ss) == 0;
}

ByteArray Encode(const std::wstring& msg)
{
	return Encode(msg, SymbolShape::NONE, -1, -1, -1, -1);
}

/**
* Performs message encoding of a DataMatrix message using the algorithm described in annex P
* of ISO/IEC 16022:2000(E).
*
* @param msg     the message
* @param shape   requested shape. May be {@code SymbolShapeHint.FORCE_NONE},
*                {@code SymbolShapeHint.FORCE_SQUARE} or {@code SymbolShapeHint.FORCE_RECTANGLE}.
* @param minSize the minimum symbol size constraint or null for no constraint
* @param maxSize the maximum symbol size constraint or null for no constraint
* @return the encoded message (the char values range from 0 to 255)
*/
ByteArray Encode(const std::wstring& msg, SymbolShape shape, int minWidth, int minHeight, int maxWidth, int maxHeight)
{
	//the codewords 0..255 are encoded as Unicode characters
	//Encoder[] encoders = {
	//	new ASCIIEncoder(), new C40Encoder(), new TextEncoder(),
	//	new X12Encoder(), new EdifactEncoder(),  new Base256Encoder()
	//};

	EncoderContext context(TextEncoder::FromUnicode(msg, CharacterSet::ISO8859_1));
	context.setSymbolShape(shape);
	context.setSizeConstraints(minWidth, minHeight, maxWidth, maxHeight);

	if (StartsWith(msg, MACRO_05_HEADER) && EndsWith(msg, MACRO_TRAILER)) {
		context.addCodeword(MACRO_05);
		context.setSkipAtEnd(2);
		context.setCurrentPos(Size(MACRO_05_HEADER));
	}
	else if (StartsWith(msg, MACRO_06_HEADER) && EndsWith(msg, MACRO_TRAILER)) {
		context.addCodeword(MACRO_06);
		context.setSkipAtEnd(2);
		context.setCurrentPos(Size(MACRO_06_HEADER));
	}

	int encodingMode = ASCII_ENCODATION; //Default mode
	while (context.hasMoreCharacters()) {
		switch (encodingMode) {
		case ASCII_ENCODATION:   ASCIIEncoder::EncodeASCII(context);     break;
		case C40_ENCODATION:     C40Encoder::EncodeC40(context);         break;
		case TEXT_ENCODATION:    DMTextEncoder::EncodeText(context);     break;
		case X12_ENCODATION:     X12Encoder::EncodeX12(context);         break;
		case EDIFACT_ENCODATION: EdifactEncoder::EncodeEdifact(context); break;
		case BASE256_ENCODATION: Base256Encoder::EncodeBase256(context); break;
		}
		if (context.newEncoding() >= 0) {
			encodingMode = context.newEncoding();
			context.clearNewEncoding();
		}
	}
	int len = context.codewordCount();
	auto symbolInfo = context.updateSymbolInfo(len);
	int capacity = symbolInfo->dataCapacity();
	if (len < capacity) {
		if (encodingMode != ASCII_ENCODATION && encodingMode != BASE256_ENCODATION && encodingMode != EDIFACT_ENCODATION) {
			context.addCodeword('\xfe'); //Unlatch (254)
		}
	}
	//Padding
	if (context.codewordCount() < capacity) {
		context.addCodeword(PAD);
	}
	while (context.codewordCount() < capacity) {
		context.addCodeword(Randomize253State(PAD, context.codewordCount() + 1));
	}

	return context.codewords();
}

} // namespace ZXing::DataMatrix
