/*
 * #%L
 * OME Bio-Formats manual and automated test suite.
 * %%
 * Copyright (C) 2006 - 2017 Open Microscopy Environment:
 *   - Board of Regents of the University of Wisconsin-Madison
 *   - Glencoe Software, Inc.
 *   - University of Dundee
 * %%
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as
 * published by the Free Software Foundation, either version 2 of the 
 * License, or (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public 
 * License along with this program.  If not, see
 * <http://www.gnu.org/licenses/gpl-2.0.html>.
 * #L%
 */

package loci.tests.testng;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;

import loci.common.DataTools;
import loci.common.services.ServiceFactory;
import loci.formats.FormatException;
import loci.formats.FormatTools;
import loci.formats.IFormatWriter;
import loci.formats.ImageWriter;
import loci.formats.gui.BufferedImageReader;
import loci.formats.meta.IMetadata;
import loci.formats.meta.MetadataRetrieve;
import loci.formats.out.JPEG2000Writer;
import loci.formats.out.JPEGWriter;
import loci.formats.services.OMEXMLService;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;

/**
 * TestNG tester for Bio-Formats file format writers.
 * Details on failed tests are written to a log file, for easier processing.
 *
 * NB: {@link loci.formats.ome} and ome-xml.jar
 * are required for some of the tests.
 *
 * To run tests:
 * ant -Dtestng.directory="/path" -Dtestng.multiplier="1.0" test-all
 */
public class FormatWriterTest {

  private static final Logger LOGGER =
    LoggerFactory.getLogger(FormatWriterTest.class);

  /** Message to give for why a test was skipped. */
  private static final String SKIP_MESSAGE = "Dataset already tested.";

  // -- Static fields --

  /** Configuration tree structure containing dataset metadata. */
  public static ConfigurationTree configTree;

  /** List of files to skip. */
  public static List skipFiles = new LinkedList();

  /** Reader for input file. */
  private static BufferedImageReader reader = new BufferedImageReader();

  /** Reader for converted files. */
  private static BufferedImageReader convertedReader =
    new BufferedImageReader();

  private static Configuration config;

  // -- Fields --

  private String id;
  private boolean skip = false;

  // -- Constructor --

  public FormatWriterTest(String filename) {
    id = filename;
    try {
      reader.setId(id);
    }
    catch (FormatException e) { LOGGER.info("", e); }
    catch (IOException e) { LOGGER.info("", e); }
  }

  // -- Data provider --

  @DataProvider(name = "getWriterList")
  public Object[][] getWriterList() {
    IFormatWriter[] writers = new ImageWriter().getWriters();
    List tmp = new ArrayList();
    for (int i=0; i<writers.length; i++) {
      String[] compressionTypes = writers[i].getCompressionTypes();
      if (compressionTypes == null) {
        try {
          IFormatWriter w = (IFormatWriter) writers[i].getClass().getDeclaredConstructor().newInstance();
          tmp.add(w);
        }
        catch (ReflectiveOperationException roe) { }
        continue;
      }
      for (int q=0; q<compressionTypes.length; q++) {
        try {
          IFormatWriter w = (IFormatWriter) writers[i].getClass().getDeclaredConstructor().newInstance();
          if (DataTools.containsValue(w.getPixelTypes(compressionTypes[q]),
            reader.getPixelType()))
          {
            w.setCompression(compressionTypes[q]);
            tmp.add(w);
          }
        }
        catch (FormatException fe) { }
        catch (ReflectiveOperationException roe) { }
      }
    }
    IFormatWriter[][] writersToUse = new IFormatWriter[tmp.size()][1];
    for (int i=0; i<tmp.size(); i++) {
      writersToUse[i][0] = (IFormatWriter) tmp.get(i);
    }
    return writersToUse;
  }

  // -- Tests --

