/*
 * MegaMek - Copyright (C) 2004 Ben Mazur (bmazur@sev.org)
 *
 *  This program 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 2 of the License, or (at your option)
 *  any later version.
 *
 *  This program 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.
 */

package megamek.client.ui.swing.util;

import megamek.common.util.ItemFile;
import megamek.common.util.ItemFileFactory;

import java.awt.Image;
import java.awt.Toolkit;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;

/**
 * This class will produce <code>Image</code> objects from files.  If an
 * image file is inside of JAR and ZIP file, then it must save the contents
 * to a temporary file.
 * <p/>
 * <p/>
 * Created on January 18, 2004
 *
 * @author James Damour
 * @version 1
 */
public class ImageFileFactory implements ItemFileFactory {

    /**
     * Accepted image file extentions
     */
    private final static String JPG = "JPG", JPEG = "JPEG", GIF = "GIF", PNG = "PNG"; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$

    /**
     * Implement the Singleton pattern.
     */
    private ImageFileFactory() {
    }

    private static ImageFileFactory singleton = null;

    /**
     * Get the Singleton <code>ImageFileFactory</code>.
     *
     * @return the Singleton <code>ImageFileFactory</code>.
     */
    public static ImageFileFactory getInstance() {
        if (null == singleton) singleton = new ImageFileFactory();
        return singleton;
    }

    /**
     * Get the <code>ItemFile</code> for the given <code>File</code>.
     *
     * @param file - the input <code>File</code> object that will be read
     *             to produce the item.  This value must not be <code>null</code>.
     * @return an <code>ItemFile</code> for the given file.
     * @throws <code>IllegalArgumentException</code>
     *          if the <code>file</code>
     *          is <code>null</code>.
     */
    public ItemFile getItemFile(File file) throws IllegalArgumentException {

        // Validate the input.
        if (null == file) {
            throw new IllegalArgumentException
                    ("A null image file was passed."); //$NON-NLS-1$
        }

        // Construct an anonymous class that gets an Image for the file.
        final File entry = file;
        return new ItemFile() {

            private File file = entry; // copy the file entry
            private Image image = null; // cache the Image

            public Object getItem() throws Exception {
                // Cache the image on first use.
                if (null == image) {
                    String name = file.getAbsolutePath();
                    image = Toolkit.getDefaultToolkit().getImage(name);
                }
                // Return a copy of the image.
                return image.getScaledInstance(84, 72, Image.SCALE_FAST);
            } // End getItem()
        };

    }

    /**
     * Get the <code>ItemFile</code> for the given <code>ZipEntry</code> in
     * the <code>ZipFile</code>.
     *
     * @param zipEntry - the <code>ZipEntry</code> that will be read
     *                 to produce the item.  This value must not be <code>null</code>.
     * @param zipFile  - the <code>ZipFile</code> object that contains
     *                 the <code>ZipEntry</code> that will produce the item.
     *                 This value must not be <code>null</code>.
     * @return an <code>ItemFile</code> for the given zip file entry.
     * @throws <code>IllegalArgumentException</code>
     *          if either the
     *          <code>zipEntry</code> or the <code>zipFile</code> is
     *          <code>null</code>.
     */
    public ItemFile getItemFile(ZipEntry zipEntry, ZipFile zipFile)
            throws IllegalArgumentException {

        // Validate the input.
        if (null == zipEntry) {
            throw new IllegalArgumentException
                    ("A null ZIP entry was passed."); //$NON-NLS-1$
        }
        if (null == zipFile) {
            throw new IllegalArgumentException
                    ("A null ZIP file was passed."); //$NON-NLS-1$
        }

        // Construct an anonymous class that gets an Image for the file.
        final ZipEntry entry = zipEntry;
        final ZipFile source = zipFile;
        return new ItemFile() {

            private ZipEntry item = entry; // copy the ZipEntry
            private ZipFile zipFile = source; // copy the ZipFile
            private Image image = null; // cache the Image

            public Object getItem() throws Exception {

                // Cache the image on first use.
                if (null == image) {

                    // Get ready to read from the item.
                    InputStream in = new BufferedInputStream
                            (zipFile.getInputStream(item),
                                    (int) item.getSize());

                    // Make a buffer big enough to hold the item,
                    // read from the ZIP file, and write it to temp.
                    byte[] buffer = new byte[(int) item.getSize()];
                    in.read(buffer);

                    // Check the last 10 bytes.  I've been having
                    // some problems with incomplete image files,
                    // and I want to detect it early and give advice
                    // to players for dealing with the problem.
                    int index = (int) item.getSize() - 10;
                    while (item.getSize() > index) {
                        if (0 != buffer[index]) {
                            break;
                        }
                        index++;
                    }
                    if (item.getSize() <= index) {
                        throw new IOException
                                ("Error reading " + item.getName() + //$NON-NLS-1$
                                "\nYou may want to unzip " + //$NON-NLS-1$
                                zipFile.getName());
                    }

                    // Create the image from the buffer.
                    image = Toolkit.getDefaultToolkit()
                            .createImage(buffer);

                } // End get-image

                // Return a copy of the image.
                return image.getScaledInstance(84, 72, Image.SCALE_FAST);

            } // End getItem()
        };

    }

    /**
     * The method that must be implemented by any object that filters filenames
     * (i.e., selects a subset of filenames from a list of filenames).
     * <p/>
     * This definition is copied from <code>java.io.FilenameFilter</code>
     * for completeness.
     *
     * @param dir  - the <code>File</code> object of the directory containing
     *             the named file.
     * @param name - the <code>String</code> name of the file.
     */
    public boolean accept(File dir, String name) {

        // Convert the file name to upper case, and compare it to image
        // file extensions.  Yeah, it's a bit of a hack, but whatever.
        String ucName = name.toUpperCase();
        return (ucName.endsWith(JPG) ||
                ucName.endsWith(JPEG) ||
                ucName.endsWith(GIF) ||
                ucName.endsWith(PNG));
    }

    /**
     * The method that must be implemented by any object that filters filenames
     * within a <code>ZipFile</code> (i.e., selects a subset of filenames from
     * a list of filenames in a ZIP archive).
     *
     * @param zipFile - the <code>ZipFile</code> object that contains
     *                the named file's entry.
     * @param name    - the <code>String</code> name of the file.
     */
    public boolean accept(ZipFile zipFile, String name) {

        // Convert the file name to upper case, and compare it to image
        // file extensions.  Yeah, it's a bit of a hack, but whatever.
        String ucName = name.toUpperCase();
        return (ucName.endsWith(JPG) ||
                ucName.endsWith(JPEG) ||
                ucName.endsWith(GIF) ||
                ucName.endsWith(PNG));
    }

}
