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

#pragma once

#include "BitArray.h"
#include "Pattern.h"
#include "BarcodeData.h"
#include "ZXAlgorithms.h"

#include <algorithm>
#include <cassert>
#include <cmath>
#include <cstddef>
#include <iterator>
#include <limits>
#include <memory>

/*
Code39 : 1:2/3, 5+4+1 (0x3|2x1 wide) -> 12-15 mods, v1-? | ToNarrowWide(OMG 1) == *
Codabar: 1:2/3, 4+3+1 (1x1|1x2|3x0 wide) -> 9-13 mods, v1-? | ToNarrowWide(OMG 2) == ABCD
ITF    : 1:2/3, 5+5   (2x2 wide) -> mods, v6-?| .5, .38 == * | qz:10

Code93 : 1-4, 3+3 -> 9 mods  v1-? | round to 1-4 == *
Code128: 1-4, 3+3 -> 11 mods v1-? | .7, .25 == ABC | qz:10
UPC/EAN: 1-4, 2+2 -> 7 mods  f    | .7, .48 == *
  UPC-A: 11d 95m = 3 + 6*4 + 5 + 6*4 + 3 = 59 | qz:3
  EAN-13: 12d 95m
  UPC-E: 6d, 3 + 6*4 + 6 = 33
  EAN-8: 8d, 3 + 4*4 + 5 + 4*4 + 3 = 43

RSS14  : 1-8, finder: (15,2+3), symbol: (15/16,4+4) | .45, .2 (finder only), 14d
  code = 2xguard + 2xfinder + 4xsymbol = (96,23), stacked = 2x50 mods
RSSExp.:  v?-74d/?-41c
*/

namespace ZXing {

class ReaderOptions;

namespace OneD {

/**
* Encapsulates functionality and implementation that is common to all families
* of one-dimensional barcodes.
*/
class RowReader
{
protected:
	const ReaderOptions& _opts;

public:
	explicit RowReader(const ReaderOptions& opts) : _opts(opts) {}
	explicit RowReader(ReaderOptions&&) = delete;

	struct DecodingState
	{
		virtual ~DecodingState() = default;
	};

	virtual ~RowReader() = default;

	virtual BarcodeData decodePattern(int rowNumber, PatternView& next, std::unique_ptr<DecodingState>& state) const = 0;

	/**
	 * Determines how closely a set of observed counts of runs of black/white values matches a given
	 * target pattern. This is reported as the ratio of the total variance from the expected pattern
	 * proportions across all pattern elements, to the length of the pattern.
	 *
	 * @param counters observed counters
	 * @param pattern expected pattern
	 * @param maxIndividualVariance The most any counter can differ before we give up
	 * @return ratio of total variance between counters and pattern compared to total pattern size
	 */
	template <typename CP, typename PP>
	static float PatternMatchVariance(const CP* counters, const PP* pattern, size_t length, float maxIndividualVariance)
	{
		int total = Reduce(counters, counters + length, 0);
		int patternLength = Reduce(pattern, pattern + length, 0);
		if (total < patternLength) {
			// If we don't even have one pixel per unit of bar width, assume this is too small
			// to reliably match, so fail:
			return std::numeric_limits<float>::max();
		}

		float unitBarWidth = (float)total / patternLength;
		maxIndividualVariance *= unitBarWidth;

		float totalVariance = 0.0f;
		for (size_t x = 0; x < length; ++x) {
			float variance = std::abs(counters[x] - pattern[x] * unitBarWidth);
			if (variance > maxIndividualVariance) {
				return std::numeric_limits<float>::max();
			}
			totalVariance += variance;
		}
		return totalVariance / total;
	}

	template <typename Counters, typename Pattern>
	static float PatternMatchVariance(const Counters& counters, const Pattern& pattern, float maxIndividualVariance) {
		assert(Size(counters) == Size(pattern));
		return PatternMatchVariance(std::data(counters), std::data(pattern), std::size(counters), maxIndividualVariance);
	}

