/*
 * Copyright (C) 2010 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.
 */

/*
 * These authors would like to acknowledge the Spanish Ministry of Industry,
 * Tourism and Trade, for the support in the project TSI020301-2008-2
 * "PIRAmIDE: Personalizable Interactions with Resources on AmI-enabled
 * Mobile Dynamic Environments", led by Treelogic
 * ( http://www.treelogic.com/ ):
 *
 *   http://www.piramidepse.com/
 */

using System;
using System.Collections.Generic;

using ZXing.Common;
using ZXing.OneD.RSS.Expanded.Decoders;

namespace ZXing.OneD.RSS.Expanded
{
    /// <summary>
    /// <author>Pablo Orduña, University of Deusto (pablo.orduna@deusto.es)</author>
    /// <author>Eduardo Castillejo, University of Deusto (eduardo.castillejo@deusto.es)</author>
    /// </summary>
    public sealed class RSSExpandedReader : AbstractRSSReader
    {
        private static readonly int[] SYMBOL_WIDEST = { 7, 5, 4, 3, 1 };
        private static readonly int[] EVEN_TOTAL_SUBSET = { 4, 20, 52, 104, 204 };
        private static readonly int[] GSUM = { 0, 348, 1388, 2948, 3988 };

        private static readonly int[][] FINDER_PATTERNS =
        {
            new[] {1, 8, 4, 1}, // A
            new[] {3, 6, 4, 1}, // B
            new[] {3, 4, 6, 1}, // C
            new[] {3, 2, 8, 1}, // D
            new[] {2, 6, 5, 1}, // E
            new[] {2, 2, 9, 1} // F
        };

        private static readonly int[][] WEIGHTS =
        {
            new[] {1, 3, 9, 27, 81, 32, 96, 77},
            new[] {20, 60, 180, 118, 143, 7, 21, 63},
            new[] {189, 145, 13, 39, 117, 140, 209, 205},
            new[] {193, 157, 49, 147, 19, 57, 171, 91},
            new[] {62, 186, 136, 197, 169, 85, 44, 132},
            new[] {185, 133, 188, 142, 4, 12, 36, 108},
            new[] {113, 128, 173, 97, 80, 29, 87, 50},
            new[] {150, 28, 84, 41, 123, 158, 52, 156},
            new[] {46, 138, 203, 187, 139, 206, 196, 166},
            new[] {76, 17, 51, 153, 37, 111, 122, 155},
            new[] {43, 129, 176, 106, 107, 110, 119, 146},
            new[] {16, 48, 144, 10, 30, 90, 59, 177},
            new[] {109, 116, 137, 200, 178, 112, 125, 164},
            new[] {70, 210, 208, 202, 184, 130, 179, 115},
            new[] {134, 191, 151, 31, 93, 68, 204, 190},
            new[] {148, 22, 66, 198, 172, 94, 71, 2},
            new[] {6, 18, 54, 162, 64, 192, 154, 40},
            new[] {120, 149, 25, 75, 14, 42, 126, 167},
            new[] {79, 26, 78, 23, 69, 207, 199, 175},
            new[] {103, 98, 83, 38, 114, 131, 182, 124},
            new[] {161, 61, 183, 127, 170, 88, 53, 159},
            new[] {55, 165, 73, 8, 24, 72, 5, 15},
            new[] {45, 135, 194, 160, 58, 174, 100, 89}
        };

        private const int FINDER_PAT_A = 0;
        private const int FINDER_PAT_B = 1;
        private const int FINDER_PAT_C = 2;
        private const int FINDER_PAT_D = 3;
        private const int FINDER_PAT_E = 4;
        private const int FINDER_PAT_F = 5;

