/*
 * @(#)Gzip.java
 * @throws ArchiveException
 *
 * Copyright (c) JSCAPE LLC.
 *
 * This software is the confidential and proprietary information of
 * JSCAPE. ("Confidential Information").  You shall not disclose such
 * Confidential Information and shall use it only in accordance with
 * the terms of the license agreement you entered into with JSCAPE.
 */
package com.jscape.zip;


import com.jscape.util.IOTools;

import java.io.*;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;


/**
 * Implements the basic functionality of GZIP compression and decompression.
 */
public class Gzip
        implements Archive {
    /**
     * Buffer size.
     */
    public static final int BUF_SIZE = 8192 * 2;

    /** */
    private final transient List<ArchiveListener> listeners = new CopyOnWriteArrayList<ArchiveListener>();


    /**
     * Creates a new Gzip instance.
     */
    public Gzip() {
        
    }

    /**
     * GZIP the input file.
     *
     * @param file the file to compress
     *
     * @throws ArchiveException if error occurs
     */
    public void compress(File file)
            throws ArchiveException {
        try {
            File dir = new File(file.getParent());
            this.compress(file, dir);
            new File(dir.getPath() + File.separator + file.getName() + ".gz");
        } catch (Exception e) {
            throw new ArchiveException(e);
        }
    }

    /**
     * GZIP the input file to specified output file or directory.
     *
     * @param file   the file to compress
     * @param output the output file or directory to write to
     *
     * @throws ArchiveException if error occurs
     */
    public void compress(File file, File output)
            throws ArchiveException {
        FileInputStream in = null;
        FileOutputStream out = null;

        try {
            // Create the output gzip archive from the name of the input file
            // and the output directory. Add the ".gz" file type at the end.
            File gzipOutput;

            if (output.isDirectory()) {
                gzipOutput = new File(output, file.getName() + ".gz");
            } else {
                gzipOutput = !output.getName().contains(".gz") ? new File(output.getAbsolutePath() + ".gz") : output;
            }

            // Create a buffered gzip output stream to the archive.
            out = new FileOutputStream(gzipOutput);
            GZIPOutputStream gzipOut = new GZIPOutputStream(new BufferedOutputStream(out));


            // Now compress the file using the GZIP output stream.
            // Need to use a buffer to read the file.
            byte[] inputBuffer = new byte[BUF_SIZE];
            int len = 0;

            // Create a buffered stream from the input file.
            in = new FileInputStream(file);
            BufferedInputStream source = new BufferedInputStream(in, BUF_SIZE);

            // Read from the input stream and write to the gzip output stream
            // which will compress the output before writing to the output file.
            while ((len = source.read(inputBuffer, 0, BUF_SIZE)) != -1) {
                gzipOut.write(inputBuffer, 0, len);
            }

            IOTools.close(source);
            IOTools.close(gzipOut);

            fireCompressedEvent(new ArchiveCompressedEvent(this, file));
        } catch (Exception e) {
            throw new ArchiveException(e);
        } finally {
            IOTools.close(in);
            IOTools.close(out);
        }
    }

    /**
     * GUNZIP the input archive.
     *
     * @param file the file to decompress.
     *
     * @throws ArchiveException if error occurs
     */
    public void decompress(File file)
            throws ArchiveException {
        File dir = new File(file.getParent());
        this.decompress(file, dir);
    }

    /**
     * GUNZIP the input archive to specified output directory.
     * Assumes that the input file name ends with .gz extension.
     *
     * @param file   the file to decompress
     * @param output the directory for decompress the GZIP file
     *
     * @throws ArchiveException if error occurs
     */
    public void decompress(File file, File output)
            throws ArchiveException {
        FileInputStream in = null;
        FileOutputStream out = null;

        try {
            in = new FileInputStream(file);
            // Create a buffered gzip input stream to the archive file.
            GZIPInputStream gzipIn = new GZIPInputStream(new BufferedInputStream(in));

            // Use the name of the archive for the output file name but
            // with ".gz" stripped off.
            String fileInputName = file.getName();
            String fileOutputName = fileInputName.substring(0, fileInputName.length() - 3);

            // Create the decompressed output file.
            File outputFile = new File(output, fileOutputName);

            // Decompress the gzipped file by reading it via
            // the GZIP input stream. Will need a buffer.
            byte[] inputBuffer = new byte[BUF_SIZE];
            int len = 0;

            // Create a buffered output stream to the file.
            out = new FileOutputStream(outputFile);
            BufferedOutputStream destination = new BufferedOutputStream(out, BUF_SIZE);

            // Now read from the gzip stream, which will decompress the data,
            // and write to the output stream.
            while ((len = gzipIn.read(inputBuffer, 0, BUF_SIZE)) != -1) {
                destination.write(inputBuffer, 0, len);
            }

            destination.flush(); // Insure that all data is written to the output.
            IOTools.close(gzipIn);
            IOTools.close(destination);

            fireDecompressedEvent(new ArchiveDecompressedEvent(this, outputFile));
        } catch (Exception e) {
            throw new ArchiveException(e);
        } finally {
            IOTools.close(in);
            IOTools.close(out);
        }
    }

    /**
     * Adds listeners.
     *
     * @param listener the listener
     *
     * @see Archive#addArchiveListener(ArchiveListener)     *
     */
    public void addArchiveListener(ArchiveListener listener) {
        this.listeners.add(listener);
    }

    /**
     * Remove listeners.
     *
     * @param listener the listener
     *
     * @see Archive#removeArchiveListener(ArchiveListener)
     */
    public void removeArchiveListener(ArchiveListener listener) {
        this.listeners.remove(listener);
    }

    /**
     * Fired when GZIP archive is compressed.
     *
     * @param ev event to fire.
     */
    protected void fireCompressedEvent(ArchiveCompressedEvent ev) {
        for (ArchiveListener listener : listeners) {
            listener.compressed(ev);
        }
    }

    /**
     * Fired when GZIP archive is decompressed.
     *
     * @param ev event to fire.
     */
    protected void fireDecompressedEvent(ArchiveDecompressedEvent ev) {
        for (ArchiveListener listener : listeners) {
            listener.decompressed(ev);
        }
    }
}
