/*
 * 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 jp.sourceforge.dvibrowser.dvicore.image.split;

import java.awt.Image;
import java.awt.image.RenderedImage;
import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.Properties;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.imageio.ImageIO;

import jp.sourceforge.dvibrowser.dvicore.DviException;
import jp.sourceforge.dvibrowser.dvicore.DviRect;
import jp.sourceforge.dvibrowser.dvicore.DviRectSplitter;
import jp.sourceforge.dvibrowser.dvicore.DviResolution;
import jp.sourceforge.dvibrowser.dvicore.DviSize;
import jp.sourceforge.dvibrowser.dvicore.util.DviUtils;
import jp.sourceforge.dvibrowser.dvicore.util.ZipBuilder;


public class ZipSplitImageWriter implements SplitImageWriter {
   private static final Logger LOGGER = Logger
   .getLogger(ZipSplitImageWriter.class.getName());

   private static class SplitImageImpl implements SplitImage {
     private final DviRectSplitter rectSplitter;
     private final DviResolution res;
     private final ArrayList<SplitPiece> imagePieces = new ArrayList<SplitPiece>();

     public SplitImageImpl(DviResolution res, DviRectSplitter rectSplitter) {
       this.res = res;
       this.rectSplitter = rectSplitter;
     }

     public DviResolution getResolution()
     {
       return res;
     }

     public Collection<SplitPiece> getPieces() throws DviException {
       return Collections.unmodifiableCollection(imagePieces);
     }

     public DviRectSplitter getRectSplitter() throws DviException {
       return rectSplitter;
     }

     public Iterator<SplitPiece> iterator() {
       return imagePieces.iterator();
     }

    public DviRect getRect() throws DviException {
      return rectSplitter.getRect();
    }
   }

  private static class SplitPieceImpl extends AbstractSplitPiece {
    private final String path;

    public SplitPieceImpl(SplitImage splitImage, String path, DviResolution res,
        DviRectSplitter rectSplitter, int row,
        int col) {
      super(splitImage, res, rectSplitter, row, col);
      this.path = path;
    }

    public String getPath() {
      return path;
    }

    public Image getImage() throws DviException {
      return null;
    }

  }

  private final String outputBasename;
  private final ImageFileConfig imgConf;
  private final String path = "images";

  protected DviRectSplitter rectSplitter;
  private SplitImageImpl splitImage;
  private final ZipBuilder zip;
  private final DviResolution res;

  public ZipSplitImageWriter(String outputBasename, ImageFileConfig imgConf, DviResolution res, ZipBuilder zip) {
    this.outputBasename = outputBasename;
    this.imgConf = imgConf;
    this.res = res;
    this.zip = zip;
    this.rectSplitter = null;
    this.splitImage = null;
  }

  public ImageFileConfig getImageFileConfig() throws DviException {
    return imgConf;
  }

  protected String generateFilename(int row, int col) throws DviException {
    final DviSize unit = rectSplitter.getUnitSize();
    final int rows = rectSplitter.getNumRows();
    final int cols = rectSplitter.getNumColumns();
    String filename = getOutputBasename() + "/" + res.dpi() + "_" + res.shrinkFactor() + "-"
        + unit.width() + "-" + unit.height() + "-" + rows + "-" + cols + "-"
        + row + "-" + col + getImageFileConfig().getImageExtension();

    return filename;
  }

  public void beginSplitImage(ImageSplitter ImageSplitter,
      DviRectSplitter rectSplitter) throws DviException {
    if (rectSplitter == null) {
      throw new IllegalArgumentException("rectSplitter is null");
    }
    this.rectSplitter = rectSplitter;
    this.splitImage = new SplitImageImpl(res, rectSplitter);
  }

  public void endSplitImage() throws DviException {
    try {
      writeMetadata();
      writeHtmlView();
    } finally {
      DviUtils.silentClose(zip);
    }
  }

  protected void writeHtmlView() throws DviException {
    try {
      OutputStream os = getZipBuilder().openOutputStream("html/index.html");
      try {
        int rows = rectSplitter.getNumRows();
        int cols = rectSplitter.getNumColumns();
        DviRect totalRect = rectSplitter.getRect();

        double baseWidth = 640.0;
        double scale = (totalRect.width() > 0) ? (baseWidth / totalRect.width()) : baseWidth;

        PrintWriter pw = new PrintWriter(new OutputStreamWriter(os));
        pw.print("<html><body><table cellpadding='0' cellspacing='0' border='0'>");
        String closingTag = "";
        for (SplitPiece piece : splitImage) {
          if (piece instanceof SplitPieceImpl) {
            SplitPieceImpl p = (SplitPieceImpl) piece;
            String filename = "../" + p.getPath();
            DviRect r = p.getRect();
            double x = r.x() * scale;
            double y = r.y() * scale;
            int w = (int) Math.floor(r.width() * scale);
            int h = (int) Math.floor(r.height() * scale);
            if (piece.getColumn() == 0) {
              pw.print(closingTag);
              pw.printf("<tr>");
              closingTag = "</tr>";
            }
            pw.printf("<td>", scale);
            pw.printf("<img src='%s' alt='' galleryimg='no' style='width: %dpx; height: %dpx;' /></span>", filename, w, h);
            pw.printf("</td>");
          }
        }
        pw.print(closingTag);
        pw.print("</table></body></html>");
        pw.flush();
        pw.close();
      } finally {
        DviUtils.silentClose(os);
      }
    } catch (IOException e) {
      DviUtils.logStackTrace(LOGGER, Level.WARNING, e);
      throw new DviException(e);
    }
  }

  protected void writeMetadata() throws DviException {
    try {
      DviRect rect = rectSplitter.getRect();
      DviSize unit = rectSplitter.getUnitSize();
      // TODO: use XML instead of .properties.
      Properties props = new Properties();
      props.setProperty("image.format.name", "image-bundle");
      props.setProperty("image.basepath", path);
      props.setProperty("image.path", path + "/" + getOutputBasename()); // TODO:
                                                                         // make
                                                                         // the
                                                                         // path
                                                                         // computation
                                                                         // a
                                                                         // method.
      props.setProperty("image.format.version", "0.1");
      props.setProperty("image.total.width", String.valueOf(rect.width()));
      props.setProperty("image.total.height", String.valueOf(rect.height()));
      props.setProperty("image.unit.width", String.valueOf(unit.width()));
      props.setProperty("image.unit.height", String.valueOf(unit.height()));
      props.setProperty("image.hres.dpi", String.valueOf(res.dpi()));
      props.setProperty("image.hres.sf", String.valueOf(res.shrinkFactor()));
      props.setProperty("image.vres.dpi", String.valueOf(res.dpi()));
      props.setProperty("image.vres.sf", String.valueOf(res.shrinkFactor()));
      props.setProperty("image.piece.length", String.valueOf(splitImage
          .getPieces().size()));
      int rows = rectSplitter.getNumRows();
      int cols = rectSplitter.getNumColumns();
      props.setProperty("image.piece.rows", String.valueOf(rows));
      props.setProperty("image.piece.cols", String.valueOf(cols));
      for (SplitPiece piece : splitImage) {
        if (piece instanceof SplitPieceImpl) {
          SplitPieceImpl p = (SplitPieceImpl) piece;
          String filename = p.getPath();
          props.setProperty("image.piece." + piece.getRow() + "."
              + piece.getColumn() + ".file", filename);
        }
      }

      OutputStream os = getZipBuilder().openOutputStream(
          "images/index.properties");
      try {
        props.store(os, "image-bundle");
      } finally {
        DviUtils.silentClose(os);
      }
    } catch (IOException e) {
      DviUtils.logStackTrace(LOGGER, Level.WARNING, e);
      throw new DviException(e);
    }
  }

  protected File createTempFile() throws IOException
  {
    // TODO: add a temporary file manager to  DviContext.
    File file = File.createTempFile("zip-png", "tmp");
    file.deleteOnExit();
    return file;
  }

  public void writeImagePiece(RenderedImage img, int row, int col)
      throws DviException {
    try {
      // TODO: Think about the directory structure of the output zip file.
      // TODO: Compute md5 of piece image and store it.  Make a flag to denote if the data contains md5 or not.
      String filename = path + "/" + generateFilename(row, col);
      File tmpFile = createTempFile();
      try {
        ImageIO.write(img, imgConf.getImageType(), tmpFile);
        getZipBuilder().write(filename, tmpFile);
        SplitPiece piece = new SplitPieceImpl(splitImage, filename, res, rectSplitter, row, col);
        splitImage.imagePieces.add(piece);
      } finally {
        tmpFile.delete();
      }
    } catch (IOException e) {
      throw new DviException(e);
    }
  }

  public ZipBuilder getZipBuilder() {
    return zip;
  }

  public String getOutputBasename() {
    return outputBasename;
  }

  public SplitImage getSplitImage()
  {
    return splitImage;
  }

  public DviResolution getResolution() {
    return res;
  }
}