        private static readonly int[][] FINDER_PATTERN_SEQUENCES =
        {
            new[] {FINDER_PAT_A, FINDER_PAT_A},
            new[] {FINDER_PAT_A, FINDER_PAT_B, FINDER_PAT_B},
            new[] {FINDER_PAT_A, FINDER_PAT_C, FINDER_PAT_B, FINDER_PAT_D},
            new[] {FINDER_PAT_A, FINDER_PAT_E, FINDER_PAT_B, FINDER_PAT_D, FINDER_PAT_C},
            new[] {FINDER_PAT_A, FINDER_PAT_E, FINDER_PAT_B, FINDER_PAT_D, FINDER_PAT_D, FINDER_PAT_F},
            new[] {FINDER_PAT_A, FINDER_PAT_E, FINDER_PAT_B, FINDER_PAT_D, FINDER_PAT_E, FINDER_PAT_F, FINDER_PAT_F},
            new[] {FINDER_PAT_A, FINDER_PAT_A, FINDER_PAT_B, FINDER_PAT_B, FINDER_PAT_C, FINDER_PAT_C, FINDER_PAT_D, FINDER_PAT_D},
            new[] {FINDER_PAT_A, FINDER_PAT_A, FINDER_PAT_B, FINDER_PAT_B, FINDER_PAT_C, FINDER_PAT_C, FINDER_PAT_D, FINDER_PAT_E, FINDER_PAT_E},
            new[] {FINDER_PAT_A, FINDER_PAT_A, FINDER_PAT_B, FINDER_PAT_B, FINDER_PAT_C, FINDER_PAT_C, FINDER_PAT_D, FINDER_PAT_E, FINDER_PAT_F, FINDER_PAT_F},
            new[] {FINDER_PAT_A, FINDER_PAT_A, FINDER_PAT_B, FINDER_PAT_B, FINDER_PAT_C, FINDER_PAT_D, FINDER_PAT_D, FINDER_PAT_E, FINDER_PAT_E, FINDER_PAT_F, FINDER_PAT_F},
        };

        private const int MAX_PAIRS = 11;

        private readonly List<ExpandedPair> pairs = new List<ExpandedPair>(MAX_PAIRS);
        private readonly List<ExpandedRow> rows = new List<ExpandedRow>();
        private readonly int[] startEnd = new int[2];
        private bool startFromEven;

        internal List<ExpandedPair> Pairs
        {
            get { return pairs; }
        }

        /// <summary>
        ///   <p>Attempts to decode a one-dimensional barcode format given a single row of
        /// an image.</p>
        /// </summary>
        /// <param name="rowNumber">row number from top of the row</param>
        /// <param name="row">the black/white pixel data of the row</param>
        /// <param name="hints">decode hints</param>
        /// <returns>
        ///   <see cref="Result"/>containing encoded string and start/end of barcode or null, if an error occurs or barcode cannot be found
        /// </returns>
        override public Result decodeRow(int rowNumber,
            BitArray row,
            IDictionary<DecodeHintType, object> hints)
        {
            // Rows can start with even pattern in case in prev rows there where odd number of patters.
            // So lets try twice
            pairs.Clear();
            startFromEven = false;
            if (decodeRow2pairs(rowNumber, row))
                return constructResult(pairs);

            pairs.Clear();
            startFromEven = true;
            if (decodeRow2pairs(rowNumber, row))
                return constructResult(pairs);
            return null;
        }

        /// <summary>
        /// Resets this instance.
        /// </summary>
        public override void reset()
        {
            pairs.Clear();
            rows.Clear();
        }

        // Not private for testing
        internal bool decodeRow2pairs(int rowNumber, BitArray row)
        {
            while (true)
            {
                ExpandedPair nextPair = retrieveNextPair(row, this.pairs, rowNumber);
                if (nextPair == null)
                    break;
                pairs.Add(nextPair);
                // exit this loop when retrieveNextPair() fails and throws
            }
            if (pairs.Count == 0)
            {
                return false;
            }

            // TODO: verify sequence of finder patterns as in checkPairSequence()
            if (checkChecksum())
            {
                return true;
            }

            bool tryStackedDecode = rows.Count != 0;
            storeRow(rowNumber); // TODO: deal with reversed rows
            if (tryStackedDecode)
            {
                // When the image is 180-rotated, then rows are sorted in wrong direction.
                // Try twice with both the directions.
                List<ExpandedPair> ps = checkRows(false);
                if (ps != null)
                {
                    return true;
                }
                ps = checkRows(true);
                if (ps != null)
                {
                    return true;
                }
            }

            return false;
        }

        private List<ExpandedPair> checkRows(bool reverse)
        {
            // Limit number of rows we are checking
            // We use recursive algorithm with pure complexity and don't want it to take forever
            // Stacked barcode can have up to 11 rows, so 25 seems reasonable enough
            if (rows.Count > 25)
            {
                rows.Clear(); // We will never have a chance to get result, so clear it
                return null;
            }

            pairs.Clear();
            if (reverse)
            {
                rows.Reverse();
            }

            List<ExpandedPair> ps = checkRows(new List<ExpandedRow>(), 0);

            if (reverse)
            {
                rows.Reverse();
            }

            return ps;
        }

