/*
 * Copyright 2013 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.
 */

#import "ZXBitMatrix.h"
#import "ZXDecoderResult.h"
#import "ZXErrors.h"
#import "ZXIntArray.h"
#import "ZXPDF417BarcodeMetadata.h"
#import "ZXPDF417BarcodeValue.h"
#import "ZXPDF417BoundingBox.h"
#import "ZXPDF417Codeword.h"
#import "ZXPDF417CodewordDecoder.h"
#import "ZXPDF417Common.h"
#import "ZXPDF417DecodedBitStreamParser.h"
#import "ZXPDF417DetectionResult.h"
#import "ZXPDF417DetectionResultRowIndicatorColumn.h"
#import "ZXPDF417ECErrorCorrection.h"
#import "ZXPDF417ScanningDecoder.h"
#import "ZXResultPoint.h"

const int ZX_PDF417_CODEWORD_SKEW_SIZE = 2;

const int ZX_PDF417_MAX_ERRORS = 3;
const int ZX_PDF417_MAX_EC_CODEWORDS = 512;
static ZXPDF417ECErrorCorrection *errorCorrection;

@implementation ZXPDF417ScanningDecoder

+ (void)initialize {
  if ([self class] != [ZXPDF417ScanningDecoder class]) return;

  errorCorrection = [[ZXPDF417ECErrorCorrection alloc] init];
}

// TODO don't pass in minCodewordWidth and maxCodewordWidth, pass in barcode columns for start and stop pattern
// columns. That way width can be deducted from the pattern column.
// This approach also allows to detect more details about the barcode, e.g. if a bar type (white or black) is wider
// than it should be. This can happen if the scanner used a bad blackpoint.
+ (ZXDecoderResult *)decode:(ZXBitMatrix *)image
               imageTopLeft:(ZXResultPoint *)imageTopLeft
            imageBottomLeft:(ZXResultPoint *)imageBottomLeft
              imageTopRight:(ZXResultPoint *)imageTopRight
           imageBottomRight:(ZXResultPoint *)imageBottomRight
           minCodewordWidth:(int)minCodewordWidth
           maxCodewordWidth:(int)maxCodewordWidth
                      error:(NSError **)error {
  ZXPDF417BoundingBox *boundingBox = [[ZXPDF417BoundingBox alloc] initWithImage:image topLeft:imageTopLeft bottomLeft:imageBottomLeft topRight:imageTopRight bottomRight:imageBottomRight];
  ZXPDF417DetectionResultRowIndicatorColumn *leftRowIndicatorColumn;
  ZXPDF417DetectionResultRowIndicatorColumn *rightRowIndicatorColumn;
  ZXPDF417DetectionResult *detectionResult;
  for (int i = 0; i < 2; i++) {
    if (imageTopLeft) {
      leftRowIndicatorColumn = [self rowIndicatorColumn:image boundingBox:boundingBox startPoint:imageTopLeft leftToRight:YES
                                       minCodewordWidth:minCodewordWidth maxCodewordWidth:maxCodewordWidth];
    }
    if (imageTopRight) {
      rightRowIndicatorColumn = [self rowIndicatorColumn:image boundingBox:boundingBox startPoint:imageTopRight leftToRight:NO
                                        minCodewordWidth:minCodewordWidth maxCodewordWidth:maxCodewordWidth];
    }
    detectionResult = [self merge:leftRowIndicatorColumn rightRowIndicatorColumn:rightRowIndicatorColumn error:error];
    if (!detectionResult) {
      return nil;
    }
    if (i == 0 && detectionResult.boundingBox &&
        (detectionResult.boundingBox.minY < boundingBox.minY ||
         detectionResult.boundingBox.maxY > boundingBox.maxY)) {
      boundingBox = [detectionResult boundingBox];
    } else {
      detectionResult.boundingBox = boundingBox;
      break;
    }
  }
  int maxBarcodeColumn = detectionResult.barcodeColumnCount + 1;
  [detectionResult setDetectionResultColumn:0 detectionResultColumn:leftRowIndicatorColumn];
  [detectionResult setDetectionResultColumn:maxBarcodeColumn detectionResultColumn:rightRowIndicatorColumn];

  BOOL leftToRight = leftRowIndicatorColumn != nil;
  for (int barcodeColumnCount = 1; barcodeColumnCount <= maxBarcodeColumn; barcodeColumnCount++) {
    int barcodeColumn = leftToRight ? barcodeColumnCount : maxBarcodeColumn - barcodeColumnCount;
    if ([detectionResult detectionResultColumn:barcodeColumn]) {
      // This will be the case for the opposite row indicator column, which doesn't need to be decoded again.
      continue;
    }
    ZXPDF417DetectionResultColumn *detectionResultColumn;
    if (barcodeColumn == 0 || barcodeColumn == maxBarcodeColumn) {
      detectionResultColumn = [[ZXPDF417DetectionResultRowIndicatorColumn alloc] initWithBoundingBox:boundingBox isLeft:barcodeColumn == 0];
    } else {
      detectionResultColumn = [[ZXPDF417DetectionResultColumn alloc] initWithBoundingBox:boundingBox];
    }
    [detectionResult setDetectionResultColumn:barcodeColumn detectionResultColumn:detectionResultColumn];
    int startColumn = -1;
    int previousStartColumn = startColumn;
    // TODO start at a row for which we know the start position, then detect upwards and downwards from there.
    for (int imageRow = boundingBox.minY; imageRow <= boundingBox.maxY; imageRow++) {
      startColumn = [self startColumn:detectionResult barcodeColumn:barcodeColumn imageRow:imageRow leftToRight:leftToRight];
      if (startColumn < 0 || startColumn > boundingBox.maxX) {
        if (previousStartColumn == -1) {
          continue;
        }
        startColumn = previousStartColumn;
      }
      ZXPDF417Codeword *codeword = [self detectCodeword:image minColumn:boundingBox.minX maxColumn:boundingBox.maxX leftToRight:leftToRight
                                            startColumn:startColumn imageRow:imageRow minCodewordWidth:minCodewordWidth maxCodewordWidth:maxCodewordWidth];
      if (codeword) {
        [detectionResultColumn setCodeword:imageRow codeword:codeword];
        previousStartColumn = startColumn;
        minCodewordWidth = MIN(minCodewordWidth, codeword.width);
        maxCodewordWidth = MAX(maxCodewordWidth, codeword.width);
      }
    }
  }
  return [self createDecoderResult:detectionResult error:error];
}

