/*
 * Copyright 2010-2015 Institut Pasteur.
 * 
 * This file is part of Icy.
 * 
 * Icy is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * Icy is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with Icy. If not, see <http://www.gnu.org/licenses/>.
 */
package icy.util;

import icy.file.FileUtil;
import icy.network.NetworkUtil;
import icy.system.IcyExceptionHandler;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.Enumeration;
import java.util.zip.DataFormatException;
import java.util.zip.Deflater;
import java.util.zip.Inflater;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;

/**
 * @author Stephane
 */
public class ZipUtil
{
    /**
     * Compress the specified array of byte with given level of compression and return packed data.<br/>
     * 
     * @param packer
     *        the packer object, can be <code>null</code> in which case we create a new Deflater object
     * @param rawData
     *        raw data to compress
     * @param level
     *        level of compression where 0 is low and 9 is high (use -1 to keep current / use default level)
     */
    public static byte[] pack(Deflater packer, byte[] rawData, int level)
    {
        final Deflater compressor;

        if (packer != null)
        {
            compressor = packer;
            compressor.reset();
        }
        else
            compressor = new Deflater();

        if (level != -1)
            compressor.setLevel(level);

        // give data to compress
        compressor.setInput(rawData);
        compressor.finish();

        // create an expandable byte array to hold the compressed data.
        final ByteArrayOutputStream bos = new ByteArrayOutputStream(rawData.length);
        final byte[] buf = new byte[65536];

        // pack data
        while (!compressor.finished())
        {
            final int count = compressor.deflate(buf);

            // nothing more to do ? --> end here
            if (count == 0)
                break;

            bos.write(buf, 0, count);
        }

        try
        {
            bos.close();
        }
        catch (IOException e)
        {
            // we can freely ignore this one
        }

        // return packed data
        return bos.toByteArray();
    }

    /**
     * Compress the specified array of byte and return packed data
     */
    public static byte[] pack(byte[] rawData)
    {
        return pack(null, rawData, -1);
    }

    /**
     * Uncompress the specified array of byte and return unpacked data
     * 
     * @param unpacker
     *        the unpacker object, can be <code>null</code> in which case we create a new Inflater object
     * @param packedData
     *        packed data to uncompress
     * @throws DataFormatException
     */
    public static byte[] unpack(Inflater unpacker, byte[] packedData) throws DataFormatException
    {
        final Inflater decompressor;

        if (unpacker != null)
        {
            decompressor = unpacker;
            decompressor.reset();
        }
        else
            decompressor = new Inflater();

        // give the data to uncompress
        decompressor.setInput(packedData);

        // create an expandable byte array to hold the uncompressed data
        final ByteArrayOutputStream bos = new ByteArrayOutputStream(packedData.length);
        final byte[] buf = new byte[65536];

        // unpack data
        while (!decompressor.finished())
        {
            final int count = decompressor.inflate(buf);

            // nothing more to do ? --> end here
            if (count == 0)
                break;

            bos.write(buf, 0, count);
        }
        try
        {
            bos.close();
        }
        catch (IOException e)
        {
            // we can freely ignore this one
        }

        // return unpacked data
        return bos.toByteArray();
    }

    /**
     * Uncompress the specified array of byte and return unpacked data
     * 
     * @throws DataFormatException
     */
    public static byte[] unpack(byte[] packedData) throws DataFormatException
    {
        return unpack(null, packedData);
    }

    /**
     * Extract the specified zip file to the specified destination directory.
     * 
     * @param zipFile
     *        input zip file name
     * @param outputDirectory
     *        output directory name
     * @return true if file was correctly extracted, false otherwise
     */
    public static boolean extract(String zipFile, String outputDirectory)
    {
        boolean ok = true;

        try
        {
            final ZipFile file = new ZipFile(zipFile);
            final Enumeration<? extends ZipEntry> entries = file.entries();

            while (entries.hasMoreElements())
            {
                ZipEntry entry = entries.nextElement();

                if (entry.isDirectory())
                {
                    if (!FileUtil.createDir(outputDirectory + FileUtil.separator + entry.getName()))
                    {
                        System.err.println("ZipUtil.extract(" + zipFile + "," + outputDirectory + ") error :");
                        System.err.println("Can't create directory : '" + outputDirectory + FileUtil.separator
                                + entry.getName() + "'");
                        ok = false;
                        break;
                    }
                }
                else if (!FileUtil.save(outputDirectory + FileUtil.separator + entry.getName(),
                        NetworkUtil.download(file.getInputStream(entry)), true))
                {
                    System.err.println("ZipUtil.extract(" + zipFile + "," + outputDirectory + ") failed.");
                    ok = false;
                    break;
                }
            }

            file.close();
        }
        catch (IOException ioe)
        {
            System.err.println("ZipUtil.extract(" + zipFile + "," + outputDirectory + ") error :");
            IcyExceptionHandler.showErrorMessage(ioe, false);
            ok = false;
        }

        return ok;
    }

    /**
     * Extract the specified zip file in to default location.
     * 
     * @param zipFile
     *        input zip file name
     * @return true if file was correctly extracted, false otherwise
     */
    public static boolean extract(String zipFile)
    {
        return extract(zipFile, FileUtil.getDirectory(zipFile) + FileUtil.getFileName(zipFile, false));
    }

    /**
     * Verify that specified file is a valid ZIP file
     * 
     * @param zipFile
     *        input zip file name
     * @throws IOException
     *         if the input file is not a valid zip file.
     */
    public static void isValid(String zipFile) throws IOException
    {
        final ZipFile file = new ZipFile(zipFile);
        try
        {
            final Enumeration<? extends ZipEntry> entries = file.entries();

            while (entries.hasMoreElements())
            {
                ZipEntry entry = entries.nextElement();
                entry.getName();
            }
        }
        finally
        {
            file.close();
        }
    }

    /**
     * Verify that specified file is a valid ZIP file
     * 
     * @param zipFile
     *        input zip file name
     * @param showError
     *        indicate if the method should show the error (in the output console) if the verify operation failed.
     * @return true if the specified file is a valid ZIP file
     */
    public static boolean isValid(String zipFile, boolean showError)
    {
        try
        {
            isValid(zipFile);
        }
        catch (IOException e)
        {
            if (showError)
            {
                System.err.println("ZipUtil.isValid(" + zipFile + ") error :");
                IcyExceptionHandler.showErrorMessage(e, false);
            }

            return false;
        }

        return true;
    }
}