  @Test(groups = {"all"}, dataProvider = "getWriterList")
  public void testWriterConsistency(IFormatWriter writer) {
    String testName = TestTools.shortClassName(writer) + " " +
      writer.getCompression() + " testWriterConsistency";
    boolean success = true;
    String msg = null;
    try {
      reader.close();

      ServiceFactory factory = new ServiceFactory();
      OMEXMLService service = factory.getInstance(OMEXMLService.class);
      reader.setMetadataStore(service.createOMEXMLMetadata());
      reader.setId(id);

      int type = reader.getPixelType();
      if (!writer.isSupportedType(type)) {
        success = true;
        result(testName, success, msg);
        return;
      }

      config = configTree.get(id);

      String prefix = id.substring(id.lastIndexOf(File.separator) + 1,
        id.lastIndexOf("."));
      // prefix must be at least 3 chars, or File.createTempFile(String, String)
      // will throw an exception
      while (prefix.length() < 3) prefix = "x" + prefix;
      String suffix = "." + writer.getSuffixes()[0];
      File tmpFile = File.createTempFile(prefix, suffix);
      tmpFile.deleteOnExit();
      String convertedFile = tmpFile.getAbsolutePath();

      IMetadata meta = (IMetadata) reader.getMetadataStore();
      writer.close();
      writer.setMetadataRetrieve((MetadataRetrieve) meta);

      // convert the input file
      writer.setId(convertedFile);

      int seriesCount = writer.canDoStacks() ? reader.getSeriesCount() : 1;

      for (int series=0; series<seriesCount; series++) {
        reader.setSeries(series);
        writer.setSeries(series);
        int imageCount = writer.canDoStacks() ? reader.getImageCount() : 1;
        for (int image=0; image<imageCount; image++) {
          writer.saveBytes(image, reader.openBytes(image));
        }
      }
      writer.close();

      // verify that the dimensions are accurate

      convertedReader.setId(convertedFile);

      boolean seriesMatch =
        convertedReader.getSeriesCount() == config.getSeriesCount();

      boolean expectRGB = config.isRGB();
      int expectedCount = config.getSizeZ() * config.getSizeT() *
        (expectRGB ? 1 : config.getSizeC());
      boolean imageMatch = convertedReader.getImageCount() == expectedCount;

      if (!seriesMatch && writer.canDoStacks()) {
        int totalImages = 0;
        for (int i=0; i<reader.getSeriesCount(); i++) {
          reader.setSeries(i);
          totalImages += reader.getImageCount();
        }
        reader.setSeries(0);
        if (convertedReader.getImageCount() != totalImages) {
          success = false;
          msg = "Series counts do not match (found " +
            convertedReader.getSeriesCount() + ", expected " +
            config.getSeriesCount() + ")";
        }
        else imageMatch = true;
      }
      if (success) {
        for (int series=0; series<seriesCount; series++) {
          if (series >= convertedReader.getSeriesCount()) {
            break;
          }
          convertedReader.setSeries(series);
          config.setSeries(series);

          int expectedX = config.getSizeX();
          int expectedY = config.getSizeY();
          expectRGB = config.isRGB();
          if (TestTools.shortClassName(writer).equals("OMEXMLWriter")) {
            expectRGB = false;
          }
          else if (TestTools.shortClassName(writer).equals("JPEGWriter")) {
            expectRGB = expectRGB || config.isIndexed();
          }

          int expectedPixelType =
            FormatTools.pixelTypeFromString(config.getPixelType());
          expectedCount = config.getSizeZ() * config.getSizeT() *
            (expectRGB ? 1 : config.getSizeC());

          String expectedMD5 = config.getMD5();

          int x = convertedReader.getSizeX();
          int y = convertedReader.getSizeY();
          int count = convertedReader.getImageCount();
          boolean rgb = convertedReader.isRGB();
          int pixelType = convertedReader.getPixelType();

          boolean isQuicktime =
            TestTools.shortClassName(writer).equals("QTWriter");

          String md5 = TestTools.md5(convertedReader.openBytes(0));

          if (msg == null) msg = checkMismatch(x, expectedX, series, "X");
          if (msg == null) msg = checkMismatch(y, expectedY, series, "Y");
          if (msg == null && writer.canDoStacks() && !imageMatch) {
            msg = checkMismatch(count, expectedCount, series, "Image count");
          }
          if (msg == null && !isQuicktime) {
            msg = checkMismatch(rgb, expectRGB, series, "RGB");
          }
          if (msg == null && !isQuicktime) {
            msg =
              checkMismatch(pixelType, expectedPixelType, series, "Pixel type");
          }
          if (msg == null && isLosslessWriter(writer) &&
            config.isRGB() == expectRGB)
          {
            msg = checkMismatch(md5, expectedMD5, series, "Pixels hash");
          }

          success = msg == null;
          if (!success) break;
        }
      }
      convertedReader.close();
    }
    catch (Throwable t) {
      LOGGER.info("", t);
      success = false;
    }
    result(testName, success, msg);
  }

  // -- Helper methods --

  private static String checkMismatch(boolean i1, boolean i2, int series,
    String label)
  {
    return checkMismatch(String.valueOf(i1), String.valueOf(i2), series, label);
  }

  private static String checkMismatch(int i1, int i2, int series, String label)
  {
    return checkMismatch(String.valueOf(i1), String.valueOf(i2), series, label);
  }

  private static String checkMismatch(String s1, String s2, int series,
    String label)
  {
    if (s1.equals(s2)) return null;
    return label + " mismatch [got " + s1 + ", expected " + s2 +
      "] in series " + series;
  }

  /**
   * Outputs test result with optional extra message
   * and generates appropriate assertion.
   */
  private static void result(String testName, boolean success, String msg) {
    LOGGER.info("\t{}: {} ({})", new Object[] {testName,
      success ? "PASSED" : "FAILED", msg == null ? "" : msg});
    if (msg == null) assert success;
    else assert success : msg;
  }

  private static boolean isLosslessWriter(IFormatWriter writer) {
    if ((writer instanceof JPEGWriter) || (writer instanceof JPEG2000Writer)) {
      return false;
    }
    String compression = writer.getCompression();
    if (compression != null) compression = compression.toLowerCase();
    if (compression == null || compression.equals("lzw") ||
      compression.equals("zlib") || compression.equals("uncompressed"))
    {
      return true;
    }
    return false;
  }

}