+ (ZXPDF417DetectionResult *)merge:(ZXPDF417DetectionResultRowIndicatorColumn *)leftRowIndicatorColumn
           rightRowIndicatorColumn:(ZXPDF417DetectionResultRowIndicatorColumn *)rightRowIndicatorColumn
                             error:(NSError **)error {
  if (!leftRowIndicatorColumn && !rightRowIndicatorColumn) {
    return nil;
  }
  ZXPDF417BarcodeMetadata *barcodeMetadata = [self barcodeMetadata:leftRowIndicatorColumn rightRowIndicatorColumn:rightRowIndicatorColumn];
  if (!barcodeMetadata) {
    return nil;
  }
  ZXPDF417BoundingBox *leftBoundingBox, *rightBoundingBox;
  if (![self adjustBoundingBox:&leftBoundingBox rowIndicatorColumn:leftRowIndicatorColumn error:error]) {
    return nil;
  }
  if (![self adjustBoundingBox:&rightBoundingBox rowIndicatorColumn:rightRowIndicatorColumn error:error]) {
    return nil;
  }

  ZXPDF417BoundingBox *boundingBox = [ZXPDF417BoundingBox mergeLeftBox:leftBoundingBox rightBox:rightBoundingBox];
  if (!boundingBox) {
    if (error) *error = ZXNotFoundErrorInstance();
    return nil;
  }
  return [[ZXPDF417DetectionResult alloc] initWithBarcodeMetadata:barcodeMetadata boundingBox:boundingBox];
}

