/*
 * #%L
 * BSD implementations of Bio-Formats readers and writers
 * %%
 * Copyright (C) 2005 - 2017 Open Microscopy Environment:
 *   - Board of Regents of the University of Wisconsin-Madison
 *   - Glencoe Software, Inc.
 *   - University of Dundee
 * %%
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 * 
 * 1. Redistributions of source code must retain the above copyright notice,
 *    this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 * #L%
 */

package loci.formats.in;

import java.io.StreamTokenizer;
import java.io.IOException;
import java.util.StringTokenizer;

import loci.common.ByteArrayHandle;
import loci.common.RandomAccessInputStream;
import loci.common.RandomAccessOutputStream;
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;

/**
 * PGMReader is the file format reader for Portable Gray Map (PGM) images.
 *
 * Much of this code was adapted from ImageJ (http://rsb.info.nih.gov/ij).
 */
public class PGMReader extends FormatReader {

  // -- Constants --

  public static final char PGM_MAGIC_CHAR = 'P';

  // -- Fields --

  private boolean rawBits;

  /** Offset to pixel data. */
  private long offset;

  // -- Constructor --

  /** Constructs a new PGMReader. */
  public PGMReader() {
    super("Portable Any Map",
      new String[] {"pbm", "pgm", "ppm"});

    domains = new String[] {FormatTools.GRAPHICS_DOMAIN};
    suffixNecessary = false;
  }

  // -- IFormatReader API methods --

  /* @see loci.formats.IFormatReader#isThisType(RandomAccessInputStream) */
  @Override
  public boolean isThisType(RandomAccessInputStream stream) throws IOException {
    final int blockLen = 2;
    if (!FormatTools.validStream(stream, blockLen, false)) return false;
    return stream.read() == PGM_MAGIC_CHAR &&
      Character.isDigit((char) stream.read());
  }

  /**
   * @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(offset);
    if (rawBits) {
      readPlane(in, x, y, w, h, buf);
    }
    else {
      ByteArrayHandle handle = new ByteArrayHandle();
      RandomAccessOutputStream out = new RandomAccessOutputStream(handle);
      out.order(isLittleEndian());

      while (in.getFilePointer() < in.length()) {
        String line = in.readLine().trim();
        line = line.replaceAll("[^0-9]", " ");
        StringTokenizer t = new StringTokenizer(line, " ");
        while (t.hasMoreTokens()) {
          int q = Integer.parseInt(t.nextToken().trim());
          if (getPixelType() == FormatTools.UINT16) {
            out.writeShort(q);
          }
          else out.writeByte(q);
        }
      }

      out.close();
      try (RandomAccessInputStream s = new RandomAccessInputStream(handle)) {
        s.seek(0);
        readPlane(s, x, y, w, h, buf);
      }
    }

    return buf;
  }

  /* @see loci.formats.IFormatReader#close(boolean) */
  @Override
  public void close(boolean fileOnly) throws IOException {
    super.close(fileOnly);
    if (!fileOnly) {
      rawBits = false;
      offset = 0;
    }
  }

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

    //              Plain     Raw
    // B&W            P1      P4
    // Grayscale      P2      P5
    // RGB            P3      P6
    final String magic = in.readString(2);
    rawBits = magic.equals("P4") || magic.equals("P5") || magic.equals("P6");
    m.sizeC = (magic.equals("P3") || magic.equals("P6")) ? 3 : 1;
    final boolean isBlackAndWhite = magic.equals("P1") || magic.equals("P4");

    StreamTokenizer st = new StreamTokenizer(in);
    st.commentChar('#');

    int token;

    token = st.nextToken();
    m.sizeX = (int) st.nval;
    token = st.nextToken();
    m.sizeY = (int) st.nval;
    if (!isBlackAndWhite) {
      token = st.nextToken();
      final int max = (int) st.nval;
      if (max > 255)
        m.pixelType = FormatTools.UINT16;
      else
        m.pixelType = FormatTools.UINT8;
    }

    // After reading the 2 or 3 header entries, it is possible that there are
    // multiple comments before the actual pixel data.
    Byte c;
    do {
      c = in.readByte();
      if (c == 35) {
        // Found a # for comment
        do {
          c = in.readByte();
        } while (c != 13 && c != 10); // comment only ends after CR or LF
      }
    } while (c == 32 || c == 13 || c == 10 || c == 9 || c == 11 || c == 12);
    //            space      CR         LF         TAB       VT         FF
    offset = in.getFilePointer() -1;

    addGlobalMeta("Black and white", isBlackAndWhite);

    m.rgb = getSizeC() == 3;
    m.dimensionOrder = "XYCZT";
    m.littleEndian = true;
    m.interleaved = true;
    m.sizeZ = 1;
    m.sizeT = 1;
    m.indexed = false;
    m.falseColor = false;

    // This may not be true. Multiple images were added to the netpbm
    // formats in July 2000.
    m.imageCount = 1;

    // 1. the comments are being ignored;
    // 2. we may be missing some images (allowed in raw)
    m.metadataComplete = false;

    MetadataStore store = makeFilterMetadata();
    MetadataTools.populatePixels(store, this);
  }

}
