/*
 *    GeoTools - OpenSource mapping toolkit
 *    http://geotools.org
 *    (C) 2003-2006, Geotools Project Managment Committee (PMC)
 *    (C) 2001, Institut de Recherche pour le D�veloppement
 *
 *    This library is free software; you can redistribute it and/or
 *    modify it under the terms of the GNU Lesser General Public
 *    License as published by the Free Software Foundation; either
 *    version 2.1 of the License, or (at your option) any later version.
 *
 *    This library 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
 *    Lesser General Public License for more details.
 */
package org.geotools.image.io;

import java.io.*; // Many imports, including some for javadoc only.
import java.net.URI;
import java.net.URL;
import java.net.URLConnection;

import javax.imageio.spi.ImageReaderSpi;
import javax.imageio.stream.ImageInputStream;

import org.geotools.util.logging.Logging;
import org.geotools.resources.Utilities;
import org.geotools.resources.i18n.ErrorKeys;


/**
 * Base class for simple image decoders. This class provides a {@link #getInputStream} method,
 * which returns the {@linkplain #input input} as an {@link InputStream} for convenience.
 * Different kinds of input like {@linkplain File} or {@linkplain URL} are automatically
 * handled.
 *
 * @since 2.4
 * @source $URL: http://svn.geotools.org/geotools/tags/2.4.1/modules/unsupported/coverageio/src/main/java/org/geotools/image/io/StreamImageReader.java $
 * @version $Id: StreamImageReader.java 27908 2007-11-15 16:29:28Z desruisseaux $
 * @author Martin Desruisseaux
 */
public abstract class StreamImageReader extends GeographicImageReader {
    /**
     * The stream to {@linkplain #close close} on {@link #setInput(Object,boolean,boolean)
     * setInput(...)}, {@link #reset} or {@link #dispose} method invocation. This stream is
     * typically an {@linkplain InputStream input stream} or a {@linkplain Reader reader}
     * created by {@link #getInputStream} or similar methods in subclasses.
     * <p>
     * This field is never equals to the user-specified {@linkplain #input input}, since the
     * usual {@link javax.imageio.ImageReader} contract is to <strong>not</strong> close the
     * user-provided stream. It is set to a non-null value only if a stream has been created
     * from an other user object like {@link File} or {@link URL}.
     *
     * @see #getInputStream
     * @see org.geotools.image.io.text.TextImageReader#getReader
     * @see #close
     *
     * @todo The field type will be changed to {@link Closeable} when we will be allowed
     *       to compile for J2SE 1.5.
     */
    protected Object closeOnReset;

    /**
     * {@link #input} as an input stream, or {@code null} if none.
     *
     * @see #getInputStream
     */
    private InputStream stream;

    /**
     * The stream position when {@link #setInput} is invoked.
     */
    private long streamOrigin;

    /**
     * Constructs a new image reader.
     *
     * @param provider The {@link ImageReaderSpi} that is invoking this constructor,
     *        or {@code null} if none.
     */
    protected StreamImageReader(final ImageReaderSpi provider) {
        super(provider);
    }

    /**
     * Sets the input source to use. Input may be one of the following object:
     * {@link File}, {@link URL}, {@link Reader} (for ASCII data), {@link InputStream} or
     * {@link ImageInputStream}. If {@code input} is {@code null}, then any currently
     * set input source will be removed.
     *
     * @param input           The input object to use for future decoding.
     * @param seekForwardOnly If {@code true}, images and metadata may only be read
     *                        in ascending order from this input source.
     * @param ignoreMetadata  If {@code true}, metadata may be ignored during reads.
     *
     * @see #getInput
     * @see #getInputStream
     */
    //@Override
    public void setInput(final Object  input,
                         final boolean seekForwardOnly,
                         final boolean ignoreMetadata)
    {
        closeSilently();
        super.setInput(input, seekForwardOnly, ignoreMetadata);
        if (input instanceof ImageInputStream) {
            try {
                streamOrigin = ((ImageInputStream) input).getStreamPosition();
            } catch (IOException exception) {
                streamOrigin = 0;
                Logging.unexpectedException(LOGGER.getName(),
                        StreamImageReader.class, "setInput", exception);
            }
        }
    }

    /**
     * Returns the stream length in bytes, or {@code -1} if unknown. This method checks the
     * {@linkplain #input input} type and invokes one of {@link File#length()},
     * {@link ImageInputStream#length()} ou {@link URLConnection#getContentLength()} method
     * accordingly.
     *
     * @return The stream length, or -1 is unknown.
     * @throws IOException if an I/O error occured.
     */
    protected long getStreamLength() throws IOException {
        final Object input = getInput();
        if (input instanceof ImageInputStream) {
            long length = ((ImageInputStream) input).length();
            if (length >= 0) {
                length -= streamOrigin;
            }
            return length;
        }
        if (input instanceof File) {
            return ((File) input).length();
        }
        if (input instanceof URL) {
            return ((URL) input).openConnection().getContentLength();
        }
        if (input instanceof URLConnection) {
            return ((URLConnection) input).getContentLength();
        }
        return -1;
    }