+ (BOOL)adjustBoundingBox:(ZXPDF417BoundingBox **)boundingBox
       rowIndicatorColumn:(ZXPDF417DetectionResultRowIndicatorColumn *)rowIndicatorColumn
                    error:(NSError **)error {
  if (!rowIndicatorColumn) {
    *boundingBox = nil;
    return YES;
  }
  ZXIntArray *rowHeights;
  if (![rowIndicatorColumn getRowHeights:&rowHeights]) {
    if (error) *error = ZXFormatErrorInstance();
    *boundingBox = nil;
    return NO;
  }
  if (!rowHeights) {
    *boundingBox = nil;
    return YES;
  }
  int maxRowHeight = [self max:rowHeights];
  int missingStartRows = 0;
  for (int i = 0; i < rowHeights.length; i++) {
    int rowHeight = rowHeights.array[i];
    missingStartRows += maxRowHeight - rowHeight;
    if (rowHeight > 0) {
      break;
    }
  }
  NSArray *codewords = rowIndicatorColumn.codewords;
  for (int row = 0; missingStartRows > 0 && codewords[row] == [NSNull null]; row++) {
    missingStartRows--;
  }
  int missingEndRows = 0;
  for (int row = rowHeights.length - 1; row >= 0; row--) {
    missingEndRows += maxRowHeight - rowHeights.array[row];
    if (rowHeights.array[row] > 0) {
      break;
    }
  }
  for (int row = (int)[codewords count] - 1; missingEndRows > 0 && codewords[row] == [NSNull null]; row--) {
    missingEndRows--;
  }
  *boundingBox = [rowIndicatorColumn.boundingBox addMissingRows:missingStartRows
                                                 missingEndRows:missingEndRows
                                                         isLeft:rowIndicatorColumn.isLeft];
  return *boundingBox != nil;
}

+ (int)max:(ZXIntArray *)values {
  int maxValue = -1;
  for (int i = 0; i < values.length; i++) {
    int value = values.array[i];
    maxValue = MAX(maxValue, value);
  }
  return maxValue;
}

+ (ZXPDF417BarcodeMetadata *)barcodeMetadata:(ZXPDF417DetectionResultRowIndicatorColumn *)leftRowIndicatorColumn
                     rightRowIndicatorColumn:(ZXPDF417DetectionResultRowIndicatorColumn *)rightRowIndicatorColumn {
  ZXPDF417BarcodeMetadata *leftBarcodeMetadata;
  if (!leftRowIndicatorColumn ||
      !(leftBarcodeMetadata = leftRowIndicatorColumn.barcodeMetadata)) {
    return rightRowIndicatorColumn ? rightRowIndicatorColumn.barcodeMetadata : nil;
  }
  ZXPDF417BarcodeMetadata *rightBarcodeMetadata;
  if (!rightRowIndicatorColumn ||
      !(rightBarcodeMetadata = rightRowIndicatorColumn.barcodeMetadata)) {
    return leftRowIndicatorColumn.barcodeMetadata;
  }

  if (leftBarcodeMetadata.columnCount != rightBarcodeMetadata.columnCount &&
      leftBarcodeMetadata.errorCorrectionLevel != rightBarcodeMetadata.errorCorrectionLevel &&
      leftBarcodeMetadata.rowCount != rightBarcodeMetadata.rowCount) {
    return nil;
  }
  return leftBarcodeMetadata;
}

+ (ZXPDF417DetectionResultRowIndicatorColumn *)rowIndicatorColumn:(ZXBitMatrix *)image
                                                      boundingBox:(ZXPDF417BoundingBox *)boundingBox
                                                       startPoint:(ZXResultPoint *)startPoint
                                                      leftToRight:(BOOL)leftToRight
                                                 minCodewordWidth:(int)minCodewordWidth
                                                 maxCodewordWidth:(int)maxCodewordWidth {
  ZXPDF417DetectionResultRowIndicatorColumn *rowIndicatorColumn = [[ZXPDF417DetectionResultRowIndicatorColumn alloc] initWithBoundingBox:boundingBox
                                                                                                                                  isLeft:leftToRight];
  for (int i = 0; i < 2; i++) {
    int increment = i == 0 ? 1 : -1;
    int startColumn = (int) startPoint.x;
    for (int imageRow = (int) startPoint.y; imageRow <= boundingBox.maxY &&
        imageRow >= boundingBox.minY; imageRow += increment) {
      ZXPDF417Codeword *codeword = [self detectCodeword:image minColumn:0 maxColumn:image.width leftToRight:leftToRight startColumn:startColumn imageRow:imageRow
                                       minCodewordWidth:minCodewordWidth maxCodewordWidth:maxCodewordWidth];
      if (codeword) {
        [rowIndicatorColumn setCodeword:imageRow codeword:codeword];
        if (leftToRight) {
          startColumn = codeword.startX;
        } else {
          startColumn = codeword.endX;
        }
      }
    }
  }
  return rowIndicatorColumn;
}

