/*
 * 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.
 */
package dvi.image.pnm;

import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.image.BufferedImage;
import java.awt.image.DataBufferByte;
import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.logging.Logger;

import dvi.DviException;
import dvi.DviRect;
import dvi.DviRectSplitter;
import dvi.DviSize;
import dvi.image.split.ImageSplitter;
import dvi.image.split.SplitImageWriter;
import dvi.util.DviUtils;

public abstract class AbstractPnmSplitter
implements ImageSplitter
{
  private static final Logger LOGGER = Logger.getLogger(AbstractPnmSplitter.class
      .getName());
  protected final SplitImageWriter imageWriter;
  protected DataInputStream input;
  protected PnmHeader header;
  private final DviSize unitSize;
  private int shrinkFactor = 1;
  
  public AbstractPnmSplitter(DviSize unitSize, SplitImageWriter imageWriter)
  {
    this.unitSize = unitSize;
    if (imageWriter == null)
      throw new IllegalArgumentException("imageWriter can't be null");
    
    this.imageWriter = imageWriter;
    header = null;
  }
  
  public PnmHeader getPnmHeader()
  {
    return header;
  }
  
  
  protected void beginSplitInternal(PnmHeader header, InputStream is)
  throws DviException
  {
    this.header = header;
    LOGGER.fine("PnmHeader: " + header);
    input = new DataInputStream(is);
  }
  
  protected void endSplitInternal()
  throws DviException
  {
  }
  
  protected abstract byte [] createLineBuffer();
  
  protected abstract BufferedImage createBufferedImage(DviRect box);
  
  protected abstract int copyLineToDataBuffer(byte [] buf, int x, DviRect box, int i, DataBufferByte data);
  
  protected void readFully(byte [] buf) throws IOException
  {
    input.readFully(buf);
  }
  
  protected DviRectSplitter createRectSplitter(DviRect rect, DviSize unitSize)
  {
    return new DviRectSplitter(rect, unitSize);
  }
  
  public void splitImageFromStream(InputStream is)
  throws IOException, DviException
  {
    try {
      PnmHeader header = PnmHeader.parseHeader(is);
      beginSplitInternal(header, is);
      try {
        DviRect rect = new DviRect(0, 0, header.getWidth(), header.getHeight());
        final int sf = shrinkFactor;
        final DviSize samplingSize = unitSize.magnify(sf);
        DviRectSplitter splitter = createRectSplitter(rect.shrink(sf), unitSize);
        DviRectSplitter samplingSplitter = createRectSplitter(rect, samplingSize);
        
        DviRect [][] rects = splitter.getRects();
        DviRect [][] samplingRects = samplingSplitter.getRects();
        int rows = splitter.getNumRows();
        int cols = splitter.getNumColumns();
        
        imageWriter.beginSplitImage(this, splitter);
        
        try {
          byte [] buf = createLineBuffer();
          for (int row=0; row<rows; row++) {
            final BufferedImage [] imgs = new BufferedImage[cols];
            final DataBufferByte [] buffers = new DataBufferByte[cols];
            int h = 0;
            LOGGER.fine("Reading row=" + row + "/" + rows);
            for (int col =0; col < cols; col++) {
              final DviRect box = samplingRects[row][col];
              imgs[col] = createBufferedImage(box);
              h = box.height();
              buffers[col] = (DataBufferByte) imgs[col].getRaster().getDataBuffer();
            }
            for (int i=0; i<h; i++) {
              readFully(buf);
              int x = 0;
              for (int col =0; col < cols; col++) {
                final DviRect box = samplingRects[row][col];
                final int uw = copyLineToDataBuffer(buf, x, box, i, buffers[col]);
                x += uw;
              }
            }
            for (int col = 0; col < cols; col++) {
              if (sf == 1) {
                final BufferedImage img = imgs[col];
                imageWriter.writeImagePiece(img, row, col);
              } else {
                final DviRect box = rects[row][col];
                final BufferedImage img = imgs[col];
                final BufferedImage img2 = new BufferedImage(box.width(), box.height(), BufferedImage.TYPE_INT_RGB);
                Graphics2D g = img2.createGraphics();
                g.drawImage(img.getScaledInstance(box.width(), box.height(), Image.SCALE_AREA_AVERAGING), 0, 0, null);
                g.dispose();
                imageWriter.writeImagePiece(img2, row, col);
              }
            }
          }
        } finally {
          imageWriter.endSplitImage();
        }
      } finally {
        endSplitInternal();
      }
    } finally {
      LOGGER.fine("Closing the stream");
      DviUtils.silentClose(input);
    }
  }
  
  public SplitImageWriter getSplitImageWriter() {
    return imageWriter;
  }

  public DviSize getUnitSize() {
    return unitSize;
  }

  public void setShrinkFactor(int shrinkFactor) {
    if (0 >= shrinkFactor)
      throw new IllegalArgumentException("shrinkFactor is non-positive.");
    this.shrinkFactor = shrinkFactor;
  }

  public int getShrinkFactor() {
    return shrinkFactor;
  }
}
