package edu.berkeley.nlp.lm.io;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.Reader;
import java.io.Writer;
import java.nio.channels.Channels;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;

import edu.berkeley.nlp.lm.util.StrUtils;

/**
 * Some IO utility functions. Naming convention: "Hard" means that the function
 * throws a RuntimeException upon failure, "Easy" means it returns null.
 * 
 * @author adampauls
 * @author Percy Liang
 * 
 */
public class IOUtils
{

	public static BufferedReader openIn(final String path) throws IOException {
		return openIn(new File(path));
	}

	public static BufferedReader openIn(final File path) throws IOException {
		InputStream is = getBufferedInputStream(path);
		if (path.getName().endsWith(".gz")) is = new GZIPInputStream(is);
		return new BufferedReader(getReader(is));
	}

	public static BufferedReader openInHard(final String path) {
		return openInHard(new File(path));
	}

	public static BufferedReader openInHard(final File path) {
		try {
			return openIn(path);
		} catch (final Exception e) {
			throw new RuntimeException(e);
		}
	}

	public static PrintWriter openOut(final String path) throws IOException {
		return openOut(new File(path));
	}

	public static PrintWriter openOut(final File path) throws IOException {
		OutputStream os = new FileOutputStream(path);
		if (path.getName().endsWith(".gz")) os = new GZIPOutputStream(os);
		return new PrintWriter(getWriter(os));
	}

	public static PrintWriter openOutEasy(final String path) {
		if (StrUtils.isEmpty(path)) return null;
		return openOutEasy(new File(path));
	}

	public static PrintWriter openOutEasy(final File path) {
		if (path == null) return null;
		try {
			return openOut(path);
		} catch (final Exception e) {
			return null;
		}
	}

	public static PrintWriter openOutHard(final String path) {
		return openOutHard(new File(path));
	}

	public static PrintWriter openOutHard(final File path) {
		try {
			return openOut(path);
		} catch (final Exception e) {
			throw new RuntimeException(e);
		}
	}

	// }

	// Java binary serialization {
	// openObjIn
	public static ObjectInputStream openObjIn(final String path) throws IOException {
		return openObjIn(new File(path));
	}

	public static ObjectInputStream openObjIn(final File path) throws IOException {
		final InputStream fis = getBufferedInputStream(path);
		return path.getName().endsWith(".gz") ? new ObjectInputStream(new GZIPInputStream(fis)) : new ObjectInputStream(fis);
	}

	/**
	 * @param path
	 * @return
	 * @throws FileNotFoundException
	 */
	private static BufferedInputStream getBufferedInputStream(final File path) throws FileNotFoundException {
		//		return new BufferedInputStream((new FileInputStream(path)));
		return new BufferedInputStream(Channels.newInputStream(new FileInputStream(path).getChannel()));
	}

	// openObjOut
	public static ObjectOutputStream openObjOut(final String path) throws IOException {
		return openObjOut(new File(path));
	}

	public static ObjectOutputStream openObjOut(final File path) throws IOException {
		final OutputStream fos = new BufferedOutputStream(new FileOutputStream(path));
		return path.getName().endsWith(".gz") ? new ObjectOutputStream(new GZIPOutputStream(fos)) : new ObjectOutputStream(fos);
	}

	// readObjFile
	public static Object readObjFile(final String path) throws IOException, ClassNotFoundException {
		return readObjFile(new File(path));
	}

	public static Object readObjFile(final File path) throws IOException, ClassNotFoundException {
		final ObjectInputStream in = openObjIn(path);
		final Object obj = in.readObject();
		in.close();
		return obj;
	}

	public static Object readObjFileEasy(final String path) {
		if (StrUtils.isEmpty(path)) return null;
		return readObjFileEasy(new File(path));
	}

	public static Object readObjFileEasy(final File path) {
		if (path == null) return null;
		try {
			return readObjFile(path);
		} catch (final Exception e) {
			return null;
		}
	}

	public static Object readObjFileHard(final String path) {
		return readObjFileHard(new File(path));
	}

	public static Object readObjFileHard(final File path) {
		try {
			return readObjFile(path);
		} catch (final Exception e) {
			throw new RuntimeException(e);
		}
	}

