/*
 * 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.special;

import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URL;
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.DviObject;
import jp.sourceforge.dvibrowser.dvicore.api.DviContextSupport;
import jp.sourceforge.dvibrowser.dvicore.api.DviDocument;
import jp.sourceforge.dvibrowser.dvicore.api.DviPage;
import jp.sourceforge.dvibrowser.dvicore.api.HasURL;
import jp.sourceforge.dvibrowser.dvicore.ctx.DviToolkit;
import jp.sourceforge.dvibrowser.dvicore.gs.GhostscriptCommandBuilder;
import jp.sourceforge.dvibrowser.dvicore.gui.swing.ViewSpec;
import jp.sourceforge.dvibrowser.dvicore.image.split.DviImage;
import jp.sourceforge.dvibrowser.dvicore.util.CommandShell;
import jp.sourceforge.dvibrowser.dvicore.util.CommandShellHandler;
import jp.sourceforge.dvibrowser.dvicore.util.DviUtils;
import jp.sourceforge.dvibrowser.dvicore.util.LineBuffer;
import jp.sourceforge.dvibrowser.dvicore.util.concurrent.Computation;


public class EPS2ImagePreparator
extends DviObject
implements Computation<String, DviImage> {
  private static final Logger LOGGER = Logger
      .getLogger(EPS2ImagePreparator.class.getName());

  private final DviPage page;
  private final ViewSpec viewSpec;
  private final String postScriptData;
  private final String hash;

  public EPS2ImagePreparator(DviContextSupport dcs, DviPage page, ViewSpec viewSpec)
  throws DviException
  {
    super(dcs);
    this.page = page;
    this.viewSpec = viewSpec;
    DviToolkit utils = getDviContext().getDviToolkit();
    this.postScriptData = utils.getEmbeddedPostScript(page, viewSpec);
    this.hash = DviUtils.md5Hex(postScriptData);
  }

  public DviImage call() throws Exception
  {
    if (postScriptData == null) return null;
    
    File dir = getDviContext().getTemporaryDirectory();
    String key = getCacheKey();
    String keyHash = DviUtils.md5Hex(key);
    String basename = String.format("dvibrowser--%s--%08d", keyHash, page.getPageNumber());
    String imgExt = getViewSpec().getEpsImageFileExtension();
    File imgFile = new File(dir, basename + imgExt);
    if (!imgFile.exists()) {
      LOGGER.fine("Now creating postscript image for page " + page);
      LOGGER.fine("Using key=" + key);
      LOGGER.fine("Using keyHash=" + keyHash);
      LOGGER.finer("PostScriptData= " + postScriptData);
      File psFile = new File(dir, basename + ".ps");
      File tmpFile = File.createTempFile(basename, imgExt, dir);
      try {
        psFile.deleteOnExit();
        DviUtils.writeStringToFile(psFile, postScriptData);
        GhostscriptCommandBuilder gs = new GhostscriptCommandBuilder(this);
        gs.setInputFile(psFile);
        gs.setOutputFile(tmpFile);
        gs.setResolution(viewSpec.getEpsResolutionDpi());
        gs.setDevice(getViewSpec().getEpsImageDeviceName());
        CommandShell shell = gs.createCommandShell();
        // TODO: outsource the max line numbers to safe.
        final LineBuffer<String> stderr = new LineBuffer<String>(100);
        final LineBuffer<String> stdout = new LineBuffer<String>(100);
        shell.setHandler(new CommandShellHandler() {
          public void handleStderr(InputStream in) throws IOException {
            DviUtils.accumulateToLineBuffer(in, stderr);
          }
          public void handleStdin(OutputStream out) throws IOException {
            out.close();
          }
          public void handleStdout(InputStream in) throws IOException {
            DviUtils.accumulateToLineBuffer(in, stdout);
          }
        });
        int ret = shell.execute();
        if (ret == CommandShell.RETCODE_SUCCESS) {
          if (!tmpFile.renameTo(imgFile)) {
            throw new DviException("Failed to write image file: " + imgFile);
          }
          imgFile.deleteOnExit();
          LOGGER.fine("Saved image to " + imgFile);
        } else {
          LOGGER.warning("Ghostscript failed with retcode " + ret + ".");
          LOGGER.warning("Ghosctscript stdout: " + stdout);
          LOGGER.warning("Ghosctscript stderr: " + stderr);
        }
      } finally {
        LOGGER.fine("Finished creating postscript image for page " + page);
        if (DELETE_PSFILE_IMMEDIATELY) {
          psFile.delete();
        }
        tmpFile.delete();
      }
    }
    DviImage dviImage;
    try {
      BufferedImage image = ImageIO.read(imgFile);
      dviImage = null;
      if (image != null) {
        dviImage = new DviImage(image, viewSpec.getEpsResolutionDpi());
      }
    } catch (OutOfMemoryError e) {
      LOGGER.severe("Unable to render image: imgFile=" + imgFile + " filesize=" + imgFile.length());
      DviUtils.logStackTrace(LOGGER, Level.SEVERE, e);
      throw e;
    }
    
    return dviImage;
  }
  
  private static final boolean DELETE_PSFILE_IMMEDIATELY = false;

  public String getCacheKey()
  {
    // TODO: use viewSpec.
    String pageKey = null;
    try {
      DviDocument doc = page.getDocument();
      if (doc instanceof HasURL) {
        URL url = ((HasURL) doc).getURL();
        pageKey = url.toExternalForm() + "#page" + page.getPageNumber();
      }
    } catch (DviException e) {
      LOGGER.warning(e.toString());
      pageKey = page.getCacheKey();
    }
    return getClass().getName() + "--" + hash + "-" + pageKey;
  }

  public ViewSpec getViewSpec()
  {
    return viewSpec;
  }

  public DviPage getPage()
  {
    return page;
  }
}
