// Copyright (c) 2003-present, Jodd Team (http://jodd.org)
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.

package jodd.io;

import static jodd.core.JoddCore.ioBufferSize;

import jodd.core.JoddCore;

import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.Closeable;
import java.io.Flushable;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Reader;
import java.io.Writer;

/**
 * Optimized byte and character stream utilities.
 */
public class StreamUtil {

	// ---------------------------------------------------------------- silent close

	/**
	 * Closes silently the closable object. If it is <code>FLushable</code>, it
	 * will be flushed first. No exception will be thrown if an I/O error occurs.
	 */
	public static void close(Closeable closeable) {
		if (closeable != null) {
			if (closeable instanceof Flushable) {
				try {
					((Flushable)closeable).flush();
				} catch (IOException ignored) {
				}
			}

			try {
				closeable.close();
			} catch (IOException ignored) {
			}
		}
	}

	// ---------------------------------------------------------------- copy

	/**
	 * Copies input stream to output stream using buffer. Streams don't have
	 * to be wrapped to buffered, since copying is already optimized.
	 */
	public static int copy(InputStream input, OutputStream output) throws IOException {
		byte[] buffer = new byte[ioBufferSize];
		int count = 0;
		int read;
		while (true) {
			read = input.read(buffer, 0, ioBufferSize);
			if (read == -1) {
				break;
			}
			output.write(buffer, 0, read);
			count += read;
		}
		return count;
	}
	/**
	 * Copies specified number of bytes from input stream to output stream using buffer.
	 */
	public static int copy(InputStream input, OutputStream output, int byteCount) throws IOException {
		int bufferSize = (byteCount > ioBufferSize) ? ioBufferSize : byteCount;

		byte[] buffer = new byte[bufferSize];
		int count = 0;
		int read;
		while (byteCount > 0) {
			if (byteCount < bufferSize) {
				read = input.read(buffer, 0, byteCount);
			} else {
				read = input.read(buffer, 0, bufferSize);
			}
			if (read == -1) {
				break;
			}
			byteCount -= read;
			count += read;
			output.write(buffer, 0, read);
		}
		return count;
	}




	/**
	 * Copies input stream to writer using buffer.
	 */
	public static void copy(InputStream input, Writer output) throws IOException {
		copy(input, output, JoddCore.encoding);
	}
	/**
	 * Copies specified number of bytes from input stream to writer using buffer.
	 */
	public static void copy(InputStream input, Writer output, int byteCount) throws IOException {
		copy(input, output, JoddCore.encoding, byteCount);
	}
	/**
	 * Copies input stream to writer using buffer and specified encoding.
	 */
	public static void copy(InputStream input, Writer output, String encoding) throws IOException {
		copy(new InputStreamReader(input, encoding), output);
	}
	/**
	 * Copies specified number of bytes from input stream to writer using buffer and specified encoding.
	 */
	public static void copy(InputStream input, Writer output, String encoding, int byteCount) throws IOException {
		copy(new InputStreamReader(input, encoding), output, byteCount);
	}

    /**
	 * Copies reader to writer using buffer.
	 * Streams don't have to be wrapped to buffered, since copying is already optimized.
	 */
	public static int copy(Reader input, Writer output) throws IOException {
		char[] buffer = new char[ioBufferSize];
		int count = 0;
		int read;
		while ((read = input.read(buffer, 0, ioBufferSize)) >= 0) {
			output.write(buffer, 0, read);
			count += read;
		}
		output.flush();
		return count;
	}
	/**
	 * Copies specified number of characters from reader to writer using buffer.
	 */
	public static int copy(Reader input, Writer output, int charCount) throws IOException {
		int bufferSize = (charCount > ioBufferSize) ? ioBufferSize : charCount;

		char[] buffer = new char[bufferSize];
		int count = 0;
		int read;
		while (charCount > 0) {
			if (charCount < bufferSize) {
				read = input.read(buffer, 0, charCount);
			} else {
				read = input.read(buffer, 0, bufferSize);
			}
			if (read == -1) {
				break;
			}
			charCount -= read;
			count += read;
			output.write(buffer, 0, read);
		}
		return count;
	}



	/**
	 * Copies reader to output stream using buffer.
	 */
	public static void copy(Reader input, OutputStream output) throws IOException {
		copy(input, output, JoddCore.encoding);
	}
	/**
	 * Copies specified number of characters from reader to output stream using buffer.
	 */
	public static void copy(Reader input, OutputStream output, int charCount) throws IOException {
		copy(input, output, JoddCore.encoding, charCount);
	}
	/**
	 * Copies reader to output stream using buffer and specified encoding.
	 */
	public static void copy(Reader input, OutputStream output, String encoding) throws IOException {
		Writer out = new OutputStreamWriter(output, encoding);
		copy(input, out);
		out.flush();
	}
	/**
	 * Copies specified number of characters from reader to output stream using buffer and specified encoding.
	 */
	public static void copy(Reader input, OutputStream output, String encoding, int charCount) throws IOException {
		Writer out = new OutputStreamWriter(output, encoding);
		copy(input, out, charCount);
		out.flush();
	}


