/*
 * #%L
 * OME Bio-Formats package for reading and converting biological file formats.
 * %%
 * Copyright (C) 2005 - 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.formats.in;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;

import loci.common.DataTools;
import loci.common.Location;
import loci.common.RandomAccessInputStream;
import loci.formats.CoreMetadata;
import loci.formats.FormatException;
import loci.formats.FormatTools;
import loci.formats.MetadataTools;
import loci.formats.meta.MetadataStore;
import loci.formats.tiff.IFD;
import loci.formats.tiff.TiffParser;
import ome.units.UNITS;
import ome.units.quantity.Length;

/**
 */
public class SlidebookTiffReader extends BaseTiffReader {

  // -- Constants --

  public static final String SLIDEBOOK_MAGIC_STRING = "SlideBook";

  private static final int X_POS_TAG = 65000;
  private static final int Y_POS_TAG = 65001;
  private static final int Z_POS_TAG = 65002;
  private static final int CHANNEL_TAG = 65004;
  private static final int PHYSICAL_SIZE_TAG = 65007;
  private static final int MAGNIFICATION_TAG = 65005;

  // -- Fields --

  private int lastFile = 0;
  private MinimalTiffReader[] readers;
  private String[] files;
  private ArrayList<String> channelNames = new ArrayList<String>();

  // -- Constructor --

  public SlidebookTiffReader() {
    super("Slidebook TIFF", new String[] {"tif", "tiff"});
    suffixSufficient = false;
    domains = new String[] {FormatTools.LM_DOMAIN};
  }

  // -- IFormatReader API methods --

  /* @see loci.formats.IFormatReader#isThisType(RandomAccessInputStream) */
  @Override
  public boolean isThisType(RandomAccessInputStream stream) throws IOException {
    TiffParser tp = new TiffParser(stream);
    IFD ifd = tp.getFirstIFD();
    if (ifd == null) return false;
    String software = ifd.getIFDTextValue(IFD.SOFTWARE);
    if (software == null) return false;
    return software.equals(SLIDEBOOK_MAGIC_STRING) &&
      ifd.getComment().length() == 0 && (ifd.containsKey(X_POS_TAG) ||
      ifd.containsKey(Y_POS_TAG) || ifd.containsKey(Z_POS_TAG) ||
      ifd.containsKey(CHANNEL_TAG) || ifd.containsKey(PHYSICAL_SIZE_TAG) ||
      ifd.containsKey(MAGNIFICATION_TAG));
  }

  /* @see loci.formats.IFormatReader#close(boolean) */
  @Override
  public void close(boolean fileOnly) throws IOException {
    super.close(fileOnly);
    if (!fileOnly) {
      if (readers != null) {
        for (MinimalTiffReader reader : readers) {
          if (reader != null) {
            reader.close();
          }
        }
      }
      lastFile = 0;
      readers = null;
      files = null;
      channelNames.clear();
    }
  }

  /* @see loci.formats.IFormatReader#getSeriesUsedFiles(boolean) */
  @Override
  public String[] getSeriesUsedFiles(boolean noPixels) {
    FormatTools.assertId(currentId, true, 1);
    return noPixels ? null : files;
  }

  /* @see loci.formats.IFormatReader#get8BitLookupTable() */
  @Override
  public byte[][] get8BitLookupTable() throws FormatException, IOException {
    FormatTools.assertId(currentId, true, 1);
    if (readers == null || lastFile < 0 || lastFile >= readers.length ||
      readers[lastFile] == null)
    {
      return super.get8BitLookupTable();
    }
    return readers[lastFile].get8BitLookupTable();
  }

  /* @see loci.formats.IFormatReader#get16BitLookupTable() */
  @Override
  public short[][] get16BitLookupTable() throws FormatException, IOException {
    FormatTools.assertId(currentId, true, 1);
    if (readers == null || lastFile < 0 || lastFile >= readers.length ||
      readers[lastFile] == null)
    {
      return super.get16BitLookupTable();
    }
    return readers[lastFile].get16BitLookupTable();
  }

  /**
   * @see loci.formats.IFormatReader#openBytes(int, byte[], int, int, int, int)
   */
  @Override
  public byte[] openBytes(int no, byte[] buf, int x, int y, int w, int h)
    throws FormatException, IOException
  {
    FormatTools.checkPlaneParameters(this, no, buf.length, x, y, w, h);

    int file = no / getSizeT();
    int plane = no % getSizeT();

    lastFile = file;

    return readers[file].openBytes(plane, buf, x, y, w, h);
  }

  // -- Internal BaseTiffReader API methods --

