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

import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileOutputStream;
import java.io.FilterOutputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.imageio.ImageIO;

import dvi.DviException;
import dvi.DviObject;
import dvi.DviPaperSize;
import dvi.DviRect;
import dvi.DviResolution;
import dvi.api.DviContext;
import dvi.api.DviContextSupport;
import dvi.api.DviDocument;
import dvi.api.DviPage;
import dvi.ctx.DefaultDviContext;
import dvi.gui.swing.ViewSpec;
import dvi.image.split.ImageFileConfig;
import dvi.util.DviUtils;
import dvi.util.ZipBuilder;

public class ConvertToImage
extends DviObject
{
  private static final String OPT_SHRINK_FACTOR = "--shrink-factor=";
  private static final String OPT_DPI = "--dpi=";
  private static final String OPT_OUTPUT_FILE = "--output-file=";
  private static final String OPT_PAPER_SIZE = "--paper-size=";
  private static final String OPT_USE_BBOX = "--use-bbox=";
  private static final String OPT_PADDING = "--padding=";
  private static final Logger LOGGER = Logger.getLogger(ConvertToImage.class
      .getName());
  
  public static class Config
  extends DviObject
  {
    private final ViewSpec viewSpec;
    private final ArrayList<File> inputs = new ArrayList<File>();
    private boolean useBoundingBox;
    private File outputFile;
    private DviPaperSize paperSize;
    private ImageFileConfig imageFileConfig;
    private int padding;
    
    public Config(DviContextSupport dcs) {
      super(dcs);
      viewSpec = new ViewSpec(dcs);
      try {
        setPaperSize(getDviContext().getDefaultPaperSize());
      } catch (DviException e) {
        DviUtils.logStackTrace(LOGGER, Level.WARNING, e);
        setPaperSize(DviPaperSize.FALLBACK);
      }
      // TODO: outsource the config.
      imageFileConfig = ImageFileConfig.PNG;
      padding = 0;
    }
    
    public void setDpi(int dpi)
    {
      viewSpec.setResolution(new DviResolution(dpi, viewSpec.getResolution().shrinkFactor()));
    }
    
    public void setShrinkFactor(int sf)
    {
      viewSpec.setResolution(new DviResolution(viewSpec.getResolution().dpi(), sf));
    }
    
    public ViewSpec getViewSpec() {
      return viewSpec;
    }
    
    public void setUseBoundingBox(boolean crop) {
      this.useBoundingBox = crop;
    }
    
    public boolean useBoundingBox() { return useBoundingBox; }
    
    public void setOutputFile(File outputFile) {
      if (outputFile != null) {
        this.outputFile = outputFile;
      } else {
        this.outputFile = null;
      }
    }
    
    public void addInputFile(File file)
    {
      if (file != null) {
        inputs.add(file);
      } else {
        LOGGER.warning("Input file is null. Ignored.");
      }
    }

    public File getOutputFile() {
      return outputFile;
    }
    
    public DviPaperSize getPaperSize() {
      return paperSize;
    }
    

    public ArrayList<File> getInputs() {
      return inputs;
    }
    
    public void parseArguments(String [] args) throws DviException
    {
      final DviContext ctx = getDviContext();
      for (int i=0; i<args.length; i++) {
        String a = args[i];
        if (a.startsWith(OPT_DPI)) {
          int dpi = Integer.parseInt(a.substring(OPT_DPI.length()));
          setShrinkFactor(dpi);
        } else if (a.startsWith(OPT_SHRINK_FACTOR)) {
          int sf = Integer.parseInt(a.substring(OPT_SHRINK_FACTOR.length()));
          setShrinkFactor(sf);
        } else if (a.startsWith(OPT_PADDING)) {
          int padding = Integer.parseInt(a.substring(OPT_PADDING.length()));
          setPaddingSize(padding);
        } else if (a.startsWith(OPT_PAPER_SIZE)) {
          String s = a.substring(OPT_PAPER_SIZE.length());
          DviPaperSize paperSize = ctx.findPaperSizeByName(s);
          if (paperSize == null) {
            throw new DviException("Unrecognized papersize: " + s);
          }
        } else if (a.startsWith(OPT_OUTPUT_FILE)) {
          String s = a.substring(OPT_OUTPUT_FILE.length()).trim();
          if (!"".equals(s)) {
            setOutputFile(new File(s));
          } else {
            throw new DviException("Output filename is empty.");
          }
        } else if (a.startsWith(OPT_USE_BBOX)) {
          String s = a.substring(OPT_USE_BBOX.length()).trim();
          if ("yes".equalsIgnoreCase(s)) {
            setUseBoundingBox(true);
          } else if ("no".equalsIgnoreCase(s)) {
              setUseBoundingBox(false);
          } else {
            throw new DviException("Invalid parameter value: " + a);
          }
        } else if (a.startsWith("-")) {
          throw new DviException("Unrecognized commandline option: " + a);
        } else {
          for (; i<args.length; i++) {
            a = args[i];
            addInputFile(new File(a));
          }
        }
      }
    }

    private void setPaddingSize(int padding) {
      this.setPadding(padding);
    }

    public void setPaperSize(DviPaperSize paperSize) {
      this.paperSize = paperSize;
    }
    
    public ImageFileConfig getImageFileConfig()
    {
      return imageFileConfig;
    }

    public void setImageFileConfig(ImageFileConfig imageFileConfig) {
      this.imageFileConfig = imageFileConfig;
    }
    
    public static String getUsage()
    {
      StringWriter sw = new StringWriter();
      PrintWriter pw = new PrintWriter(sw);
      pw.println("usage: java " + ConvertToImage.class.getName() + " [options] <input-files>");
      pw.println("  options: ");
      pw.println("    --dpi=N            Set output DPI to N");
      pw.println("    --shrink-factor=N  Set shrink factor to N (1--1024)");
      pw.println("    --output-file=F    Set output zip file to F");
      pw.println("    --padding=N        Set padding size to N");
      return sw.toString();
    }
    
    public void showUsage()
    {
      System.err.println(getUsage());
    }

    public boolean isValid() {
      return getInputs().size() > 0;
    }

    public void setPadding(int padding) {
      this.padding = padding;
    }

    public int getPadding() {
      return padding;
    }
  }
  
  public ConvertToImage(DviContextSupport dcs) {
    super(dcs);
  }

  public int convert(Config config) throws Exception {
    DviContext ctx = getDviContext();
    ViewSpec vs = config.getViewSpec();
    DviResolution res = vs.getResolution();
    File outputFile = config.getOutputFile();
    OutputStream os = null;
    if (outputFile != null) {
      if (!outputFile.getName().toLowerCase().endsWith(".zip")) {
        outputFile = new File(outputFile.getParentFile(), outputFile.getName() + ".zip");
      }
      os = new FileOutputStream(outputFile);
      LOGGER.info("Writing outputs to " + outputFile);
    } else {
      os = new FilterOutputStream(System.out) {
        @Override
        public void close()
        {
          // We don't close the stdout.
        }
      };
    }
    ZipBuilder zb = new ZipBuilder(os);
    
    try {
      for (File file : config.getInputs()) {
        try {
          DviDocument doc = ctx.openDviDocument(file);
          LOGGER.info("Processing file: " + file);
          for (int i=0; i<doc.getTotalPages(); i++) {
            DviPage page = doc.getPage(i);
            LOGGER.info("Processing page " + (i + 1) + "/" + doc.getTotalPages());
            DviRect bbox;
            if (config.useBoundingBox()) {
              bbox = 
                ctx.getDviToolkit().computeBoundingBox(page, res);
            } else {
              bbox = config.getPaperSize().toBoundingBox(res);
            }
            bbox = bbox.addPadding(config.getPadding());
            
            BufferedImage img = ctx.getDviToolkit().renderToBufferedImage(page, bbox, vs);
            if (img == null) {
              throw new DviException("Failed to render page " + (i + 1));
            }
            
            OutputStream imageOut = zb.openOutputStream
              (String.format("%04d%s", i+1, config.getImageFileConfig().getImageExtension()));
            try {
              ImageIO.write(img, config.getImageFileConfig().getImageType(), imageOut);
            } finally {
              DviUtils.silentClose(imageOut);
            }
          }
        } catch (DviException e) {
          DviUtils.logStackTrace(LOGGER, Level.SEVERE, e);
          throw e;
        }
      }
    } finally {
      DviUtils.silentClose(zb);
    }
    return 0;
  }

  public static void main(String[] args)
  {
    try {
      DviContext ctx = new DefaultDviContext();
      Config config = new Config(ctx);
      config.parseArguments(args);
      
      if (!config.isValid()) {
        config.showUsage();
        throw new DviException
          ("Unrecognized command line: " + DviUtils.join(" ", args));
      }
      
      ConvertToImage app = new ConvertToImage(ctx);
      int retcode = app.convert(config);
      
      LOGGER.info("Finished.");
      System.exit(retcode);
    } catch (Exception e) {
      DviUtils.logStackTrace(LOGGER, Level.WARNING, e);
      System.exit(1);
    }
  }

}