	// writeObjFile
	public static void writeObjFile(final File path, final Object obj) throws IOException {
		final ObjectOutputStream out = openObjOut(path);
		out.writeObject(obj);
		out.close();
	}

	public static boolean writeObjFileEasy(final File path, final Object obj) {
		if (path == null) return false;
		try {
			writeObjFile(path, obj);
			return true;
		} catch (final Exception e) {
			return false;
		}
	}

	public static void writeObjFileHard(final String path, final Object obj) {
		writeObjFileHard(new File(path), obj);
	}

	public static void writeObjFileHard(final File path, final Object obj) {
		try {
			writeObjFile(path, obj);
		} catch (final Exception e) {
			throw new RuntimeException(e);
		}
	}

	// }

	public static boolean closeEasy(final BufferedReader in) {
		try {
			in.close();
			return true;
		} catch (final IOException e) {
			return false;
		}
	}

	// Copying files {
	// Return number of bytes copied
	public static int copy(final InputStream in, final OutputStream out) throws IOException {
		final byte[] buf = new byte[16384];
		int total = 0, n;
		while ((n = in.read(buf)) != -1) {
			total += n;
			out.write(buf, 0, n);
		}
		out.flush();
		return total;
	}

	// Return number of characters copied
	public static int copy(final Reader in, final Writer out) throws IOException {
		final char[] buf = new char[16384];
		int total = 0, n;
		while ((n = in.read(buf)) != -1) {
			total += n;
			out.write(buf, 0, n);
		}
		out.flush();
		return total;
	}

	// }

	public static Iterator<String> lineIterator(final String path) throws IOException {
		final BufferedReader reader = IOUtils.openIn(path);
		return lineIterator(reader);
	}

	/**
	 * @param reader
	 * @return
	 */
	public static Iterator<String> lineIterator(final BufferedReader reader) {
		return new Iterator<String>()
		{

			private String line;

			@Override
			public boolean hasNext() {
				// TODO Auto-generated method stub
				try {
					return nextLine();
				} catch (final Exception e) {
					e.printStackTrace();
				}
				return false;
			}

			private boolean nextLine() throws IOException {
				if (line != null) { return true; }
				line = reader.readLine();
				if (line == null) reader.close();
				return line != null;
			}

			@Override
			public String next() {
				// TODO Auto-generated method stub
				try {
					nextLine();
					final String retLine = line;
					line = null;
					return retLine;
				} catch (final IOException e) {
					throw new RuntimeException();
				}
			}

			@Override
			public void remove() {
				// TODO Auto-generated method stub
				throw new RuntimeException("remove() not supported");
			}

		};
	}

	public static List<String> readLines(final BufferedReader in) throws IOException {
		String line;
		final List<String> lines = new ArrayList<String>();
		while ((line = in.readLine()) != null)
			lines.add(line);
		return lines;
	}

	public static List<String> readLinesEasy(final String path) {
		try {
			return readLines(path);
		} catch (final IOException e) {
			return Collections.emptyList();
		}
	}

	public static List<String> readLinesHard(final String path) {
		try {
			return readLines(path);
		} catch (final IOException e) {
			throw new RuntimeException(e);
		}
	}

	// Return the first line, null if it doesn't exist
	public static String readLine(final String path) throws IOException {
		final BufferedReader in = IOUtils.openIn(path);
		final String line = in.readLine();
		in.close();
		return line;
	}

	public static String readLineEasy(final String path) {
		try {
			return readLine(path);
		} catch (final IOException e) {
			return null;
		}
	}

	public static List<String> readLines(final String path) throws IOException {

		final BufferedReader in = IOUtils.openIn(path);
		final List<String> list = readLines(in);
		in.close();
		return list;
	}

	//private static String charEncoding = "ISO-8859-1";
	private static String charEncoding = "UTF-8";

	public static String getCharEncoding() {
		return charEncoding;
	}

	public static void setCharEncoding(final String charEncoding) {
		if (StrUtils.isEmpty(charEncoding)) return;
		IOUtils.charEncoding = charEncoding;
	}

	public static BufferedReader getReader(final InputStream in) throws IOException {
		return new BufferedReader(new InputStreamReader(in, getCharEncoding()));
	}

	public static PrintWriter getWriter(final OutputStream out) throws IOException {
		return new PrintWriter(new OutputStreamWriter(out, getCharEncoding()), true);
	}

}