  /* @see BaseTiffReader#initStandardMetadata() */
  @Override
  protected void initStandardMetadata() throws FormatException, IOException {
    super.initStandardMetadata();

    put("Slidebook", "yes");

    if (isGroupFiles()) {
      // look for other TIFF files that belong to this dataset

      String timestamp = getTimestamp(currentId);

      Location parent =
        new Location(currentId).getAbsoluteFile().getParentFile();
      String[] list = parent.list(true);
      Arrays.sort(list);
      ArrayList<String> matchingFiles = new ArrayList<String>();
      for (String f : list) {
        String path = new Location(parent, f).getAbsolutePath();
        if (isThisType(path) && getTimestamp(path).equals(timestamp)) {
          matchingFiles.add(path);
        }
      }

      files = matchingFiles.toArray(new String[matchingFiles.size()]);
    }
    else {
      files = new String[] {currentId};
    }

    readers = new MinimalTiffReader[files.length];

    CoreMetadata m = core.get(0, 0);

    m.imageCount = ifds.size() * files.length;
    m.sizeT = ifds.size();

    for (int i=0; i<readers.length; i++) {
      readers[i] = new MinimalTiffReader();
      readers[i].setId(files[i]);

      String channelName = getFirstChannel(files[i]);
      if (!channelNames.contains(channelName)) {
        channelNames.add(channelName);
      }
    }

    m.sizeC = channelNames.size();
    m.sizeZ = getImageCount() / (getSizeT() * getSizeC());
    m.dimensionOrder = "XYTCZ";
  }

  /* @see BaseTiffReader#initMetadataStore() */
  @Override
  protected void initMetadataStore() throws FormatException {
    super.initMetadataStore();
    MetadataStore store = makeFilterMetadata();
    MetadataTools.populatePixels(store, this, true);

    Location file = new Location(currentId).getAbsoluteFile();
    store.setImageName(file.getParentFile().getName(), 0);

    if (getMetadataOptions().getMetadataLevel() != MetadataLevel.MINIMUM) {
      for (int c=0; c<getEffectiveSizeC(); c++) {
        if (c < channelNames.size()) {
          String name = channelNames.get(c);
          if (name != null) {
            if (name.indexOf(':') > 0) {
              name = name.substring(name.indexOf(':') + 1);
            }
            if (name.indexOf(';') > 0) {
              name = name.substring(0, name.indexOf(';'));
            }

            store.setChannelName(name.trim(), 0, c);
          }
        }
      }

      IFD ifd = ifds.get(0);
      String physicalSize = ifd.getIFDTextValue(PHYSICAL_SIZE_TAG);
      if (physicalSize != null) {
        Double size = DataTools.parseDouble(physicalSize);
        if (size > 0) {
          store.setPixelsPhysicalSizeX(FormatTools.getPhysicalSizeX(size), 0);
          store.setPixelsPhysicalSizeY(FormatTools.getPhysicalSizeY(size), 0);
        }
      }

      String mag = ifd.getIFDTextValue(MAGNIFICATION_TAG);
      if (mag != null) {
        store.setInstrumentID(MetadataTools.createLSID("Instrument", 0), 0);
        store.setObjectiveID(MetadataTools.createLSID("Objective", 0, 0), 0, 0);
        store.setObjectiveCorrection(MetadataTools.getCorrection("Other"), 0, 0);
        store.setObjectiveImmersion(MetadataTools.getImmersion("Other"), 0, 0);
        store.setObjectiveNominalMagnification(DataTools.parseDouble(mag), 0, 0);
      }

      final Double xn = Double.valueOf(ifd.getIFDTextValue(X_POS_TAG));
      final Double yn = Double.valueOf(ifd.getIFDTextValue(Y_POS_TAG));
      final Double zn = Double.valueOf(ifd.getIFDTextValue(Z_POS_TAG));

      final Length xl = new Length(xn, UNITS.REFERENCEFRAME);
      final Length yl = new Length(yn, UNITS.REFERENCEFRAME);
      final Length zl = new Length(zn, UNITS.REFERENCEFRAME);

      for (int i=0; i<getImageCount(); i++) {
        store.setPlanePositionX(xl, 0, i);
        store.setPlanePositionY(yl, 0, i);
        store.setPlanePositionZ(zl, 0, i);
      }
    }
  }

  // -- Helper methods --

  private String getTimestamp(String path) throws FormatException, IOException {
    Object date = null;
    try (RandomAccessInputStream s = new RandomAccessInputStream(path)) {
        TiffParser parser = new TiffParser(s);
        IFD ifd = parser.getFirstIFD();
        date = ifd.getIFDValue(IFD.DATE_TIME);
    }

    return date == null ? null : date.toString();
  }

  private String getFirstChannel(String path)
    throws FormatException, IOException
  {
    Object channel = null;
    try (RandomAccessInputStream s = new RandomAccessInputStream(path)) {
      TiffParser parser = new TiffParser(s);
      IFD ifd = parser.getFirstIFD();
      channel = ifd.getIFDValue(CHANNEL_TAG);
    }

    return channel == null ? null : channel.toString();
  }

}