        // Try to construct a valid rows sequence
        // Recursion is used to implement backtracking
        private List<ExpandedPair> checkRows(List<ExpandedRow> collectedRows, int currentRow)
        {
            for (int i = currentRow; i < rows.Count; i++)
            {
                ExpandedRow row = rows[i];
                pairs.Clear();
                int size = collectedRows.Count;
                for (int j = 0; j < size; j++)
                {
                    pairs.AddRange(collectedRows[j].Pairs);
                }
                pairs.AddRange(row.Pairs);

                if (!isValidSequence(pairs))
                {
                    continue;
                }

                if (checkChecksum())
                {
                    return this.pairs;
                }

                var rs = new List<ExpandedRow>(collectedRows);
                rs.Add(row);
                // Recursion: try to add more rows
                var result = checkRows(rs, i + 1);
                if (result == null)
                    // We failed, try the next candidate
                    continue;
                return result;
            }

            return null;
        }

        // Whether the pairs form a valid find pattern sequence,
        // either complete or a prefix
        private static bool isValidSequence(List<ExpandedPair> pairs)
        {
            foreach (int[] sequence in FINDER_PATTERN_SEQUENCES)
            {
                if (pairs.Count > sequence.Length)
                {
                    continue;
                }

                bool stop = true;
                for (int j = 0; j < pairs.Count; j++)
                {
                    if (pairs[j].FinderPattern.Value != sequence[j])
                    {
                        stop = false;
                        break;
                    }
                }

                if (stop)
                {
                    return true;
                }
            }

            return false;
        }

        private void storeRow(int rowNumber)
        {
            // Discard if duplicate above or below; otherwise insert in order by row number.
            int insertPos = 0;
            bool prevIsSame = false;
            bool nextIsSame = false;
            while (insertPos < rows.Count)
            {
                ExpandedRow erow = rows[insertPos];
                if (erow.RowNumber > rowNumber)
                {
                    nextIsSame = erow.IsEquivalent(pairs);
                    break;
                }
                prevIsSame = erow.IsEquivalent(pairs);
                insertPos++;
            }
            if (nextIsSame || prevIsSame)
            {
                return;
            }

            // When the row was partially decoded (e.g. 2 pairs found instead of 3),
            // it will prevent us from detecting the barcode.
            // Try to merge partial rows

            // Check whether the row is part of an already detected row
            if (isPartialRow(pairs, rows))
            {
                return;
            }

            rows.Insert(insertPos, new ExpandedRow(pairs, rowNumber));

            removePartialRows(pairs, rows);
        }

        // Remove all the rows that contains only specified pairs 
        private static void removePartialRows(List<ExpandedPair> pairs, List<ExpandedRow> rows)
        {
            for (var index = 0; index < rows.Count; index++)
            {
                var r = rows[index];
                if (r.Pairs.Count != pairs.Count)
                {
                    bool allFound = true;
                    foreach (ExpandedPair p in r.Pairs)
                    {
                        if (!pairs.Contains(p))
                        {
                            allFound = false;
                            break;
                        }
                    }
                    if (allFound)
                    {
                        // 'pairs' contains all the pairs from the row 'r'
                        rows.RemoveAt(index);
                    }
                }
            }
        }

        // Returns true when one of the rows already contains all the pairs
        private static bool isPartialRow(IEnumerable<ExpandedPair> pairs, IEnumerable<ExpandedRow> rows)
        {
            foreach (ExpandedRow r in rows)
            {
                var allFound = true;
                foreach (ExpandedPair p in pairs)
                {
                    bool found = false;
                    foreach (ExpandedPair pp in r.Pairs)
                    {
                        if (p.Equals(pp))
                        {
                            found = true;
                            break;
                        }
                    }
                    if (!found)
                    {
                        allFound = false;
                        break;
                    }
                }
                if (allFound)
                {
                    // the row 'r' contain all the pairs from 'pairs'
                    return true;
                }
            }
            return false;
        }

        // Only used for unit testing
        internal List<ExpandedRow> Rows
        {
            get { return this.rows; }
        }

        internal static Result constructResult(List<ExpandedPair> pairs)
        {
            BitArray binary = BitArrayBuilder.buildBitArray(pairs);

            AbstractExpandedDecoder decoder = AbstractExpandedDecoder.createDecoder(binary);
            String resultingString = decoder.parseInformation();
            if (resultingString == null)
                return null;

            ResultPoint[] firstPoints = pairs[0].FinderPattern.ResultPoints;
            ResultPoint[] lastPoints = pairs[pairs.Count - 1].FinderPattern.ResultPoints;

            var result = new Result(
                resultingString,
                null,
                new ResultPoint[] { firstPoints[0], firstPoints[1], lastPoints[0], lastPoints[1] },
                BarcodeFormat.RSS_EXPANDED
            );
            result.putMetadata(ResultMetadataType.SYMBOLOGY_IDENTIFIER, "]e0");
            return result;
        }

