// Copyright 2014 PDFium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

// Original code copyright 2014 Foxit Software Inc. http://www.foxitsoftware.com
// Original code is licensed as follows:
/*
 * 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.
 */

#include "xfa/fxbarcode/pdf417/BC_PDF417BarcodeMetadata.h"
#include "xfa/fxbarcode/pdf417/BC_PDF417BoundingBox.h"
#include "xfa/fxbarcode/pdf417/BC_PDF417Codeword.h"
#include "xfa/fxbarcode/pdf417/BC_PDF417Common.h"
#include "xfa/fxbarcode/pdf417/BC_PDF417DetectionResult.h"
#include "xfa/fxbarcode/pdf417/BC_PDF417DetectionResultColumn.h"
#include "xfa/fxbarcode/pdf417/BC_PDF417DetectionResultRowIndicatorColumn.h"

int32_t CBC_DetectionResult::ADJUST_ROW_NUMBER_SKIP = 2;

CBC_DetectionResult::CBC_DetectionResult(CBC_BarcodeMetadata* barcodeMetadata,
                                         CBC_BoundingBox* boundingBox) {
  m_barcodeMetadata = barcodeMetadata;
  m_barcodeColumnCount = barcodeMetadata->getColumnCount();
  m_boundingBox = boundingBox;
  m_detectionResultColumns.SetSize(m_barcodeColumnCount + 2);
  for (int32_t i = 0; i < m_barcodeColumnCount + 2; i++) {
    m_detectionResultColumns[i] = NULL;
  }
}
CBC_DetectionResult::~CBC_DetectionResult() {
  delete m_boundingBox;
  delete m_barcodeMetadata;
  m_detectionResultColumns.RemoveAll();
}
CFX_PtrArray& CBC_DetectionResult::getDetectionResultColumns() {
  adjustIndicatorColumnRowNumbers(
      (CBC_DetectionResultColumn*)m_detectionResultColumns.GetAt(0));
  adjustIndicatorColumnRowNumbers(
      (CBC_DetectionResultColumn*)m_detectionResultColumns.GetAt(
          m_barcodeColumnCount + 1));
  int32_t unadjustedCodewordCount = CBC_PDF417Common::MAX_CODEWORDS_IN_BARCODE;
  int32_t previousUnadjustedCount;
  do {
    previousUnadjustedCount = unadjustedCodewordCount;
    unadjustedCodewordCount = adjustRowNumbers();
  } while (unadjustedCodewordCount > 0 &&
           unadjustedCodewordCount < previousUnadjustedCount);
  return m_detectionResultColumns;
}
void CBC_DetectionResult::setBoundingBox(CBC_BoundingBox* boundingBox) {
  m_boundingBox = boundingBox;
}
CBC_BoundingBox* CBC_DetectionResult::getBoundingBox() {
  return m_boundingBox;
}
void CBC_DetectionResult::setDetectionResultColumn(
    int32_t barcodeColumn,
    CBC_DetectionResultColumn* detectionResultColumn) {
  m_detectionResultColumns[barcodeColumn] = detectionResultColumn;
}
CBC_DetectionResultColumn* CBC_DetectionResult::getDetectionResultColumn(
    int32_t barcodeColumn) {
  return (CBC_DetectionResultColumn*)m_detectionResultColumns[barcodeColumn];
}
CFX_ByteString CBC_DetectionResult::toString() {
  CBC_DetectionResultColumn* rowIndicatorColumn =
      (CBC_DetectionResultColumn*)m_detectionResultColumns[0];
  if (rowIndicatorColumn == NULL) {
    rowIndicatorColumn = (CBC_DetectionResultColumn*)
        m_detectionResultColumns[m_barcodeColumnCount + 1];
  }
  CFX_ByteString result;
  for (int32_t codewordsRow = 0;
       codewordsRow < rowIndicatorColumn->getCodewords()->GetSize();
       codewordsRow++) {
    result += (FX_CHAR)codewordsRow;
    for (int32_t barcodeColumn = 0; barcodeColumn < m_barcodeColumnCount + 2;
         barcodeColumn++) {
      if (m_detectionResultColumns[barcodeColumn] == NULL) {
        result += "    |   ";
        continue;
      }
      CBC_Codeword* codeword =
          (CBC_Codeword*)((CBC_DetectionResultColumn*)
                              m_detectionResultColumns[barcodeColumn])
              ->getCodewords()
              ->GetAt(codewordsRow);
      if (codeword == NULL) {
        result += "    |   ";
        continue;
      }
      result += codeword->getRowNumber();
      result += codeword->getValue();
    }
  }
  return result;
}
void CBC_DetectionResult::adjustIndicatorColumnRowNumbers(
    CBC_DetectionResultColumn* detectionResultColumn) {
  if (detectionResultColumn) {
    ((CBC_DetectionResultRowIndicatorColumn*)detectionResultColumn)
        ->adjustCompleteIndicatorColumnRowNumbers(*m_barcodeMetadata);
  }
}
int32_t CBC_DetectionResult::adjustRowNumbers() {
  int32_t unadjustedCount = adjustRowNumbersByRow();
  if (unadjustedCount == 0) {
    return 0;
  }
  for (int32_t barcodeColumn = 1; barcodeColumn < m_barcodeColumnCount + 1;
       barcodeColumn++) {
    CFX_PtrArray* codewords =
        ((CBC_DetectionResultColumn*)m_detectionResultColumns[barcodeColumn])
            ->getCodewords();
    for (int32_t codewordsRow = 0; codewordsRow < codewords->GetSize();
         codewordsRow++) {
      if (codewords->GetAt(codewordsRow) == NULL) {
        continue;
      }
      if (!((CBC_Codeword*)codewords->GetAt(codewordsRow))
               ->hasValidRowNumber()) {
        adjustRowNumbers(barcodeColumn, codewordsRow, codewords);
      }
    }
  }
  return unadjustedCount;
}
int32_t CBC_DetectionResult::adjustRowNumbersByRow() {
  adjustRowNumbersFromBothRI();
  int32_t unadjustedCount = adjustRowNumbersFromLRI();
  return unadjustedCount + adjustRowNumbersFromRRI();
}
int32_t CBC_DetectionResult::adjustRowNumbersFromBothRI() {
  if (m_detectionResultColumns[0] == NULL ||
      m_detectionResultColumns[m_barcodeColumnCount + 1] == NULL) {
    return 0;
  }
  CFX_PtrArray* LRIcodewords =
      ((CBC_DetectionResultColumn*)m_detectionResultColumns[0])->getCodewords();
  CFX_PtrArray* RRIcodewords =
      ((CBC_DetectionResultColumn*)
           m_detectionResultColumns[m_barcodeColumnCount + 1])
          ->getCodewords();
  for (int32_t codewordsRow = 0; codewordsRow < LRIcodewords->GetSize();
       codewordsRow++) {
    if (LRIcodewords->GetAt(codewordsRow) &&
        RRIcodewords->GetAt(codewordsRow) &&
        ((CBC_Codeword*)LRIcodewords->GetAt(codewordsRow))->getRowNumber() ==
            ((CBC_Codeword*)RRIcodewords->GetAt(codewordsRow))
                ->getRowNumber()) {
      for (int32_t barcodeColumn = 1; barcodeColumn <= m_barcodeColumnCount;
           barcodeColumn++) {
        CBC_Codeword* codeword =
            (CBC_Codeword*)((CBC_DetectionResultColumn*)
                                m_detectionResultColumns[barcodeColumn])
                ->getCodewords()
                ->GetAt(codewordsRow);
        if (codeword == NULL) {
          continue;
        }
        codeword->setRowNumber(
            ((CBC_Codeword*)LRIcodewords->GetAt(codewordsRow))->getRowNumber());
        if (!codeword->hasValidRowNumber()) {
          ((CBC_DetectionResultColumn*)m_detectionResultColumns[barcodeColumn])
              ->getCodewords()
              ->SetAt(codewordsRow, NULL);
        }
      }
    }
  }
  return 0;
}
int32_t CBC_DetectionResult::adjustRowNumbersFromRRI() {
  if (m_detectionResultColumns[m_barcodeColumnCount + 1] == NULL) {
    return 0;
  }
  int32_t unadjustedCount = 0;
  CFX_PtrArray* codewords =
      ((CBC_DetectionResultColumn*)m_detectionResultColumns.GetAt(
           m_barcodeColumnCount + 1))
          ->getCodewords();
  for (int32_t codewordsRow = 0; codewordsRow < codewords->GetSize();
       codewordsRow++) {
    if (codewords->GetAt(codewordsRow) == NULL) {
      continue;
    }
    int32_t rowIndicatorRowNumber =
        ((CBC_Codeword*)codewords->GetAt(codewordsRow))->getRowNumber();
    int32_t invalidRowCounts = 0;
    for (int32_t barcodeColumn = m_barcodeColumnCount + 1;
         barcodeColumn > 0 && invalidRowCounts < ADJUST_ROW_NUMBER_SKIP;
         barcodeColumn--) {
      CBC_Codeword* codeword =
          (CBC_Codeword*)((CBC_DetectionResultColumn*)
                              m_detectionResultColumns.GetAt(barcodeColumn))
              ->getCodewords()
              ->GetAt(codewordsRow);
      if (codeword) {
        invalidRowCounts = adjustRowNumberIfValid(rowIndicatorRowNumber,
                                                  invalidRowCounts, codeword);
        if (!codeword->hasValidRowNumber()) {
          unadjustedCount++;
        }
      }
    }
  }
  return unadjustedCount;
}
int32_t CBC_DetectionResult::adjustRowNumbersFromLRI() {
  if (m_detectionResultColumns[0] == NULL) {
    return 0;
  }
  int32_t unadjustedCount = 0;
  CFX_PtrArray* codewords =
      ((CBC_DetectionResultColumn*)m_detectionResultColumns.GetAt(0))
          ->getCodewords();
  for (int32_t codewordsRow = 0; codewordsRow < codewords->GetSize();
       codewordsRow++) {
    if (codewords->GetAt(codewordsRow) == NULL) {
      continue;
    }
    int32_t rowIndicatorRowNumber =
        ((CBC_Codeword*)codewords->GetAt(codewordsRow))->getRowNumber();
    int32_t invalidRowCounts = 0;
    for (int32_t barcodeColumn = 1; barcodeColumn < m_barcodeColumnCount + 1 &&
                                    invalidRowCounts < ADJUST_ROW_NUMBER_SKIP;
         barcodeColumn++) {
      CBC_Codeword* codeword =
          (CBC_Codeword*)((CBC_DetectionResultColumn*)
                              m_detectionResultColumns[barcodeColumn])
              ->getCodewords()
              ->GetAt(codewordsRow);
      if (codeword) {
        invalidRowCounts = adjustRowNumberIfValid(rowIndicatorRowNumber,
                                                  invalidRowCounts, codeword);
        if (!codeword->hasValidRowNumber()) {
          unadjustedCount++;
        }
      }
    }
  }
  return unadjustedCount;
}
int32_t CBC_DetectionResult::adjustRowNumberIfValid(
    int32_t rowIndicatorRowNumber,
    int32_t invalidRowCounts,
    CBC_Codeword* codeword) {
  if (codeword == NULL) {
    return invalidRowCounts;
  }
  if (!codeword->hasValidRowNumber()) {
    if (codeword->isValidRowNumber(rowIndicatorRowNumber)) {
      codeword->setRowNumber(rowIndicatorRowNumber);
      invalidRowCounts = 0;
    } else {
      ++invalidRowCounts;
    }
  }
  return invalidRowCounts;
}
void CBC_DetectionResult::adjustRowNumbers(int32_t barcodeColumn,
                                           int32_t codewordsRow,
                                           CFX_PtrArray* codewords) {
  CBC_Codeword* codeword = (CBC_Codeword*)codewords->GetAt(codewordsRow);
  CFX_PtrArray* previousColumnCodewords =
      ((CBC_DetectionResultColumn*)m_detectionResultColumns.GetAt(
           barcodeColumn - 1))
          ->getCodewords();
  CFX_PtrArray* nextColumnCodewords = previousColumnCodewords;
  if (m_detectionResultColumns[barcodeColumn + 1]) {
    nextColumnCodewords = ((CBC_DetectionResultColumn*)
                               m_detectionResultColumns[barcodeColumn + 1])
                              ->getCodewords();
  }
  CFX_PtrArray otherCodewords;
  otherCodewords.SetSize(14);
  otherCodewords[2] = previousColumnCodewords->GetAt(codewordsRow);
  otherCodewords[3] = nextColumnCodewords->GetAt(codewordsRow);
  if (codewordsRow > 0) {
    otherCodewords[0] = codewords->GetAt(codewordsRow - 1);
    otherCodewords[4] = previousColumnCodewords->GetAt(codewordsRow - 1);
    otherCodewords[5] = nextColumnCodewords->GetAt(codewordsRow - 1);
  }
  if (codewordsRow > 1) {
    otherCodewords[8] = codewords->GetAt(codewordsRow - 2);
    otherCodewords[10] = previousColumnCodewords->GetAt(codewordsRow - 2);
    otherCodewords[11] = nextColumnCodewords->GetAt(codewordsRow - 2);
  }
  if (codewordsRow < codewords->GetSize() - 1) {
    otherCodewords[1] = codewords->GetAt(codewordsRow + 1);
    otherCodewords[6] = previousColumnCodewords->GetAt(codewordsRow + 1);
    otherCodewords[7] = nextColumnCodewords->GetAt(codewordsRow + 1);
  }
  if (codewordsRow < codewords->GetSize() - 2) {
    otherCodewords[9] = codewords->GetAt(codewordsRow + 2);
    otherCodewords[12] = previousColumnCodewords->GetAt(codewordsRow + 2);
    otherCodewords[13] = nextColumnCodewords->GetAt(codewordsRow + 2);
  }
  for (int32_t i = 0; i < otherCodewords.GetSize(); i++) {
    CBC_Codeword* otherCodeword = (CBC_Codeword*)otherCodewords.GetAt(i);
    if (adjustRowNumber(codeword, otherCodeword)) {
      return;
    }
  }
}
FX_BOOL CBC_DetectionResult::adjustRowNumber(CBC_Codeword* codeword,
                                             CBC_Codeword* otherCodeword) {
  if (otherCodeword == NULL) {
    return FALSE;
  }
  if (otherCodeword->hasValidRowNumber() &&
      otherCodeword->getBucket() == codeword->getBucket()) {
    codeword->setRowNumber(otherCodeword->getRowNumber());
    return TRUE;
  }
  return FALSE;
}
int32_t CBC_DetectionResult::getBarcodeColumnCount() {
  return m_barcodeColumnCount;
}
int32_t CBC_DetectionResult::getBarcodeRowCount() {
  return m_barcodeMetadata->getRowCount();
}
int32_t CBC_DetectionResult::getBarcodeECLevel() {
  return m_barcodeMetadata->getErrorCorrectionLevel();
}