package dvi.image.pnm;
/*
 * Copyright (c) 2009, Takeyuki Nagao
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or
 * without modification, are permitted provided that the
 * following conditions are met:
 * 
 *  * Redistributions of source code must retain the above
 *    copyright notice, this list of conditions and the
 *    following disclaimer.
 *  * 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 HOLDER 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.
 */


import java.io.IOException;
import java.io.InputStream;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import dvi.util.DviUtils;


public final class PnmHeader
{
  private static final Logger LOGGER = Logger.getLogger(PnmHeader.class
      .getName());
  
  public static final String PNM_COMMENT_NEWLINE = "\n";
  
  public static final int PNM_TYPE_BITMAP_ASCII = 1;
  public static final int PNM_TYPE_GRAYMAP_ASCII = 2;
  public static final int PNM_TYPE_PIXMAP_ASCII = 3;
  public static final int PNM_TYPE_BITMAP_BINARY = 4;
  public static final int PNM_TYPE_GRAYMAP_BINARY = 5;
  public static final int PNM_TYPE_PIXMAP_BINARY = 6;

  private final int type;
  private final int width;
  private final int height;
  private final int maxval;
  private final String comment;
  
  public PnmHeader(int type, String comment, int width, int height, int maxval)
  {
    this.type = type;
    this.comment = comment;
    this.width = width;
    this.height = height;
    this.maxval = maxval;
  }
  
  public boolean isBitmap()
  {
    return (PNM_TYPE_BITMAP_ASCII == type || PNM_TYPE_BITMAP_BINARY == type);
  }
  
  public boolean isGraymap()
  {
    return (PNM_TYPE_GRAYMAP_ASCII == type || PNM_TYPE_GRAYMAP_BINARY == type);
  }

  public boolean isPixmap()
  {
    return (PNM_TYPE_PIXMAP_ASCII == type || PNM_TYPE_PIXMAP_BINARY == type);
  }
  
  public boolean isASCII()
  {
    return (1 <= type && type <= 3);
  }

  public boolean isBinary()
  {
    return !isASCII();
  }
  
  public int getType() {
    return type;
  }

  public int getWidth() {
    return width;
  }

  public int getHeight() {
    return height;
  }

  public int getMaxValue() {
    return maxval;
  }
  
  public String toString() {
    return getClass().getName() + "[type=" + type + ",width=" + width
        + ",height=" + height + ",maxval=" + maxval + ",comment=" + comment + "]";
  }
  
  // P1: bitmap (ASCII)
  // P2: graymap (ASCII)
  // P3: pixmap (ASCII)
  // P4: bitmap (binary)
  // P5: graymap (binary)
  // P6: pixmap (binary)
  
//  private static final Pattern patPnmMagicNumbers = Pattern.compile("P([1-6])");
  private static final Pattern patPnmComment = Pattern.compile("#\\s?(.*)");
  private static final Pattern patPnmSize = Pattern.compile("([0-9][0-9]*)\\s\\s*([0-9][0-9]*)");
  
  public String readLine(InputStream is)
  throws IOException
  {
    StringBuilder sb = new StringBuilder();

    int c = -1;
    do {
      c = is.read();
    } while (c != '\n');
    
    return sb.toString();
  }
  
  public static String readLine(InputStream is, int c, int delim)
  throws IOException
  {
    String line = "";
    do {
      if (c >= 0) {
        line += (char) c;
      }
      c = is.read();
      if (c < 0)
        return null;
    } while (c != delim);
    return line;
  }

  
  public static PnmHeader parseHeader(InputStream is)
  throws IOException
  {
    int type=-1, width=-1, height=-1, maxval=-1;
    String comment = null;
    
    {
      int c1 = is.read();
      int c2 = is.read();
    
      if (!(c1 == 'P' && '1' <= c2 && c2 <= '6'))
        throw new IOException("Invalid PNM magic number: c1=" + (char) c1 + " c2=" + (char) c2);
      type = c2 - '0';
    }
    
    int c = -1;
    int delim = -1;
    
    c = is.read();
    
    while (isNewLine(c)) {
      delim = c;
      c = is.read();
    }
    
    if (delim < 0) {
      throw new IOException("Illegal data after PNM magic number: " + c);
    }
    
    // Parse Comments.
    String line = readLine(is, c, delim);
    while (null != line) {
      Matcher mat = patPnmComment.matcher(line);
      if (!mat.matches()) break;
      String s = mat.group(1);
      comment = (comment == null) ? s : comment + PNM_COMMENT_NEWLINE + s;
      line = readLine(is, -1, delim);
    }

    // Parse size.
    {
      Matcher mat2 = patPnmSize.matcher(line);
      if (mat2.matches()) {
        try {
          width = Integer.parseInt(mat2.group(1));
          height = Integer.parseInt(mat2.group(2));
        } catch (NumberFormatException e) {
          DviUtils.logStackTrace(LOGGER, Level.WARNING, e);
        }
      }
    }
    if (width < 0 || height < 0) {
      throw new IOException("Failed to decode PNM image size: " + line);
    }
    
    // Parse max value (which is required for graymap and pixmap).
    if (PNM_TYPE_BITMAP_ASCII == type || PNM_TYPE_BITMAP_BINARY == type) {
      maxval = 1;
    } else {
      line = readLine(is, -1, delim);
      try {
        maxval = Integer.parseInt(line);
      } catch (NumberFormatException e) {
        DviUtils.logStackTrace(LOGGER, Level.WARNING, e);
      }
      if (maxval < 0) {
        throw new IOException("Failed to decode PNM max value: " + line);
      }
    }
    
    return new PnmHeader(type, comment, width, height, maxval);
  }

  private static boolean isNewLine(int c) {
    return (c == '\r' || c == '\n');
  }

  public String getComment() {
    return comment;
  }
}