        private bool checkChecksum()
        {
            ExpandedPair firstPair = pairs[0];
            DataCharacter checkCharacter = firstPair.LeftChar;
            DataCharacter firstCharacter = firstPair.RightChar;

            if (firstCharacter == null)
            {
                return false;
            }

            int checksum = firstCharacter.ChecksumPortion;
            int s = 2;

            for (int i = 1; i < pairs.Count; ++i)
            {
                ExpandedPair currentPair = pairs[i];
                checksum += currentPair.LeftChar.ChecksumPortion;
                s++;
                DataCharacter currentRightChar = currentPair.RightChar;
                if (currentRightChar != null)
                {
                    checksum += currentRightChar.ChecksumPortion;
                    s++;
                }
            }

            checksum %= 211;

            int checkCharacterValue = 211 * (s - 4) + checksum;

            return checkCharacterValue == checkCharacter.Value;
        }

        private static int getNextSecondBar(BitArray row, int initialPos)
        {
            int currentPos;
            if (row[initialPos])
            {
                currentPos = row.getNextUnset(initialPos);
                currentPos = row.getNextSet(currentPos);
            }
            else
            {
                currentPos = row.getNextSet(initialPos);
                currentPos = row.getNextUnset(currentPos);
            }
            return currentPos;
        }

        // not private for testing
        internal ExpandedPair retrieveNextPair(BitArray row, List<ExpandedPair> previousPairs, int rowNumber)
        {
            bool isOddPattern = previousPairs.Count % 2 == 0;
            if (startFromEven)
            {
                isOddPattern = !isOddPattern;
            }

            FinderPattern pattern;

            bool keepFinding = true;
            int forcedOffset = -1;
            do
            {
                if (!findNextPair(row, previousPairs, forcedOffset))
                    return null;
                pattern = parseFoundFinderPattern(row, rowNumber, isOddPattern);
                if (pattern == null)
                {
                    forcedOffset = getNextSecondBar(row, startEnd[0]);
                }
                else
                {
                    keepFinding = false;
                }
            } while (keepFinding);

            // When stacked symbol is split over multiple rows, there's no way to guess if this pair can be last or not.
            // bool mayBeLast;
            // if (!checkPairSequence(previousPairs, pattern, out mayBeLast))
            //   return null;

            DataCharacter leftChar = decodeDataCharacter(row, pattern, isOddPattern, true);
            if (leftChar == null)
                return null;

            if (previousPairs.Count != 0 &&
                previousPairs[previousPairs.Count - 1].MustBeLast)
            {
                return null;
            }

            DataCharacter rightChar = decodeDataCharacter(row, pattern, isOddPattern, false);

            return new ExpandedPair(leftChar, rightChar, pattern);
        }

        private bool findNextPair(BitArray row, List<ExpandedPair> previousPairs, int forcedOffset)
        {
            int[] counters = getDecodeFinderCounters();
            counters[0] = 0;
            counters[1] = 0;
            counters[2] = 0;
            counters[3] = 0;

            int width = row.Size;

            int rowOffset;
            if (forcedOffset >= 0)
            {
                rowOffset = forcedOffset;
            }
            else if (previousPairs.Count == 0)
            {
                rowOffset = 0;
            }
            else
            {
                ExpandedPair lastPair = previousPairs[previousPairs.Count - 1];
                rowOffset = lastPair.FinderPattern.StartEnd[1];
            }
            bool searchingEvenPair = previousPairs.Count % 2 != 0;
            if (startFromEven)
            {
                searchingEvenPair = !searchingEvenPair;
            }

            bool isWhite = false;
            while (rowOffset < width)
            {
                isWhite = !row[rowOffset];
                if (!isWhite)
                {
                    break;
                }
                rowOffset++;
            }

            int counterPosition = 0;
            int patternStart = rowOffset;
            for (int x = rowOffset; x < width; x++)
            {
                if (row[x] != isWhite)
                {
                    counters[counterPosition]++;
                }
                else
                {
                    if (counterPosition == 3)
                    {
                        if (searchingEvenPair)
                        {
                            reverseCounters(counters);
                        }

                        if (isFinderPattern(counters))
                        {
                            startEnd[0] = patternStart;
                            startEnd[1] = x;
                            return true;
                        }

                        if (searchingEvenPair)
                        {
                            reverseCounters(counters);
                        }

                        patternStart += counters[0] + counters[1];
                        counters[0] = counters[2];
                        counters[1] = counters[3];
                        counters[2] = 0;
                        counters[3] = 0;
                        counterPosition--;
                    }
                    else
                    {
                        counterPosition++;
                    }
                    counters[counterPosition] = 1;
                    isWhite = !isWhite;
                }
            }
            return false;
        }