+ (BOOL)adjustCodewordCount:(ZXPDF417DetectionResult *)detectionResult barcodeMatrix:(NSArray *)barcodeMatrix {
  ZXIntArray *numberOfCodewords = [(ZXPDF417BarcodeValue *)barcodeMatrix[0][1] value];
  int calculatedNumberOfCodewords = [detectionResult barcodeColumnCount] * [detectionResult barcodeRowCount];
    [self numberOfECCodeWords:detectionResult.barcodeECLevel];
  if (numberOfCodewords.length == 0) {
    if (calculatedNumberOfCodewords < 1 || calculatedNumberOfCodewords > ZX_PDF417_MAX_CODEWORDS_IN_BARCODE) {
      return NO;
    }
    [(ZXPDF417BarcodeValue *)barcodeMatrix[0][1] setValue:calculatedNumberOfCodewords];
  } else if (numberOfCodewords.array[0] != calculatedNumberOfCodewords) {
    // The calculated one is more reliable as it is derived from the row indicator columns
    [(ZXPDF417BarcodeValue *)barcodeMatrix[0][1] setValue:calculatedNumberOfCodewords];
  }

  return YES;
}

+ (ZXDecoderResult *)createDecoderResult:(ZXPDF417DetectionResult *)detectionResult error:(NSError **)error {
  NSArray *barcodeMatrix = [self createBarcodeMatrix:detectionResult];
  if (!barcodeMatrix) {
    if (error) *error = ZXFormatErrorInstance();
    return nil;
  }
  if (![self adjustCodewordCount:detectionResult barcodeMatrix:barcodeMatrix]) {
    if (error) *error = ZXNotFoundErrorInstance();
    return nil;
  }
  NSMutableArray *erasures = [NSMutableArray array];
  ZXIntArray *codewords = [[ZXIntArray alloc] initWithLength:detectionResult.barcodeRowCount * detectionResult.barcodeColumnCount];
  NSMutableArray *ambiguousIndexValuesList = [NSMutableArray array];
  NSMutableArray *ambiguousIndexesList = [NSMutableArray array];
  for (int row = 0; row < detectionResult.barcodeRowCount; row++) {
    for (int column = 0; column < detectionResult.barcodeColumnCount; column++) {
      ZXIntArray *values = [(ZXPDF417BarcodeValue *)barcodeMatrix[row][column + 1] value];
      int codewordIndex = row * detectionResult.barcodeColumnCount + column;
      if (values.length == 0) {
        [erasures addObject:@(codewordIndex)];
      } else if (values.length == 1) {
        codewords.array[codewordIndex] = values.array[0];
      } else {
        [ambiguousIndexesList addObject:@(codewordIndex)];
        [ambiguousIndexValuesList addObject:values];
      }
    }
  }
  return [self createDecoderResultFromAmbiguousValues:detectionResult.barcodeECLevel
                                            codewords:codewords
                                         erasureArray:[ZXPDF417Common toIntArray:erasures]
                                     ambiguousIndexes:[ZXPDF417Common toIntArray:ambiguousIndexesList]
                                 ambiguousIndexValues:ambiguousIndexValuesList
                                                error:error];
}

/**
 * This method deals with the fact, that the decoding process doesn't always yield a single most likely value. The
 * current error correction implementation doesn't deal with erasures very well, so it's better to provide a value
 * for these ambiguous codewords instead of treating it as an erasure. The problem is that we don't know which of
 * the ambiguous values to choose. We try decode using the first value, and if that fails, we use another of the
 * ambiguous values and try to decode again. This usually only happens on very hard to read and decode barcodes,
 * so decoding the normal barcodes is not affected by this.
 *
 * @param erasureArray contains the indexes of erasures
 * @param ambiguousIndexes array with the indexes that have more than one most likely value
 * @param ambiguousIndexValues two dimensional array that contains the ambiguous values. The first dimension must
 * be the same length as the ambiguousIndexes array
 */
