/*
 * utils - TemporaryInputStream.java - Copyright © 2010 David Roden
 *
 * 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 3 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.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package net.pterodactylus.util.io;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;

/**
 * An input stream implementation that copies a given input stream to a
 * temporary file and delivers the content of the temporary file at a later
 * time.
 *
 * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
 */
public class TemporaryInputStream extends FilterInputStream {

	/**
	 * Maps input streams to temporary files, for deletion on {@link #close()}.
	 */
	private static final Map<InputStream, File> streamFiles = new HashMap<InputStream, File>();

	/** Counter for streams per file. */
	private static final Map<File, Integer> fileCounts = new HashMap<File, Integer>();

	/**
	 * Creates a new temporary input stream.
	 *
	 * @param sourceInputStream
	 *            The input stream to copy to a temporary file
	 * @throws IOException
	 *             if an I/O error occurs
	 */
	public TemporaryInputStream(InputStream sourceInputStream) throws IOException {
		super(createFileInputStream(sourceInputStream));
	}

	/**
	 * Creates a new temporary input stream from the given temporary file.
	 *
	 * @param tempFile
	 *            The temporary file
	 * @throws FileNotFoundException
	 *             if the file can not be found
	 */
	private TemporaryInputStream(File tempFile) throws FileNotFoundException {
		super(new FileInputStream(tempFile));
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void close() throws IOException {
		super.close();
		synchronized (fileCounts) {
			File tempFile = streamFiles.remove(in);
			if (tempFile != null) {
				if (fileCounts.get(tempFile) > 0) {
					fileCounts.put(tempFile, fileCounts.get(tempFile) - 1);
				} else {
					fileCounts.remove(tempFile);
					tempFile.delete();
				}
			}
		}
	}

	/**
	 * Creates a new input stream from the temporary file that is backing this
	 * input stream. If the file has already been removed, this method will
	 * throw an exception.
	 *
	 * @return A new input stream
	 * @throws IOException
	 *             if an I/O error occurs
	 */
	public InputStream reopen() throws IOException {
		synchronized (fileCounts) {
			File tempFile = streamFiles.get(in);
			if (tempFile != null) {
				fileCounts.put(tempFile, fileCounts.get(tempFile) + 1);
				return new TemporaryInputStream(tempFile);
			}
			throw new FileNotFoundException("Temporary file has already disappeared.");
		}
	}

	/**
	 * Creates a temporary file, copies the given input stream to the temporary
	 * file, and creates an input stream reading from the temporary file. The
	 * returned input stream will delete the temporary file when its
	 * {@link #close()} method is called.
	 *
	 * @param sourceInputStream
	 *            The input stream to copy
	 * @return The copied input stream, ready for consumption
	 * @throws IOException
	 */
	private static InputStream createFileInputStream(InputStream sourceInputStream) throws IOException {
		File tempFile = File.createTempFile("utils-temp-", ".tmp");
		tempFile.deleteOnExit();
		FileOutputStream fileOutputStream = null;
		try {
			fileOutputStream = new FileOutputStream(tempFile);
			StreamCopier.copy(sourceInputStream, fileOutputStream);
		} catch (IOException ioe1) {
			throw ioe1;
		} finally {
			Closer.close(fileOutputStream);
		}
		FileInputStream fileInputStream = null;
		try {
			fileInputStream = new FileInputStream(tempFile);
			streamFiles.put(fileInputStream, tempFile);
			synchronized (fileCounts) {
				fileCounts.put(tempFile, 0);
			}
			return fileInputStream;
		} catch (IOException ioe1) {
			Closer.close(fileInputStream);
			tempFile.delete();
			throw ioe1;
		}
	}

}