        private static void reverseCounters(int[] counters)
        {
            int length = counters.Length;
            for (int i = 0; i < length / 2; ++i)
            {
                int tmp = counters[i];
                counters[i] = counters[length - i - 1];
                counters[length - i - 1] = tmp;
            }
        }

        private FinderPattern parseFoundFinderPattern(BitArray row, int rowNumber, bool oddPattern)
        {
            // Actually we found elements 2-5.
            int firstCounter;
            int start;
            int end;

            if (oddPattern)
            {
                // If pattern number is odd, we need to locate element 1 *before* the current block.

                int firstElementStart = startEnd[0] - 1;
                // Locate element 1
                while (firstElementStart >= 0 && !row[firstElementStart])
                {
                    firstElementStart--;
                }

                firstElementStart++;
                firstCounter = startEnd[0] - firstElementStart;
                start = firstElementStart;
                end = startEnd[1];

            }
            else
            {
                // If pattern number is even, the pattern is reversed, so we need to locate element 1 *after* the current block.
                start = startEnd[0];
                end = row.getNextUnset(startEnd[1] + 1);
                firstCounter = end - startEnd[1];
            }

            // Make 'counters' hold 1-4
            int[] counters = getDecodeFinderCounters();
            Array.Copy(counters, 0, counters, 1, counters.Length - 1);

            counters[0] = firstCounter;
            int value;
            if (!parseFinderValue(counters, FINDER_PATTERNS, out value))
                return null;

            return new FinderPattern(value, new int[] { start, end }, start, end, rowNumber);
        }

        internal DataCharacter decodeDataCharacter(BitArray row,
            FinderPattern pattern,
            bool isOddPattern,
            bool leftChar)
        {
            int[] counters = getDataCharacterCounters();
            SupportClass.Fill(counters, 0);

            if (leftChar)
            {
                if (!recordPatternInReverse(row, pattern.StartEnd[0], counters))
                    return null;
            }
            else
            {
                if (!recordPattern(row, pattern.StartEnd[1], counters))
                    return null;
                // reverse it
                for (int i = 0, j = counters.Length - 1; i < j; i++, j--)
                {
                    int temp = counters[i];
                    counters[i] = counters[j];
                    counters[j] = temp;
                }
            } //counters[] has the pixels of the module

            const int numModules = 17; //left and right data characters have all the same length
            float elementWidth = (float)ZXing.Common.Detector.MathUtils.sum(counters) / (float)numModules;

            // Sanity check: element width for pattern and the character should match
            float expectedElementWidth = (pattern.StartEnd[1] - pattern.StartEnd[0]) / 15.0f;
            if (Math.Abs(elementWidth - expectedElementWidth) / expectedElementWidth > 0.3f)
            {
                return null;
            }

            int[] oddCounts = getOddCounts();
            int[] evenCounts = getEvenCounts();
            float[] oddRoundingErrors = getOddRoundingErrors();
            float[] evenRoundingErrors = getEvenRoundingErrors();

            for (int i = 0; i < counters.Length; i++)
            {
                float divided = 1.0f * counters[i] / elementWidth;
                int rounded = (int)(divided + 0.5f); // Round
                if (rounded < 1)
                {
                    if (divided < 0.3f)
                    {
                        return null;
                    }
                    rounded = 1;
                }
                else if (rounded > 8)
                {
                    if (divided > 8.7f)
                    {
                        return null;
                    }
                    rounded = 8;
                }
                int offset = i >> 1;
                if ((i & 0x01) == 0)
                {
                    oddCounts[offset] = rounded;
                    oddRoundingErrors[offset] = divided - rounded;
                }
                else
                {
                    evenCounts[offset] = rounded;
                    evenRoundingErrors[offset] = divided - rounded;
                }
            }

            if (!adjustOddEvenCounts(numModules))
                return null;

            int weightRowNumber = 4 * pattern.Value + (isOddPattern ? 0 : 2) + (leftChar ? 0 : 1) - 1;

            int oddSum = 0;
            int oddChecksumPortion = 0;
            for (int i = oddCounts.Length - 1; i >= 0; i--)
            {
                if (isNotA1left(pattern, isOddPattern, leftChar))
                {
                    int weight = WEIGHTS[weightRowNumber][2 * i];
                    oddChecksumPortion += oddCounts[i] * weight;
                }
                oddSum += oddCounts[i];
            }
            int evenChecksumPortion = 0;
            for (int i = evenCounts.Length - 1; i >= 0; i--)
            {
                if (isNotA1left(pattern, isOddPattern, leftChar))
                {
                    int weight = WEIGHTS[weightRowNumber][2 * i + 1];
                    evenChecksumPortion += evenCounts[i] * weight;
                }
            }
            int checksumPortion = oddChecksumPortion + evenChecksumPortion;

            if ((oddSum & 0x01) != 0 || oddSum > 13 || oddSum < 4)
            {
                return null;
            }

            int group = (13 - oddSum) / 2;
            int oddWidest = SYMBOL_WIDEST[group];
            int evenWidest = 9 - oddWidest;
            int vOdd = RSSUtils.getRSSvalue(oddCounts, oddWidest, true);
            int vEven = RSSUtils.getRSSvalue(evenCounts, evenWidest, false);
            int tEven = EVEN_TOTAL_SUBSET[group];
            int gSum = GSUM[group];
            int value = vOdd * tEven + vEven + gSum;

            return new DataCharacter(value, checksumPortion);
        }