+ (ZXDecoderResult *)createDecoderResultFromAmbiguousValues:(int)ecLevel
                                                  codewords:(ZXIntArray *)codewords
                                               erasureArray:(ZXIntArray *)erasureArray
                                           ambiguousIndexes:(ZXIntArray *)ambiguousIndexes
                                       ambiguousIndexValues:(NSArray *)ambiguousIndexValues
                                                      error:(NSError **)error {
  ZXIntArray *ambiguousIndexCount = [[ZXIntArray alloc] initWithLength:ambiguousIndexes.length];

  int tries = 100;
  while (tries-- > 0) {
    for (int i = 0; i < ambiguousIndexCount.length; i++) {
      ZXIntArray *a = ambiguousIndexValues[i];
      codewords.array[ambiguousIndexes.array[i]] = a.array[(ambiguousIndexCount.array[i] + 1) % [(ZXIntArray *)ambiguousIndexValues[i] length]];
    }
    NSError *e;
    ZXDecoderResult *result = [self decodeCodewords:codewords ecLevel:ecLevel erasures:erasureArray error:&e];
    if (result) {
      return result;
    } else if (e.code != ZXChecksumError) {
      if (error) *error = e;
      return nil;
    }
    if (ambiguousIndexCount.length == 0) {
      if (error) *error = ZXChecksumErrorInstance();
      return nil;
    }
    for (int i = 0; i < ambiguousIndexCount.length; i++) {
      if (ambiguousIndexCount.array[i] < [(ZXIntArray *)ambiguousIndexValues[i] length] - 1) {
        ambiguousIndexCount.array[i]++;
        break;
      } else {
        ambiguousIndexCount.array[i] = 0;
        if (i == ambiguousIndexes.length - 1) {
          if (error) *error = ZXChecksumErrorInstance();
          return nil;
        }
      }
    }
  }

  if (error) *error = ZXChecksumErrorInstance();
  return nil;
}

+ (NSArray *)createBarcodeMatrix:(ZXPDF417DetectionResult *)detectionResult {
  NSMutableArray *barcodeMatrix = [NSMutableArray array];
  for (int row = 0; row < detectionResult.barcodeRowCount; row++) {
    [barcodeMatrix addObject:[NSMutableArray array]];
    for (int column = 0; column < detectionResult.barcodeColumnCount + 2; column++) {
      barcodeMatrix[row][column] = [[ZXPDF417BarcodeValue alloc] init];
    }
  }

  int column = 0;
  for (ZXPDF417DetectionResultColumn *detectionResultColumn in [detectionResult detectionResultColumns]) {
    if ((id)detectionResultColumn != [NSNull null]) {
      for (ZXPDF417Codeword *codeword in detectionResultColumn.codewords) {
        if ((id)codeword != [NSNull null]) {
          int rowNumber = codeword.rowNumber;
          if (rowNumber >= 0) {
            if (rowNumber >= barcodeMatrix.count) {
              return nil;
            }
            [(ZXPDF417BarcodeValue *)barcodeMatrix[rowNumber][column] setValue:codeword.value];
          }
        }
      }
    }
    column++;
  }

  return barcodeMatrix;
}

+ (BOOL)isValidBarcodeColumn:(ZXPDF417DetectionResult *)detectionResult barcodeColumn:(int)barcodeColumn {
  return barcodeColumn >= 0 && barcodeColumn <= detectionResult.barcodeColumnCount + 1;
}

