content: /*
   * Copyright 2009 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.
   */
  
  package com.ailiwean.core.zxing.core.oned.rss;
  
  import com.ailiwean.core.zxing.core.BarcodeFormat;
  import com.ailiwean.core.zxing.core.DecodeHintType;
  import com.ailiwean.core.zxing.core.NotFoundException;
  import com.ailiwean.core.zxing.core.Result;
  import com.ailiwean.core.zxing.core.ResultPoint;
  import com.ailiwean.core.zxing.core.ResultPointCallback;
  import com.ailiwean.core.zxing.core.common.BitArray;
  import com.ailiwean.core.zxing.core.common.detector.MathUtils;
  
  import java.util.Arrays;
  import java.util.ArrayList;
  import java.util.Collection;
  import java.util.List;
  import java.util.Map;
  
  /**
   * Decodes RSS-14, including truncated and stacked variants. See ISO/IEC 24724:2006.
   */
  public final class RSS14Reader extends AbstractRSSReader {
  
    private static final int[] OUTSIDE_EVEN_TOTAL_SUBSET = {1,10,34,70,126};
    private static final int[] INSIDE_ODD_TOTAL_SUBSET = {4,20,48,81};
    private static final int[] OUTSIDE_GSUM = {0,161,961,2015,2715};
    private static final int[] INSIDE_GSUM = {0,336,1036,1516};
    private static final int[] OUTSIDE_ODD_WIDEST = {8,6,4,3,1};
    private static final int[] INSIDE_ODD_WIDEST = {2,4,6,8};
  
    private static final int[][] FINDER_PATTERNS = {
        {3,8,2,1},
        {3,5,5,1},
        {3,3,7,1},
        {3,1,9,1},
        {2,7,4,1},
        {2,5,6,1},
        {2,3,8,1},
        {1,5,7,1},
        {1,3,9,1},
    };
  
    private final List<Pair> possibleLeftPairs;
    private final List<Pair> possibleRightPairs;
  
    public RSS14Reader() {
      possibleLeftPairs = new ArrayList<>();
      possibleRightPairs = new ArrayList<>();
    }
  
    @Override
    public Result decodeRow(int rowNumber,
                            BitArray row,
                            Map<DecodeHintType,?> hints) throws NotFoundException {
      Pair leftPair = decodePair(row, false, rowNumber, hints);
      addOrTally(possibleLeftPairs, leftPair);
      row.reverse();
      Pair rightPair = decodePair(row, true, rowNumber, hints);
      addOrTally(possibleRightPairs, rightPair);
      row.reverse();
      for (Pair left : possibleLeftPairs) {
        if (left.getCount() > 1) {
          for (Pair right : possibleRightPairs) {
            if (right.getCount() > 1 && checkChecksum(left, right)) {
              return constructResult(left, right);
            }
          }
        }
      }
      throw NotFoundException.getNotFoundInstance();
    }
  
    private static void addOrTally(Collection<Pair> possiblePairs, Pair pair) {
      if (pair == null) {
        return;
      }
      boolean found = false;
      for (Pair other : possiblePairs) {
        if (other.getValue() == pair.getValue()) {
          other.incrementCount();
          found = true;
          break;
        }
      }
      if (!found) {
        possiblePairs.add(pair);
      }
    }
  
    @Override
    public void reset() {
      possibleLeftPairs.clear();
      possibleRightPairs.clear();
    }
  
    private static Result constructResult(Pair leftPair, Pair rightPair) {
      long symbolValue = 4537077L * leftPair.getValue() + rightPair.getValue();
      String text = String.valueOf(symbolValue);
  
      StringBuilder buffer = new StringBuilder(14);
      for (int i = 13 - text.length(); i > 0; i--) {
        buffer.append('0');
      }
      buffer.append(text);
  
      int checkDigit = 0;
      for (int i = 0; i < 13; i++) {
        int digit = buffer.charAt(i) - '0';
        checkDigit += (i & 0x01) == 0 ? 3 * digit : digit;
      }
      checkDigit = 10 - (checkDigit % 10);
      if (checkDigit == 10) {
        checkDigit = 0;
      }
      buffer.append(checkDigit);
  
      ResultPoint[] leftPoints = leftPair.getFinderPattern().getResultPoints();
      ResultPoint[] rightPoints = rightPair.getFinderPattern().getResultPoints();
      return new Result(
          buffer.toString(),
          null,
          new ResultPoint[] { leftPoints[0], leftPoints[1], rightPoints[0], rightPoints[1], },
          BarcodeFormat.RSS_14);
    }
  
    private static boolean checkChecksum(Pair leftPair, Pair rightPair) {
      //int leftFPValue = leftPair.getFinderPattern().getValue();
      //int rightFPValue = rightPair.getFinderPattern().getValue();
      //if ((leftFPValue == 0 && rightFPValue == 8) ||
      //    (leftFPValue == 8 && rightFPValue == 0)) {
      //}
      int checkValue = (leftPair.getChecksumPortion() + 16 * rightPair.getChecksumPortion()) % 79;
      int targetCheckValue =
          9 * leftPair.getFinderPattern().getValue() + rightPair.getFinderPattern().getValue();
      if (targetCheckValue > 72) {
        targetCheckValue--;
      }
      if (targetCheckValue > 8) {
        targetCheckValue--;
      }
      return checkValue == targetCheckValue;
    }
  
    private Pair decodePair(BitArray row, boolean right, int rowNumber, Map<DecodeHintType,?> hints) {
      try {
        int[] startEnd = findFinderPattern(row, right);
        FinderPattern pattern = parseFoundFinderPattern(row, rowNumber, right, startEnd);
  
        ResultPointCallback resultPointCallback = hints == null ? null :
          (ResultPointCallback) hints.get(DecodeHintType.NEED_RESULT_POINT_CALLBACK);
  
        if (resultPointCallback != null) {
          startEnd = pattern.getStartEnd();
          float center = (startEnd[0] + startEnd[1] - 1) / 2.0f;
          if (right) {
            // row is actually reversed
            center = row.getSize() - 1 - center;
          }
          resultPointCallback.foundPossibleResultPoint(new ResultPoint(center, rowNumber));
        }
  
        DataCharacter outside = decodeDataCharacter(row, pattern, true);
        DataCharacter inside = decodeDataCharacter(row, pattern, false);
        return new Pair(1597 * outside.getValue() + inside.getValue(),
                        outside.getChecksumPortion() + 4 * inside.getChecksumPortion(),
                        pattern);
      } catch (NotFoundException ignored) {
        return null;
      }
    }
  
    private DataCharacter decodeDataCharacter(BitArray row, FinderPattern pattern, boolean outsideChar)
        throws NotFoundException {
  
      int[] counters = getDataCharacterCounters();
      Arrays.fill(counters, 0);
  
      if (outsideChar) {
        recordPatternInReverse(row, pattern.getStartEnd()[0], counters);
      } else {
        recordPattern(row, pattern.getStartEnd()[1], counters);
        // 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;
        }
      }
  
      int numModules = outsideChar ? 16 : 15;
      float elementWidth = MathUtils.sum(counters) / (float) numModules;
  
      int[] oddCounts = this.getOddCounts();
      int[] evenCounts = this.getEvenCounts();
      float[] oddRoundingErrors = this.getOddRoundingErrors();
      float[] evenRoundingErrors = this.getEvenRoundingErrors();
  
      for (int i = 0; i < counters.length; i++) {
        float value = counters[i] / elementWidth;
        int count = (int) (value + 0.5f); // Round
        if (count < 1) {
          count = 1;
        } else if (count > 8) {
          count = 8;
        }
        int offset = i / 2;
        if ((i & 0x01) == 0) {
          oddCounts[offset] = count;
          oddRoundingErrors[offset] = value - count;
        } else {
          evenCounts[offset] = count;
          evenRoundingErrors[offset] = value - count;
        }
      }
  
      adjustOddEvenCounts(outsideChar, numModules);
  
      int oddSum = 0;
      int oddChecksumPortion = 0;
      for (int i = oddCounts.length - 1; i >= 0; i--) {
        oddChecksumPortion *= 9;
        oddChecksumPortion += oddCounts[i];
        oddSum += oddCounts[i];
      }
      int evenChecksumPortion = 0;
      int evenSum = 0;
      for (int i = evenCounts.length - 1; i >= 0; i--) {
        evenChecksumPortion *= 9;
        evenChecksumPortion += evenCounts[i];
        evenSum += evenCounts[i];
      }
      int checksumPortion = oddChecksumPortion + 3 * evenChecksumPortion;
  
      if (outsideChar) {
        if ((oddSum & 0x01) != 0 || oddSum > 12 || oddSum < 4) {
          throw NotFoundException.getNotFoundInstance();
        }
        int group = (12 - oddSum) / 2;
        int oddWidest = OUTSIDE_ODD_WIDEST[group];
        int evenWidest = 9 - oddWidest;
        int vOdd = RSSUtils.getRSSvalue(oddCounts, oddWidest, false);
        int vEven = RSSUtils.getRSSvalue(evenCounts, evenWidest, true);
        int tEven = OUTSIDE_EVEN_TOTAL_SUBSET[group];
        int gSum = OUTSIDE_GSUM[group];
        return new DataCharacter(vOdd * tEven + vEven + gSum, checksumPortion);
      } else {
        if ((evenSum & 0x01) != 0 || evenSum > 10 || evenSum < 4) {
          throw NotFoundException.getNotFoundInstance();
        }
        int group = (10 - evenSum) / 2;
        int oddWidest = INSIDE_ODD_WIDEST[group];
        int evenWidest = 9 - oddWidest;
        int vOdd = RSSUtils.getRSSvalue(oddCounts, oddWidest, true);
        int vEven = RSSUtils.getRSSvalue(evenCounts, evenWidest, false);
        int tOdd = INSIDE_ODD_TOTAL_SUBSET[group];
        int gSum = INSIDE_GSUM[group];
        return new DataCharacter(vEven * tOdd + vOdd + gSum, checksumPortion);
      }
  
    }
  
    private int[] findFinderPattern(BitArray row, boolean rightFinderPattern)
        throws NotFoundException {
  
      int[] counters = getDecodeFinderCounters();
      counters[0] = 0;
      counters[1] = 0;
      counters[2] = 0;
      counters[3] = 0;
  
      int width = row.getSize();
      boolean isWhite = false;
      int rowOffset = 0;
      while (rowOffset < width) {
        isWhite = !row.get(rowOffset);
        if (rightFinderPattern == isWhite) {
          // Will encounter white first when searching for right finder pattern
          break;
        }
        rowOffset++;
      }
  
      int counterPosition = 0;
      int patternStart = rowOffset;
      for (int x = rowOffset; x < width; x++) {
        if (row.get(x) != isWhite) {
          counters[counterPosition]++;
        } else {
          if (counterPosition == 3) {
            if (isFinderPattern(counters)) {
              return new int[]{patternStart, x};
            }
            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;
        }
      }
      throw NotFoundException.getNotFoundInstance();
  
    }
  
    private FinderPattern parseFoundFinderPattern(BitArray row, int rowNumber, boolean right, int[] startEnd)
        throws NotFoundException {
      // Actually we found elements 2-5
      boolean firstIsBlack = row.get(startEnd[0]);
      int firstElementStart = startEnd[0] - 1;
      // Locate element 1
      while (firstElementStart >= 0 && firstIsBlack != row.get(firstElementStart)) {
        firstElementStart--;
      }
      firstElementStart++;
      int firstCounter = startEnd[0] - firstElementStart;
      // Make 'counters' hold 1-4
      int[] counters = getDecodeFinderCounters();
      System.arraycopy(counters, 0, counters, 1, counters.length - 1);
      counters[0] = firstCounter;
      int value = parseFinderValue(counters, FINDER_PATTERNS);
      int start = firstElementStart;
      int end = startEnd[1];
      if (right) {
        // row is actually reversed
        start = row.getSize() - 1 - start;
        end = row.getSize() - 1 - end;
      }
      return new FinderPattern(value, new int[] {firstElementStart, startEnd[1]}, start, end, rowNumber);
    }
  
    private void adjustOddEvenCounts(boolean outsideChar, int numModules) throws NotFoundException {
  
      int oddSum = MathUtils.sum(getOddCounts());
      int evenSum = MathUtils.sum(getEvenCounts());
  
      boolean incrementOdd = false;
      boolean decrementOdd = false;
      boolean incrementEven = false;
      boolean decrementEven = false;
  
      if (outsideChar) {
        if (oddSum > 12) {
          decrementOdd = true;
        } else if (oddSum < 4) {
          incrementOdd = true;
        }
        if (evenSum > 12) {
          decrementEven = true;
        } else if (evenSum < 4) {
          incrementEven = true;
        }
      } else {
        if (oddSum > 11) {
          decrementOdd = true;
        } else if (oddSum < 5) {
          incrementOdd = true;
        }
        if (evenSum > 10) {
          decrementEven = true;
        } else if (evenSum < 4) {
          incrementEven = true;
        }
      }
  
      int mismatch = oddSum + evenSum - numModules;
      boolean oddParityBad = (oddSum & 0x01) == (outsideChar ? 1 : 0);
      boolean evenParityBad = (evenSum & 0x01) == 1;
      /*if (mismatch == 2) {
        if (!(oddParityBad && evenParityBad)) {
          throw ReaderException.getInstance();
        }
        decrementOdd = true;
        decrementEven = true;
      } else if (mismatch == -2) {
        if (!(oddParityBad && evenParityBad)) {
          throw ReaderException.getInstance();
        }
        incrementOdd = true;
        incrementEven = true;
      } else */
      switch (mismatch) {
        case 1:
          if (oddParityBad) {
            if (evenParityBad) {
              throw NotFoundException.getNotFoundInstance();
            }
            decrementOdd = true;
          } else {
            if (!evenParityBad) {
              throw NotFoundException.getNotFoundInstance();
            }
            decrementEven = true;
          }
          break;
        case -1:
          if (oddParityBad) {
            if (evenParityBad) {
              throw NotFoundException.getNotFoundInstance();
            }
            incrementOdd = true;
          } else {
            if (!evenParityBad) {
              throw NotFoundException.getNotFoundInstance();
            }
            incrementEven = true;
          }
          break;
        case 0:
          if (oddParityBad) {
            if (!evenParityBad) {
              throw NotFoundException.getNotFoundInstance();
            }
            // Both bad
            if (oddSum < evenSum) {
              incrementOdd = true;
              decrementEven = true;
            } else {
              decrementOdd = true;
              incrementEven = true;
            }
          } else {
            if (evenParityBad) {
              throw NotFoundException.getNotFoundInstance();
            }
            // Nothing to do!
          }
          break;
        default:
          throw NotFoundException.getNotFoundInstance();
      }
  
      if (incrementOdd) {
        if (decrementOdd) {
          throw NotFoundException.getNotFoundInstance();
        }
        increment(getOddCounts(), getOddRoundingErrors());
      }
      if (decrementOdd) {
        decrement(getOddCounts(), getOddRoundingErrors());
      }
      if (incrementEven) {
        if (decrementEven) {
          throw NotFoundException.getNotFoundInstance();
        }
        increment(getEvenCounts(), getOddRoundingErrors());
      }
      if (decrementEven) {
        decrement(getEvenCounts(), getEvenRoundingErrors());
      }
  
    }
  
  }
  