        private static bool isNotA1left(FinderPattern pattern, bool isOddPattern, bool leftChar)
        {
            // A1: pattern.getValue is 0 (A), and it's an oddPattern, and it is a left char
            return !(pattern.Value == 0 && isOddPattern && leftChar);
        }

        private bool adjustOddEvenCounts(int numModules)
        {
            int oddSum = ZXing.Common.Detector.MathUtils.sum(getOddCounts());
            int evenSum = ZXing.Common.Detector.MathUtils.sum(getEvenCounts());
            bool incrementOdd = false;
            bool decrementOdd = false;

            if (oddSum > 13)
            {
                decrementOdd = true;
            }
            else if (oddSum < 4)
            {
                incrementOdd = true;
            }
            bool incrementEven = false;
            bool decrementEven = false;
            if (evenSum > 13)
            {
                decrementEven = true;
            }
            else if (evenSum < 4)
            {
                incrementEven = true;
            }

            int mismatch = oddSum + evenSum - numModules;
            bool oddParityBad = (oddSum & 0x01) == 1;
            bool evenParityBad = (evenSum & 0x01) == 0;
            switch (mismatch)
            {
                case 1:
                    if (oddParityBad)
                    {
                        if (evenParityBad)
                        {
                            return false;
                        }
                        decrementOdd = true;
                    }
                    else
                    {
                        if (!evenParityBad)
                        {
                            return false;
                        }
                        decrementEven = true;
                    }
                    break;
                case -1:
                    if (oddParityBad)
                    {
                        if (evenParityBad)
                        {
                            return false;
                        }
                        incrementOdd = true;
                    }
                    else
                    {
                        if (!evenParityBad)
                        {
                            return false;
                        }
                        incrementEven = true;
                    }
                    break;
                case 0:
                    if (oddParityBad)
                    {
                        if (!evenParityBad)
                        {
                            return false;
                        }
                        // Both bad
                        if (oddSum < evenSum)
                        {
                            incrementOdd = true;
                            decrementEven = true;
                        }
                        else
                        {
                            decrementOdd = true;
                            incrementEven = true;
                        }
                    }
                    else
                    {
                        if (evenParityBad)
                        {
                            return false;
                        }
                        // Nothing to do!
                    }
                    break;
                default:
                    return false;
            }

            if (incrementOdd)
            {
                if (decrementOdd)
                {
                    return false;
                }
                increment(getOddCounts(), getOddRoundingErrors());
            }
            if (decrementOdd)
            {
                decrement(getOddCounts(), getOddRoundingErrors());
            }
            if (incrementEven)
            {
                if (decrementEven)
                {
                    return false;
                }
                increment(getEvenCounts(), getOddRoundingErrors());
            }
            if (decrementEven)
            {
                decrement(getEvenCounts(), getEvenRoundingErrors());
            }

            return true;
        }
    }
}