+ (int)startColumn:(ZXPDF417DetectionResult *)detectionResult
     barcodeColumn:(int)barcodeColumn
          imageRow:(int)imageRow
       leftToRight:(BOOL)leftToRight {
  int offset = leftToRight ? 1 : -1;
  ZXPDF417Codeword *codeword;
  if ([self isValidBarcodeColumn:detectionResult barcodeColumn:barcodeColumn - offset]) {
    codeword = [[detectionResult detectionResultColumn:barcodeColumn - offset] codeword:imageRow];
  }
  if (codeword) {
    return leftToRight ? codeword.endX : codeword.startX;
  }
  codeword = [[detectionResult detectionResultColumn:barcodeColumn] codewordNearby:imageRow];
  if (codeword) {
    return leftToRight ? codeword.startX : codeword.endX;
  }
  if ([self isValidBarcodeColumn:detectionResult barcodeColumn:barcodeColumn - offset]) {
    codeword = [[detectionResult detectionResultColumn:barcodeColumn - offset] codewordNearby:imageRow];
  }
  if (codeword) {
    return leftToRight ? codeword.endX : codeword.startX;
  }
  int skippedColumns = 0;

  while ([self isValidBarcodeColumn:detectionResult barcodeColumn:barcodeColumn - offset]) {
    barcodeColumn -= offset;
    for (ZXPDF417Codeword *previousRowCodeword in [detectionResult detectionResultColumn:barcodeColumn].codewords) {
      if ((id)previousRowCodeword != [NSNull null]) {
        return (leftToRight ? previousRowCodeword.endX : previousRowCodeword.startX) +
          offset *
          skippedColumns *
          (previousRowCodeword.endX - previousRowCodeword.startX);
      }
    }
    skippedColumns++;
  }
  return leftToRight ? detectionResult.boundingBox.minX : detectionResult.boundingBox.maxX;
}

+ (ZXPDF417Codeword *)detectCodeword:(ZXBitMatrix *)image
                           minColumn:(int)minColumn
                           maxColumn:(int)maxColumn
                         leftToRight:(BOOL)leftToRight
                         startColumn:(int)startColumn
                            imageRow:(int)imageRow
                    minCodewordWidth:(int)minCodewordWidth
                    maxCodewordWidth:(int)maxCodewordWidth {
  startColumn = [self adjustCodewordStartColumn:image minColumn:minColumn maxColumn:maxColumn leftToRight:leftToRight codewordStartColumn:startColumn imageRow:imageRow];
  // we usually know fairly exact now how long a codeword is. We should provide minimum and maximum expected length
  // and try to adjust the read pixels, e.g. remove single pixel errors or try to cut off exceeding pixels.
  // min and maxCodewordWidth should not be used as they are calculated for the whole barcode an can be inaccurate
  // for the current position
  NSMutableArray *moduleBitCount = [self moduleBitCount:image minColumn:minColumn maxColumn:maxColumn leftToRight:leftToRight startColumn:startColumn imageRow:imageRow];
  if (!moduleBitCount) {
    return nil;
  }
  int endColumn;
  int codewordBitCount = [ZXPDF417Common bitCountSum:moduleBitCount];
  if (leftToRight) {
    endColumn = startColumn + codewordBitCount;
  } else {
    for (int i = 0; i < [moduleBitCount count] / 2; i++) {
      int tmpCount = [moduleBitCount[i] intValue];
      moduleBitCount[i] = moduleBitCount[[moduleBitCount count] - 1 - i];
      moduleBitCount[[moduleBitCount count] - 1 - i] = @(tmpCount);
    }
    endColumn = startColumn;
    startColumn = endColumn - codewordBitCount;
  }
  // TODO implement check for width and correction of black and white bars
  // use start (and maybe stop pattern) to determine if blackbars are wider than white bars. If so, adjust.
  // should probably done only for codewords with a lot more than 17 bits.
  // The following fixes 10-1.png, which has wide black bars and small white bars
  //    for (int i = 0; i < moduleBitCount.length; i++) {
  //      if (i % 2 == 0) {
  //        moduleBitCount[i]--;
  //      } else {
  //        moduleBitCount[i]++;
  //      }
  //    }

  // We could also use the width of surrounding codewords for more accurate results, but this seems
  // sufficient for now
  if (![self checkCodewordSkew:codewordBitCount minCodewordWidth:minCodewordWidth maxCodewordWidth:maxCodewordWidth]) {
    // We could try to use the startX and endX position of the codeword in the same column in the previous row,
    // create the bit count from it and normalize it to 8. This would help with single pixel errors.
    return nil;
  }

  int decodedValue = [ZXPDF417CodewordDecoder decodedValue:moduleBitCount];
  int codeword = [ZXPDF417Common codeword:decodedValue];
  if (codeword == -1) {
    return nil;
  }
  return [[ZXPDF417Codeword alloc] initWithStartX:startColumn endX:endColumn bucket:[self codewordBucketNumber:decodedValue] value:codeword];
}

