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

package com.google.zxing.pdf417;

import com.google.zxing.BarcodeFormat;
import com.google.zxing.BinaryBitmap;
import com.google.zxing.BufferedImageLuminanceSource;
import com.google.zxing.DecodeHintType;
import com.google.zxing.LuminanceSource;
import com.google.zxing.ReaderException;
import com.google.zxing.Result;
import com.google.zxing.ResultMetadataType;
import com.google.zxing.common.AbstractBlackBoxTestCase;
import com.google.zxing.common.HybridBinarizer;
import com.google.zxing.common.TestResult;

import com.google.zxing.multi.MultipleBarcodeReader;
import org.junit.Test;

import javax.imageio.ImageIO;

import java.awt.image.BufferedImage;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.EnumMap;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.logging.Logger;

/**
 * This class tests Macro PDF417 barcode specific functionality. It ensures that information, which is split into
 * several barcodes can be properly combined again to yield the original data content.
 *
 * @author Guenther Grau
 */
public final class PDF417BlackBox4TestCase extends AbstractBlackBoxTestCase {
  private static final Logger log = Logger.getLogger(AbstractBlackBoxTestCase.class.getSimpleName());

  private final MultipleBarcodeReader barcodeReader = new PDF417Reader();

  private final List<TestResult> testResults = new ArrayList<>();

  public PDF417BlackBox4TestCase() {
    super("src/test/resources/blackbox/pdf417-4", null, BarcodeFormat.PDF_417);
    testResults.add(new TestResult(3, 3, 0, 0, 0.0f));
  }

  @Test
  @Override
  public void testBlackBox() throws IOException {
    assertFalse(testResults.isEmpty());

    Map<String,List<Path>> imageFiles = getImageFileLists();
    int testCount = testResults.size();

    int[] passedCounts = new int[testCount];
    int[] tryHarderCounts = new int[testCount];

    Path testBase = getTestBase();

    for (Entry<String,List<Path>> testImageGroup : imageFiles.entrySet()) {
      log.fine(String.format("Starting Image Group %s", testImageGroup.getKey()));

      String fileBaseName = testImageGroup.getKey();
      String expectedText;
      Path expectedTextFile = testBase.resolve(fileBaseName + ".txt");
      if (Files.exists(expectedTextFile)) {
        expectedText = readFileAsString(expectedTextFile, StandardCharsets.UTF_8);
      } else {
        expectedTextFile = testBase.resolve(fileBaseName + ".bin");
        assertTrue(Files.exists(expectedTextFile));
        expectedText = readFileAsString(expectedTextFile, StandardCharsets.ISO_8859_1);
      }

      for (int x = 0; x < testCount; x++) {
        List<Result> results = new ArrayList<>();
        for (Path imageFile : testImageGroup.getValue()) {
          BufferedImage image = ImageIO.read(imageFile.toFile());
          float rotation = testResults.get(x).getRotation();
          BufferedImage rotatedImage = rotateImage(image, rotation);
          LuminanceSource source = new BufferedImageLuminanceSource(rotatedImage);
          BinaryBitmap bitmap = new BinaryBitmap(new HybridBinarizer(source));

          try {
            results.addAll(Arrays.asList(decode(bitmap, false)));
          } catch (ReaderException ignored) {
            // ignore
          }
        }
        results.sort(Comparator.comparingInt((Result r) -> getMeta(r).getSegmentIndex()));
        StringBuilder resultText = new StringBuilder();
        String fileId = null;
        for (Result result : results) {
          PDF417ResultMetadata resultMetadata = getMeta(result);
          assertNotNull("resultMetadata", resultMetadata);
          if (fileId == null) {
            fileId = resultMetadata.getFileId();
          }
          assertEquals("FileId", fileId, resultMetadata.getFileId());
          resultText.append(result.getText());
        }
        assertEquals("ExpectedText", expectedText, resultText.toString());
        passedCounts[x]++;
        tryHarderCounts[x]++;
      }
    }

    // Print the results of all tests first
    int totalFound = 0;
    int totalMustPass = 0;

    int numberOfTests = imageFiles.keySet().size();
    for (int x = 0; x < testResults.size(); x++) {
      TestResult testResult = testResults.get(x);
      log.info(String.format("Rotation %d degrees:", (int) testResult.getRotation()));
      log.info(String.format(" %d of %d images passed (%d required)", passedCounts[x], numberOfTests,
          testResult.getMustPassCount()));
      log.info(String.format(" %d of %d images passed with try harder (%d required)", tryHarderCounts[x],
          numberOfTests, testResult.getTryHarderCount()));
      totalFound += passedCounts[x] + tryHarderCounts[x];
      totalMustPass += testResult.getMustPassCount() + testResult.getTryHarderCount();
    }

    int totalTests = numberOfTests * testCount * 2;
    log.info(String.format("Decoded %d images out of %d (%d%%, %d required)", totalFound, totalTests, totalFound *
        100 /
        totalTests, totalMustPass));
    if (totalFound > totalMustPass) {
      log.warning(String.format("+++ Test too lax by %d images", totalFound - totalMustPass));
    } else if (totalFound < totalMustPass) {
      log.warning(String.format("--- Test failed by %d images", totalMustPass - totalFound));
    }

    // Then run through again and assert if any failed
    for (int x = 0; x < testCount; x++) {
      TestResult testResult = testResults.get(x);
      String label = "Rotation " + testResult.getRotation() + " degrees: Too many images failed";
      assertTrue(label, passedCounts[x] >= testResult.getMustPassCount());
      assertTrue("Try harder, " + label, tryHarderCounts[x] >= testResult.getTryHarderCount());
    }
  }

  private static PDF417ResultMetadata getMeta(Result result) {
    return result.getResultMetadata() == null ? null : (PDF417ResultMetadata) result.getResultMetadata().get(
        ResultMetadataType.PDF417_EXTRA_METADATA);
  }

  private Result[] decode(BinaryBitmap source, boolean tryHarder) throws ReaderException {
    Map<DecodeHintType,Object> hints = new EnumMap<>(DecodeHintType.class);
    if (tryHarder) {
      hints.put(DecodeHintType.TRY_HARDER, Boolean.TRUE);
    }

    return barcodeReader.decodeMultiple(source, hints);
  }

  private Map<String,List<Path>> getImageFileLists() throws IOException {
    Map<String,List<Path>> result = new HashMap<>();
    for (Path file : getImageFiles()) {
      String testImageFileName = file.getFileName().toString();
      String fileBaseName = testImageFileName.substring(0, testImageFileName.indexOf('-'));
      List<Path> files = result.computeIfAbsent(fileBaseName, k -> new ArrayList<>());
      files.add(file);
    }
    return result;
  }

}
