/*
 * #%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 loci.common.DataTools;
import loci.common.RandomAccessInputStream;
import loci.common.xml.BaseHandler;
import loci.common.xml.XMLTools;
import loci.formats.CoreMetadata;
import loci.formats.FormatException;
import loci.formats.FormatReader;
import loci.formats.FormatTools;
import loci.formats.MetadataTools;
import loci.formats.meta.MetadataStore;

import ome.xml.model.primitives.Timestamp;

import org.xml.sax.Attributes;

import ome.units.quantity.Time;
import ome.units.UNITS;

/**
 * BioRadSCNReader is the reader for Bio-Rad .scn files
 */
public class BioRadSCNReader extends FormatReader {

  // -- Constants --

  private static final String MAGIC = "Generated by Image Lab";

  // -- Fields --

  private long pixelsOffset;

  private Double gain;
  private Double exposureTime;
  private String imageName;
  private String serialNumber;
  private String acquisitionDate;
  private String binning;
  private String model;
  private Double physicalSizeX, physicalSizeY;

  // -- Constructor --

  /** Constructs a new Bio-Rad .scn reader. */
  public BioRadSCNReader() {
    super("Bio-Rad SCN", "scn");
    domains = new String[] {FormatTools.GEL_DOMAIN};
    suffixSufficient = false;
  }

  // -- IFormatReader API methods --

  /* @see loci.formats.IFormatReader#isThisType(RandomAccessInputStream) */
  @Override
  public boolean isThisType(RandomAccessInputStream stream) throws IOException {
    final int blockLen = 64;
    if (!FormatTools.validStream(stream, blockLen, false)) return false;
    String check = stream.readString(blockLen);
    return check.indexOf(MAGIC) >= 0;
  }

  /**
   * @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);

    in.seek(pixelsOffset);
    readPlane(in, x, y, w, h, buf);
    return buf;
  }

  /* @see loci.formats.IFormatReader#close(boolean) */
  @Override
  public void close(boolean fileOnly) throws IOException {
    super.close(fileOnly);
    pixelsOffset = 0;
    gain = null;
    exposureTime = null;
    imageName = null;
    serialNumber = null;
    acquisitionDate = null;
    binning = null;
    model = null;
    physicalSizeX = null;
    physicalSizeY = null;
  }

  // -- Internal FormatReader API methods --

  /* @see loci.formats.FormatReader#initFile(String) */
  @Override
  protected void initFile(String id) throws FormatException, IOException {
    super.initFile(id);
    in = new RandomAccessInputStream(id);
    CoreMetadata m = core.get(0);

    String line = in.readLine();
    String currentBoundary = "";
    String currentType = "";
    int currentLength = 0;
    ArrayList<String> xml = new ArrayList<String>();
    while (in.getFilePointer() < in.length() && line != null) {
      line = line.trim();
      if (line.startsWith("Content-Type")) {
        currentType = line.substring(line.indexOf(' ') + 1);

        int boundary = currentType.indexOf("boundary");
        if (boundary > 0) {
          currentBoundary =
            currentType.substring(boundary + 10, currentType.length() - 1);
        }

        if (currentType.indexOf(';') > 0) {
          currentType = currentType.substring(0, currentType.indexOf(';'));
        }
      }
      else if (line.equals("--" + currentBoundary)) {
        currentLength = 0;
      }
      else if (line.startsWith("Content-Length")) {
        currentLength = Integer.parseInt(line.substring(line.indexOf(' ') + 1));
      }
      else if (line.length() == 0) {
        if (currentType.equals("application/octet-stream")) {
          pixelsOffset = in.getFilePointer();
          in.skipBytes(currentLength);
        }
        else if (currentType.equals("text/xml")) {
          String xmlBlock = in.readString(currentLength);
          xml.add(xmlBlock);
        }
      }

      line = in.readLine();
    }

    SCNHandler handler = new SCNHandler();
    for (String block : xml) {
      XMLTools.parseXML(block, handler);
    }

    m.sizeZ = 1;
    m.sizeT = 1;
    m.imageCount = 1;
    m.dimensionOrder = "XYCZT";

    MetadataStore store = makeFilterMetadata();
    MetadataTools.populatePixels(store, this, exposureTime != null);

    store.setInstrumentID(MetadataTools.createLSID("Instrument", 0), 0);

    if (serialNumber != null) {
      store.setMicroscopeSerialNumber(serialNumber, 0);
    }
    if (model != null) {
      store.setMicroscopeModel(model, 0);
    }

    if (imageName != null) {
      store.setImageName(imageName, 0);
    }
    if (acquisitionDate != null) {
      store.setImageAcquisitionDate(new Timestamp(acquisitionDate), 0);
    }

    if (gain != null || binning != null) {
      String detector = MetadataTools.createLSID("Detector", 0, 0);
      store.setDetectorID(detector, 0, 0);
      store.setDetectorSettingsID(detector, 0, 0);
    }

    if (gain != null) {
      store.setDetectorSettingsGain(gain, 0, 0);
    }
    if (binning != null) {
      store.setDetectorSettingsBinning(MetadataTools.getBinning(binning), 0, 0);
    }

    if (exposureTime != null) {
      store.setPlaneExposureTime(new Time(exposureTime, UNITS.SECOND), 0, 0);
    }

    if (physicalSizeX != null) {
      store.setPixelsPhysicalSizeX(FormatTools.createLength(physicalSizeX, UNITS.MICROMETER), 0);
    }
    if (physicalSizeY != null) {
      store.setPixelsPhysicalSizeY(FormatTools.createLength(physicalSizeY, UNITS.MICROMETER), 0);
    }
  }