+ (NSMutableArray *)moduleBitCount:(ZXBitMatrix *)image
                         minColumn:(int)minColumn
                         maxColumn:(int)maxColumn
                       leftToRight:(BOOL)leftToRight
                       startColumn:(int)startColumn
                          imageRow:(int)imageRow {
  int imageColumn = startColumn;
  NSMutableArray *moduleBitCount = [NSMutableArray arrayWithCapacity:8];
  for (int i = 0; i < 8; i++) {
    [moduleBitCount addObject:@0];
  }
  int moduleNumber = 0;
  int increment = leftToRight ? 1 : -1;
  BOOL previousPixelValue = leftToRight;
  while (((leftToRight && imageColumn < maxColumn) || (!leftToRight && imageColumn >= minColumn)) &&
      moduleNumber < [moduleBitCount count]) {
    if ([image getX:imageColumn y:imageRow] == previousPixelValue) {
      moduleBitCount[moduleNumber] = @([moduleBitCount[moduleNumber] intValue] + 1);
      imageColumn += increment;
    } else {
      moduleNumber++;
      previousPixelValue = !previousPixelValue;
    }
  }
  if (moduleNumber == [moduleBitCount count] ||
      (((leftToRight && imageColumn == maxColumn) || (!leftToRight && imageColumn == minColumn)) && moduleNumber == [moduleBitCount count] - 1)) {
    return moduleBitCount;
  }
  return nil;
}

+ (int)numberOfECCodeWords:(int)barcodeECLevel {
  return 2 << barcodeECLevel;
}

+ (int)adjustCodewordStartColumn:(ZXBitMatrix *)image
                       minColumn:(int)minColumn
                       maxColumn:(int)maxColumn
                     leftToRight:(BOOL)leftToRight
             codewordStartColumn:(int)codewordStartColumn
                        imageRow:(int)imageRow {
  int correctedStartColumn = codewordStartColumn;
  int increment = leftToRight ? -1 : 1;
  // there should be no black pixels before the start column. If there are, then we need to start earlier.
  for (int i = 0; i < 2; i++) {
    while (((leftToRight && correctedStartColumn >= minColumn) || (!leftToRight && correctedStartColumn < maxColumn)) &&
           leftToRight == [image getX:correctedStartColumn y:imageRow]) {
      if (abs(codewordStartColumn - correctedStartColumn) > ZX_PDF417_CODEWORD_SKEW_SIZE) {
        return codewordStartColumn;
      }
      correctedStartColumn += increment;
    }
    increment = -increment;
    leftToRight = !leftToRight;
  }
  return correctedStartColumn;
}

+ (BOOL)checkCodewordSkew:(int)codewordSize minCodewordWidth:(int)minCodewordWidth maxCodewordWidth:(int)maxCodewordWidth {
  return minCodewordWidth - ZX_PDF417_CODEWORD_SKEW_SIZE <= codewordSize &&
      codewordSize <= maxCodewordWidth + ZX_PDF417_CODEWORD_SKEW_SIZE;
}

+ (ZXDecoderResult *)decodeCodewords:(ZXIntArray *)codewords ecLevel:(int)ecLevel erasures:(ZXIntArray *)erasures error:(NSError **)error {
  if (codewords.length == 0) {
    if (error) *error = ZXFormatErrorInstance();
    return nil;
  }

  int numECCodewords = 1 << (ecLevel + 1);
  int correctedErrorsCount = [self correctErrors:codewords erasures:erasures numECCodewords:numECCodewords];
  if (correctedErrorsCount == -1) {
    if (error) *error = ZXChecksumErrorInstance();
    return nil;
  }
  if (![self verifyCodewordCount:codewords numECCodewords:numECCodewords]) {
    if (error) *error = ZXFormatErrorInstance();
    return nil;
  }

  // Decode the codewords
  ZXDecoderResult *decoderResult = [ZXPDF417DecodedBitStreamParser decode:codewords ecLevel:[@(ecLevel) stringValue] error:error];
  if (!decoderResult) {
    return nil;
  }
  decoderResult.errorsCorrected = @(correctedErrorsCount);
  decoderResult.erasures = @(erasures.length);
  return decoderResult;
}