	/**
	* Attempts to decode a sequence of black/white lines into single
	* digit.
	*
	* @param counters the counts of runs of observed black/white/black/... values
	* @param patterns the list of patterns to compare the contents of counters to
	* @param requireUnambiguousMatch the 'best match' must be better than all other matches
	* @return The decoded digit index, -1 if no pattern matched
	*/
	template <typename Counters, typename Patterns>
	static int DecodeDigit(const Counters& counters, const Patterns& patterns, float maxAvgVariance,
						   float maxIndividualVariance, bool requireUnambiguousMatch = true)
	{
		float bestVariance = maxAvgVariance; // worst variance we'll accept
		constexpr int INVALID_MATCH = -1;
		int bestMatch = INVALID_MATCH;
		for (int i = 0; i < Size(patterns); i++) {
			float variance = PatternMatchVariance(counters, patterns[i], maxIndividualVariance);
			if (variance < bestVariance) {
				bestVariance = variance;
				bestMatch = i;
			} else if (requireUnambiguousMatch && variance == bestVariance) {
				// if we find a second 'best match' with the same variance, we can not reliably report to have a suitable match
				bestMatch = INVALID_MATCH;
			}
		}
		return bestMatch;
	}

	/**
	 * @brief NarrowWideThreshold calculates width thresholds to separate narrow and wide bars and spaces.
	 *
	 * This is useful for codes like Codabar, Code39 and ITF which distinguish between narrow and wide
	 * bars/spaces. Where wide ones are between 2 and 3 times as wide as the narrow ones.
	 *
	 * @param view containing one character
	 * @return threshold value for bars and spaces
	 */
	static BarAndSpaceI NarrowWideThreshold(const PatternView& view)
	{
		BarAndSpaceI m = {view[0], view[1]};
		BarAndSpaceI M = m;
		for (int i = 2; i < view.size(); ++i)
			UpdateMinMax(m[i], M[i], view[i]);

		// the max-spread check between bar/space depends on whether both have seen narrow and wide
		int maxSpread = M[0] >= 2 * m[0] && M[1] >= 2 * m[1] ? 2 : 4;

		BarAndSpaceI res;
		for (int i = 0; i < 2; ++i) {
			// check that
			//  a) wide <= 4 * narrow
			//  b) bars and spaces are not more than a factor of spread apart from each other
			if (M[i] > 4 * (m[i] + 1) || M[i] > maxSpread * M[i + 1] || m[i] > maxSpread * (m[i + 1] + 1))
				return {};
			// the threshold is the average of min and max but at least 1.5 * min
			res[i] = std::max((m[i] + M[i]) / 2, m[i] * 3 / 2);
		}

		return res;
	}

	/**
	 * @brief ToNarrowWidePattern takes a PatternView, calculates a NarrowWideThreshold and returns int where a '0' bit
	 * means narrow and a '1' bit means 'wide'.
	 */
	static int NarrowWideBitPattern(const PatternView& view)
	{
		const auto threshold = NarrowWideThreshold(view);
		if (!threshold.isValid())
			return -1;

		int pattern = 0;
		for (int i = 0; i < view.size(); ++i) {
			if (view[i] > threshold[i] * 2)
				return -1;
			AppendBit(pattern, view[i] > threshold[i]);
		}

		return pattern;
	}

	/**
	 * @brief Lookup the pattern in the table and return the character in alphabet at the same index.
	 * @returns 0 if pattern is not found. Used to be -1 but that fails on systems where char is unsigned.
	 */
	template<typename INDEX, typename ALPHABET>
	static char LookupBitPattern(int pattern, const INDEX& table, const ALPHABET& alphabet)
	{
		int i = IndexOf(table, pattern);
		return i == -1 ? 0 : alphabet[i];
	}

	template<typename INDEX, typename ALPHABET>
	static char DecodeNarrowWidePattern(const PatternView& view, const INDEX& table, const ALPHABET& alphabet)
	{
		return LookupBitPattern(NarrowWideBitPattern(view), table, alphabet);
	}
};

template<typename Range>
Barcode DecodeSingleRow(const RowReader& reader, const Range& range)
{
	PatternRow row;
	GetPatternRow(range, row);
	PatternView view(row);

	std::unique_ptr<RowReader::DecodingState> state;
	return {reader.decodePattern(0, view, state)};
}

} // OneD
} // ZXing
