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

import java.io.Closeable;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FilterOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.logging.Logger;
import java.util.zip.CRC32;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;


public class ZipBuilder
implements Closeable
{
  private static final Logger LOGGER = Logger.getLogger(ZipBuilder.class.getName());
  
  private final ZipOutputStream output;
  private final int method;

  public ZipBuilder(OutputStream out, int method)
  {
    if (!isValidMethod(method)) {
      throw new IllegalArgumentException("Unknown ZIP method: " + method);
    }
    this.method = method;
    this.output = createZipOutputStream(out, method);
  }
  
  public ZipBuilder(OutputStream os)
  {
    this(os, ZipOutputStream.DEFLATED);
  }
  
  protected ZipOutputStream createZipOutputStream(OutputStream out, int method)
  {
    ZipOutputStream zos = new ZipOutputStream(out);
    zos.setMethod(method);
    return zos;
  }
  
  protected ZipEntry createZipEntryFromFile(String entryName, File file) throws FileNotFoundException, IOException
  {
    ZipEntry ze = new ZipEntry(entryName);
    switch (getMethod()) {
    case ZipOutputStream.STORED:
      ze.setSize(file.length());
      CRC32 crc = DviUtils.getCRC32(new FileInputStream(file));
      ze.setCrc(crc.getValue());
      break;
    case ZipOutputStream.DEFLATED:
      break;
    default:
      // We have no chance to reach here since the constructor throws
      // exception when method is not any one of above.
      throw new IllegalStateException("Unknown ZIP method: " + getMethod());
    }
    return ze;
  }
  
  protected File createTempFile() throws IOException
  {
    // TODO: Let a manager to control the temporary file.
    File tmpFile = File.createTempFile("zip-builder", null);
    tmpFile.deleteOnExit();
    return tmpFile;
  }
  
  public void write(String entryName, InputStream is) throws IOException
  {
    File tmpFile = createTempFile();
    try {
      FileOutputStream fos = new FileOutputStream(tmpFile);
      DviUtils.copyStream(is, fos);
      fos.flush();
      fos.close();
      is.close();
      write(entryName, tmpFile);
    } finally {
      tmpFile.delete();
    }
  }
  
  public void write(String entryName, File file) throws IOException
  {
    ZipEntry ze = createZipEntryFromFile(entryName, file);
    output.putNextEntry(ze);
    DviUtils.copyStream(new FileInputStream(file), output);
  }
  
  public OutputStream openOutputStream(final String entryName) throws IOException
  {
    final File tmpFile = createTempFile();
    try {
      final boolean [] done = new boolean[1];
      done[0] = false;
      FileOutputStream fos = new FileOutputStream(tmpFile);
      FilterOutputStream os = new FilterOutputStream(fos) {
        public synchronized void close() throws IOException {
          // Note that this method might be called multiple times.
          // So we return quickly when called twice.
          if (done[0]) {
            return;
          }
          done[0] = true;
          super.close();
          try {
            ZipBuilder.this.write(entryName, tmpFile);
          } finally {
            tmpFile.delete();
          }
        }
      };
      return os;
    } catch (IOException ex) {
      tmpFile.delete();
      throw ex;
    } catch (RuntimeException ex) {
      tmpFile.delete();
      throw ex;
    } catch (Error ex) {
      tmpFile.delete();
      throw ex;
    }
  }
  
  public static boolean isValidMethod(int method) {
    if (method == ZipOutputStream.DEFLATED || method == ZipOutputStream.STORED) {
      return true; 
    }
    return false;
  }

  public ZipOutputStream getOutputStream() {
    return output;
  }

  public int getMethod() {
    return method;
  }

  public void close() throws IOException {
    output.close();
  }
}
