/*
* Copyright 2016 Huy Cuong Nguyen
* Copyright 2016 ZXing authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*      http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

#include "oned/ODCode128Writer.h"
#include "oned/ODWriterHelper.h"
#include "oned/ODCode128Patterns.h"

#include <list>
#include <numeric>

namespace ZXing {
namespace OneD {

static const int CODE_START_A = 103;
static const int CODE_START_B = 104;
static const int CODE_START_C = 105;
static const int CODE_CODE_A = 101;
static const int CODE_CODE_B = 100;
static const int CODE_CODE_C = 99;
static const int CODE_STOP = 106;

// Dummy characters used to specify control characters in input
static const auto ESCAPE_FNC_1 = L'\u00f1';
static const auto ESCAPE_FNC_2 = L'\u00f2';
static const auto ESCAPE_FNC_3 = L'\u00f3';
static const auto ESCAPE_FNC_4 = L'\u00f4';

static const int CODE_FNC_1 = 102;   // Code A, Code B, Code C
static const int CODE_FNC_2 = 97;    // Code A, Code B
static const int CODE_FNC_3 = 96;    // Code A, Code B
static const int CODE_FNC_4_A = 101; // Code A
static const int CODE_FNC_4_B = 100; // Code B

 // Results of minimal lookahead for code C
enum class CType {
	UNCODABLE,
	ONE_DIGIT,
	TWO_DIGITS,
	FNC_1
};

static CType FindCType(const std::wstring& value, int start)
{
	int last = static_cast<int>(value.length());
	if (start >= last) {
		return CType::UNCODABLE;
	}
	wchar_t c = value[start];
	if (c == ESCAPE_FNC_1) {
		return CType::FNC_1;
	}
	if (c < '0' || c > '9') {
		return CType::UNCODABLE;
	}
	if (start + 1 >= last) {
		return CType::ONE_DIGIT;
	}
	c = value[start + 1];
	if (c < '0' || c > '9') {
		return CType::ONE_DIGIT;
	}
	return CType::TWO_DIGITS;
}

static int ChooseCode(const std::wstring& value, int start, int oldCode)
{
	CType lookahead = FindCType(value, start);
	if (lookahead == CType::ONE_DIGIT) {
		if (oldCode == CODE_CODE_A) {
			return CODE_CODE_A;
		}
		return CODE_CODE_B;
	}
	if (lookahead == CType::UNCODABLE) {
		if (start < (int)value.length()) {
			int c = value[start];
			if (c < ' ' || (oldCode == CODE_CODE_A && (c < '`' || (c >= ESCAPE_FNC_1 && c <= ESCAPE_FNC_4)))) {
				// can continue in code A, encodes ASCII 0 to 95 or FNC1 to FNC4
				return CODE_CODE_A;
			}
		}
		return CODE_CODE_B; // no choice
	}
	if (oldCode == CODE_CODE_A && lookahead == CType::FNC_1) {
		return CODE_CODE_A;
	}
	if (oldCode == CODE_CODE_C) { // can continue in code C
		return CODE_CODE_C;
	}
	if (oldCode == CODE_CODE_B) {
		if (lookahead == CType::FNC_1) {
			return CODE_CODE_B; // can continue in code B
		}
		// Seen two consecutive digits, see what follows
		lookahead = FindCType(value, start + 2);
		if (lookahead == CType::UNCODABLE || lookahead == CType::ONE_DIGIT) {
			return CODE_CODE_B; // not worth switching now
		}
		if (lookahead == CType::FNC_1) { // two digits, then FNC_1...
			lookahead = FindCType(value, start + 3);
			if (lookahead == CType::TWO_DIGITS) { // then two more digits, switch
				return CODE_CODE_C;
			}
			else {
				return CODE_CODE_B; // otherwise not worth switching
			}
		}
		// At this point, there are at least 4 consecutive digits.
		// Look ahead to choose whether to switch now or on the next round.
		int index = start + 4;
		while ((lookahead = FindCType(value, index)) == CType::TWO_DIGITS) {
			index += 2;
		}
		if (lookahead == CType::ONE_DIGIT) { // odd number of digits, switch later
			return CODE_CODE_B;
		}
		return CODE_CODE_C; // even number of digits, switch now
	}
	// Here oldCode == 0, which means we are choosing the initial code
	if (lookahead == CType::FNC_1) { // ignore FNC_1
		lookahead = FindCType(value, start + 1);
	}
	if (lookahead == CType::TWO_DIGITS) { // at least two digits, start in code C
		return CODE_CODE_C;
	}
	return CODE_CODE_B;
}

BitMatrix
Code128Writer::encode(const std::wstring& contents, int width, int height) const
{
	// Check length
	int length = static_cast<int>(contents.length());
	if (length < 1 || length > 80) {
		throw std::invalid_argument("Contents length should be between 1 and 80 characters");
	}

	// Check content
	for (int i = 0; i < length; ++i) {
		int c = contents[i];
		switch (c) {
			case ESCAPE_FNC_1:
			case ESCAPE_FNC_2:
			case ESCAPE_FNC_3:
			case ESCAPE_FNC_4:
				break;
			default:
				if (c > 127) {
					// support for FNC4 isn't implemented, no full Latin-1 character set available at the moment
					throw std::invalid_argument(std::string("Bad character in input: ") + static_cast<char>(c));
				}
		}
	}

	std::list<std::vector<int>> patterns; // temporary storage for patterns
	int checkSum = 0;
	int checkWeight = 1;
	int codeSet = 0; // selected code (CODE_CODE_B or CODE_CODE_C)
	int position = 0; // position in contents

	while (position < length) {
		//Select code to use
		int newCodeSet = ChooseCode(contents, position, codeSet);

		//Get the pattern index
		int patternIndex;
		if (newCodeSet == codeSet) {
			// Encode the current character
			// First handle escapes
			switch (contents[position]) {
			case ESCAPE_FNC_1:
				patternIndex = CODE_FNC_1;
				break;
			case ESCAPE_FNC_2:
				patternIndex = CODE_FNC_2;
				break;
			case ESCAPE_FNC_3:
				patternIndex = CODE_FNC_3;
				break;
			case ESCAPE_FNC_4:
				if (codeSet == CODE_CODE_A) {
					patternIndex = CODE_FNC_4_A;
				}
				else {
					patternIndex = CODE_FNC_4_B;
				}
				break;
			default:
				// Then handle normal characters otherwise
				if (codeSet == CODE_CODE_A) {
					patternIndex = contents[position] - ' ';
					if (patternIndex < 0) {
						// everything below a space character comes behind the underscore in the code patterns table
						patternIndex += '`';
					}
				}
				else if (codeSet == CODE_CODE_B) {
					patternIndex = contents[position] - ' ';
				}
				else { // CODE_CODE_C
					patternIndex = (contents[position] - '0') * 10 + (position+1 < length ? contents[position+1] - '0' : 0);
					position++; // Also incremented below
				}
			}
			position++;
		}
		else {
			// Should we change the current code?
			// Do we have a code set?
			if (codeSet == 0) {
				// No, we don't have a code set
				if (newCodeSet == CODE_CODE_A) {
					patternIndex = CODE_START_A;
				}
				else if (newCodeSet == CODE_CODE_B) {
					patternIndex = CODE_START_B;
				}
				else {
					// CODE_CODE_C
					patternIndex = CODE_START_C;
				}
			}
			else {
				// Yes, we have a code set
				patternIndex = newCodeSet;
			}
			codeSet = newCodeSet;
		}

		// Get the pattern
		patterns.push_back(Code128::CODE_PATTERNS[patternIndex]);

		// Compute checksum
		checkSum += patternIndex * checkWeight;
		if (position != 0) {
			checkWeight++;
		}
	}

	// Compute and append checksum
	checkSum %= 103;
	patterns.push_back(Code128::CODE_PATTERNS[checkSum]);

	// Append stop code
	patterns.push_back(Code128::CODE_PATTERNS[CODE_STOP]);

	// Compute code width
	int codeWidth = 0;
	for (const std::vector<int>& pattern : patterns) {
		codeWidth += std::accumulate(pattern.begin(), pattern.end(), 0);
	}

	// Compute result
	std::vector<bool> result(codeWidth, false);
	int pos = 0;
	for (const std::vector<int>& pattern : patterns) {
		pos += WriterHelper::AppendPattern(result, pos, pattern, true);
	}

	return WriterHelper::RenderResult(result, width, height, _sidesMargin >= 0 ? _sidesMargin : 10);
}

} // OneD
} // ZXing