  class SCNHandler extends BaseHandler {

    // -- Fields --

    private String key;

    // -- DefaultHandler API methods --

    @Override
    public void endElement(String uri, String localName, String qName) {
      key = null;
    }

    @Override
    public void characters(char[] ch, int start, int length) {
      String value = new String(ch, start, length);
      addGlobalMeta(key, value);

      if ("endian".equals(key)) {
        core.get(0).littleEndian = value.equals("little");
      }
      else if ("channel_count".equals(key)) {
        core.get(0).sizeC = Integer.parseInt(value);
      }
      else if ("application_gain".equals(key)) {
        gain = DataTools.parseDouble(value);
      }
      else if ("exposure_time".equals(key)) {
        exposureTime = DataTools.parseDouble(value);
      }
      else if ("name".equals(key)) {
        imageName = value;
      }
    }

    @Override
    public void startElement(String uri, String localName, String qName,
      Attributes attributes)
    {
      key = qName;

      for (int i=0; i<attributes.getLength(); i++) {
        String attrKey = attributes.getQName(i);
        String attrValue = attributes.getValue(i);
        addGlobalMeta(key + " " + attrKey, attrValue);

        if (key.equals("size_pix")) {
          if (attrKey.equals("width")) {
            core.get(0).sizeX = Integer.parseInt(attrValue);
          }
          else if (attrKey.equals("height")) {
            core.get(0).sizeY = Integer.parseInt(attrValue);
          }
        }
        else if (key.equals("scanner")) {
          if (attrKey.equals("max_value")) {
            long value = Long.parseLong(attrValue);
            if (value <= 256) {
              core.get(0).pixelType = FormatTools.UINT8;
            }
            else if (value <= 65535) {
              core.get(0).pixelType = FormatTools.UINT16;
            }
          }
        }
        else if (key.equals("size_mm")) {
          if (attrKey.equals("width")) {
            Double size = DataTools.parseDouble(attrValue);
            if (size != null) {
              physicalSizeX = size / getSizeX();
              physicalSizeX *= 1000; // convert from mm to um
            }
          }
          else if (attrKey.equals("height")) {
            Double size = DataTools.parseDouble(attrValue);
            if (size != null) {
              physicalSizeY = size / getSizeY();
              physicalSizeY *= 1000; // convert from mm to um
            }
          }
        }
        else if (key.equals("serial_number")) {
          if (attrKey.equals("value")) {
            serialNumber = attrValue;
          }
        }
        else if (key.equals("binning")) {
          if (attrKey.equals("value")) {
            binning = attrValue;
          }
        }
        else if (key.equals("image_date")) {
          if (attrKey.equals("value")) {
            acquisitionDate = attrValue;
          }
        }
        else if (key.equals("imager")) {
          if (attrKey.equals("value")) {
            model = attrValue;
          }
        }
      }
    }

  }

}