    /**
     * Returns the {@linkplain #input input} as an {@linkplain InputStream input stream} object.
     * If the input is already an input stream, it is returned unchanged. Otherwise this method
     * creates a new {@linkplain InputStream input stream} (usually <strong>not</strong>
     * {@linkplain BufferedInputStream buffered}) from {@link File}, {@link URL},
     * {@link URLConnection} or {@link ImageInputStream} inputs.
     * <p>
     * This method creates a new {@linkplain InputStream input stream} only when first invoked.
     * All subsequent calls will returns the same instance. Consequently, the returned stream
     * should never be closed by the caller. It may be {@linkplain #close closed} automatically
     * when {@link #setInput setInput(...)}, {@link #reset()} or {@link #dispose()} methods are
     * invoked.
     *
     * @return {@link #getInput} as an {@link InputStream}. This input stream is usually
     *         not {@linkplain BufferedInputStream buffered}.
     * @throws IllegalStateException if the {@linkplain #input input} is not set.
     * @throws IOException If the input stream can't be created for an other reason.
     *
     * @see #getInput
     * @see org.geotools.image.io.text.TextImageReader#getReader
     */
    protected InputStream getInputStream() throws IllegalStateException, IOException {
        if (stream == null) {
            final Object input = getInput();
            if (input == null) {
                throw new IllegalStateException(getErrorResources().getString(ErrorKeys.NO_IMAGE_INPUT));
            }
            if (input instanceof InputStream) {
                stream = (InputStream) input;
                closeOnReset = null; // We don't own the stream, so don't close it.
            } else if (input instanceof ImageInputStream) {
                stream = new InputStreamAdapter((ImageInputStream) input);
                closeOnReset = null; // We don't own the ImageInputStream, so don't close it.
            } else if (input instanceof String) {
                stream = new FileInputStream((String) input);
                closeOnReset = stream;
            } else if (input instanceof File) {
                stream = new FileInputStream((File) input);
                closeOnReset = stream;
            } else if (input instanceof URI) {
                stream = ((URI) input).toURL().openStream();
                closeOnReset = stream;
            } else if (input instanceof URL) {
                stream = ((URL) input).openStream();
                closeOnReset = stream;
            } else if (input instanceof URLConnection) {
                stream = ((URLConnection) input).getInputStream();
                closeOnReset = stream;
            } else {
                throw new IllegalStateException(getErrorResources().getString(ErrorKeys.ILLEGAL_CLASS_$2,
                        Utilities.getShortClassName(input),
                        Utilities.getShortClassName(InputStream.class)));
            }
        }
        return stream;
    }

    /**
     * Closes the input stream created by {@link #getInputStream()}. This method does nothing
     * if the input stream is the {@linkplain #input input} instance given by the user rather
     * than a stream created by this class from a {@link File} or {@link URL} input.
     * <p>
     * This method is invoked automatically by {@link #setInput(Object,boolean,boolean)
     * setInput(...)}, {@link #reset}, {@link #dispose} or {@link #finalize} methods and
     * doesn't need to be invoked explicitly. It has protected access only in order to
     * allow overriding by subclasses.
     *
     * @throws IOException if an error occured while closing the stream.
     *
     * @see #closeOnReset
     */
    //@Override
    protected void close() throws IOException {
        if (closeOnReset != null) {
            // TODO: replace the remaining of this block by the following line
            //       when we will be allowed to compile for J2SE 1.5.
            //closeOnReset.close();
            if (closeOnReset instanceof InputStream) {
                ((InputStream) closeOnReset).close();
            }
            if (closeOnReset instanceof Reader) {
                ((Reader) closeOnReset).close();
            }
        }
        closeOnReset = null;
        stream       = null;
        super.close();
    }

    /**
     * Invokes {@link #close} and log the exception if any. This method is invoked from
     * methods that do not allow {@link IOException} to be thrown. Since we will not use
     * the stream anymore after closing it, it should not be a big deal if an error occured.
     */
    private void closeSilently() {
        try {
            close();
        } catch (IOException exception) {
            Logging.unexpectedException(LOGGER.getName(), getClass(), "close", exception);
        }
    }

    /**
     * Restores the {@code StreamImageReader} to its initial state. If an input stream were
     * created by a previous call to {@link #getInputStream}, it will be {@linkplain #close
     * closed} before to reset this reader.
     */
    //@Override
    public void reset() {
        closeSilently();
        super.reset();
    }

    /**
     * Allows any resources held by this reader to be released. If an input stream were created
     * by a previous call to {@link #getInputStream}, it will be {@linkplain #close closed}
     * before to dispose this reader.
     */
    //@Override
    public void dispose() {
        closeSilently();
        super.dispose();
    }

    /**
     * Closes the streams. This method is automatically invoked by the garbage collector.
     */
    //@Override
    protected void finalize() throws Throwable {
        close();
        super.finalize();
    }




    /**
     * Service provider interface (SPI) for {@link StreamImageReader}s.
     *
     * @since 2.4
     * @source $URL: http://svn.geotools.org/geotools/tags/2.4.1/modules/unsupported/coverageio/src/main/java/org/geotools/image/io/StreamImageReader.java $
     * @version $Id: StreamImageReader.java 27908 2007-11-15 16:29:28Z desruisseaux $
     * @author Martin Desruisseaux
     */
    public static abstract class Spi extends ImageReaderSpi {
        /**
         * List of legal input types for {@link StreamImageReader}.
         */
        private static final Class[] INPUT_TYPES = new Class[] {
            File.class,
            URI.class,
            URL.class,
            URLConnection.class,
            InputStream.class,
            ImageInputStream.class,
            String.class // To be interpreted as file path.
        };

        /**
         * Constructs a quasi-blank {@code StreamImageReader.Spi}. It is up to the subclass to
         * initialize instance variables in order to provide working versions of all methods.
         * This constructor provides the following defaults:
         *
         * <ul>
         *   <li>{@link #inputTypes} = {{@link File}, {@link URL}, {@link URLConnection},
         *       {@link InputStream}, {@link ImageInputStream}, {@link String}}</li>
         * </ul>
         *
         * For efficienty reasons, the above fields are initialized to shared arrays. Subclasses
         * can assign new arrays, but should not modify the default array content.
         */
        public Spi() {
            inputTypes = INPUT_TYPES;
        }
    }
}