	// ---------------------------------------------------------------- read bytes

	/**
	 * Reads all available bytes from InputStream as a byte array.
	 * Uses <code>in.available()</code> to determine the size of input stream.
	 * This is the fastest method for reading input stream to byte array, but
	 * depends on stream implementation of <code>available()</code>.
	 * Buffered internally.
	 */
	public static byte[] readAvailableBytes(InputStream in) throws IOException {
		int l = in.available();
		byte[] byteArray = new byte[l];
		int i = 0, j;
		while ((i < l) && (j = in.read(byteArray, i, l - i)) >= 0) {
			i +=j;
		}
		if (i < l) {
			throw new IOException("Failed to completely read input stream");
		}
		return byteArray;
	}

	public static byte[] readBytes(InputStream input) throws IOException {
		FastByteArrayOutputStream output = new FastByteArrayOutputStream();
		copy(input, output);
		return output.toByteArray();
	}
	public static byte[] readBytes(InputStream input, int byteCount) throws IOException {
		FastByteArrayOutputStream output = new FastByteArrayOutputStream();
		copy(input, output, byteCount);
		return output.toByteArray();
	}

	public static byte[] readBytes(Reader input) throws IOException {
		FastByteArrayOutputStream output = new FastByteArrayOutputStream();
		copy(input, output);
		return output.toByteArray();
	}
	public static byte[] readBytes(Reader input, int byteCount) throws IOException {
		FastByteArrayOutputStream output = new FastByteArrayOutputStream();
		copy(input, output, byteCount);
		return output.toByteArray();
	}
	public static byte[] readBytes(Reader input, String encoding) throws IOException {
		FastByteArrayOutputStream output = new FastByteArrayOutputStream();
		copy(input, output, encoding);
		return output.toByteArray();
	}
	public static byte[] readBytes(Reader input, String encoding, int byteCount) throws IOException {
		FastByteArrayOutputStream output = new FastByteArrayOutputStream();
		copy(input, output, encoding, byteCount);
		return output.toByteArray();
	}

	// ---------------------------------------------------------------- read chars

	public static char[] readChars(InputStream input) throws IOException {
		FastCharArrayWriter output = new FastCharArrayWriter();
		copy(input, output);
		return output.toCharArray();
	}
	public static char[] readChars(InputStream input, int charCount) throws IOException {
		FastCharArrayWriter output = new FastCharArrayWriter();
		copy(input, output, charCount);
		return output.toCharArray();
	}

	public static char[] readChars(InputStream input, String encoding) throws IOException {
        FastCharArrayWriter output = new FastCharArrayWriter();
        copy(input, output, encoding);
        return output.toCharArray();
    }
	public static char[] readChars(InputStream input, String encoding, int charCount) throws IOException {
        FastCharArrayWriter output = new FastCharArrayWriter();
        copy(input, output, encoding, charCount);
        return output.toCharArray();
    }

	public static char[] readChars(Reader input) throws IOException {
		FastCharArrayWriter output = new FastCharArrayWriter();
		copy(input, output);
		return output.toCharArray();
	}
	public static char[] readChars(Reader input, int charCount) throws IOException {
		FastCharArrayWriter output = new FastCharArrayWriter();
		copy(input, output, charCount);
		return output.toCharArray();
	}


	// ---------------------------------------------------------------- compare content

	/**
	 * Compares the content of two byte streams.
	 *
	 * @return <code>true</code> if the content of the first stream is equal
	 *         to the content of the second stream.
	 */
	public static boolean compare(InputStream input1, InputStream input2) throws IOException {
        if (!(input1 instanceof BufferedInputStream)) {
            input1 = new BufferedInputStream(input1);
        }
        if (!(input2 instanceof BufferedInputStream)) {
            input2 = new BufferedInputStream(input2);
        }
        int ch = input1.read();
        while (ch != -1) {
            int ch2 = input2.read();
            if (ch != ch2) {
                return false;
            }
            ch = input1.read();
        }
        int ch2 = input2.read();
		return (ch2 == -1);
	}
	/**
	 * Compares the content of two character streams.
	 *
	 * @return <code>true</code> if the content of the first stream is equal
	 *         to the content of the second stream.
	 */
	public static boolean compare(Reader input1, Reader input2) throws IOException {
        if (!(input1 instanceof BufferedReader)) {
            input1 = new BufferedReader(input1);
        }
        if (!(input2 instanceof BufferedReader)) {
            input2 = new BufferedReader(input2);
        }

        int ch = input1.read();
        while (ch != -1) {
            int ch2 = input2.read();
            if (ch != ch2) {
                return false;
            }
            ch = input1.read();
        }
        int ch2 = input2.read();
        return (ch2 == -1);
    }

}