/**
 * Given data and error-correction codewords received, possibly corrupted by errors, attempts to
 * correct the errors in-place.
 *
 * @param codewords   data and error correction codewords
 * @param erasures positions of any known erasures
 * @param numECCodewords number of error correction codewords that are available in codewords
 * @throws ChecksumException if error correction fails
 */
+ (int)correctErrors:(ZXIntArray *)codewords erasures:(ZXIntArray *)erasures numECCodewords:(int)numECCodewords {
  if (erasures &&
      (erasures.length > numECCodewords / 2 + ZX_PDF417_MAX_ERRORS ||
       numECCodewords < 0 ||
       numECCodewords > ZX_PDF417_MAX_EC_CODEWORDS)) {
    // Too many errors or EC Codewords is corrupted
    return -1;
  }
  return [errorCorrection decode:codewords numECCodewords:numECCodewords erasures:erasures];
}

/**
 * Verify that all is OK with the codeword array.
 */
+ (BOOL)verifyCodewordCount:(ZXIntArray *)codewords numECCodewords:(int)numECCodewords {
  if (codewords.length < 4) {
    // Codeword array size should be at least 4 allowing for
    // Count CW, At least one Data CW, Error Correction CW, Error Correction CW
    return NO;
  }
  // The first codeword, the Symbol Length Descriptor, shall always encode the total number of data
  // codewords in the symbol, including the Symbol Length Descriptor itself, data codewords and pad
  // codewords, but excluding the number of error correction codewords.
  int numberOfCodewords = codewords.array[0];
  if (numberOfCodewords > codewords.length) {
    return NO;
  }
  if (numberOfCodewords == 0) {
    // Reset to the length of the array - 8 (Allow for at least level 3 Error Correction (8 Error Codewords)
    if (numECCodewords < codewords.length) {
      codewords.array[0] = codewords.length - numECCodewords;
    } else {
      return NO;
    }
  }

  return YES;
}

+ (NSArray *)bitCountForCodeword:(int)codeword {
  NSMutableArray *result = [NSMutableArray array];
  for (int i = 0; i < 8; i++) {
    [result addObject:@0];
  }

  int previousValue = 0;
  int i = (int)[result count] - 1;
  while (YES) {
    if ((codeword & 0x1) != previousValue) {
      previousValue = codeword & 0x1;
      i--;
      if (i < 0) {
        break;
      }
    }
    result[i] = @([result[i] intValue] + 1);
    codeword >>= 1;
  }
  return result;
}

+ (int)codewordBucketNumber:(int)codeword {
  return [self codewordBucketNumberWithModuleBitCount:[self bitCountForCodeword:codeword]];
}

+ (int)codewordBucketNumberWithModuleBitCount:(NSArray *)moduleBitCount {
  return ([moduleBitCount[0] intValue] - [moduleBitCount[2] intValue] + [moduleBitCount[4] intValue] - [moduleBitCount[6] intValue] + 9) % 9;
}

- (NSString *)description:(NSArray *)barcodeMatrix {
  NSMutableString *result = [NSMutableString string];
  for (int row = 0; row < [barcodeMatrix count]; row++) {
    [result appendFormat:@"Row %2d: ", row];
    for (int column = 0; column < [(NSArray *)barcodeMatrix[row] count]; column++) {
      ZXPDF417BarcodeValue *barcodeValue = barcodeMatrix[row][column];
      if ([barcodeValue value].length == 0) {
        [result appendString:@"        "];
      } else {
        [result appendFormat:@"%4d(%2d)", [barcodeValue value].array[0],
         [[barcodeValue confidence:[barcodeValue value].array[0]] intValue]];
      }
    }
    [result appendString:@"\n"];
  }
  return [NSString stringWithString:result];
}

@end
