/* This code is part of Freenet. It is distributed under the GNU General
 * Public License, version 2 (or at your option any later version). See
 * http://www.gnu.org/ for further details of the GPL. */
package freenet.support.io;

import java.io.EOFException;
import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;

import freenet.support.HexUtil;

/**
 * A FilterInputStream which provides readLine().
 */
public class LineReadingInputStream extends FilterInputStream implements LineReader {

	public LineReadingInputStream(InputStream in) {
		super(in);
	}

	/**
	 * Read a \n or \r\n terminated line of UTF-8 or ISO-8859-1.
	 * @param maxLength The maximum length of a line. If a line is longer than this, we throw IOException rather
	 * than keeping on reading it forever.
	 * @param bufferSize The initial size of the read buffer.
	 * @param utf If true, read as UTF-8, if false, read as ISO-8859-1.
	 */
	@Override
	public String readLine(int maxLength, int bufferSize, boolean utf) throws IOException {
		if(maxLength < 1)
			return null;
		if(maxLength <= bufferSize)
			bufferSize = maxLength + 1; // Buffer too big, shrink it (add 1 for the optional \r)

		if(!markSupported())
			return readLineWithoutMarking(maxLength, bufferSize, utf);

		byte[] buf = new byte[Math.max(Math.min(128, maxLength), Math.min(1024, bufferSize))];
		int ctr = 0;
		mark(maxLength + 2); // in case we have both a \r and a \n
		while(true) {
			assert(buf.length - ctr > 0);
			int x = read(buf, ctr, buf.length - ctr);
			if(x < 0) {
				if(ctr == 0)
					return null;
				return new String(buf, 0, ctr, utf ? StandardCharsets.UTF_8 : StandardCharsets.ISO_8859_1);
			}
			if(x == 0) {
				// Don't busy-loop. Probably a socket closed or something.
				// If not, it's not a salavageable situation; either way throw.
				throw new EOFException();
			}
			// REDFLAG this is definitely safe with the above charsets, it may not be safe with some wierd ones.
			int end = ctr + x;
			for(; ctr < end; ctr++) {
				if(buf[ctr] == '\n') {
					String toReturn = "";
					if(ctr != 0) {
						boolean removeCR = (buf[ctr - 1] == '\r');
						toReturn = new String(buf, 0, (removeCR ? ctr - 1 : ctr), utf ? StandardCharsets.UTF_8 : StandardCharsets.ISO_8859_1);
					}
					reset();
					skip(ctr + 1);
					return toReturn;
				}
				if(ctr >= maxLength)
					throw new TooLongException("We reached maxLength="+maxLength+ " parsing\n "+HexUtil.bytesToHex(buf, 0, ctr) + "\n" + new String(buf, 0, ctr, utf ? StandardCharsets.UTF_8 : StandardCharsets.ISO_8859_1));
			}
			if((buf.length < maxLength) && (buf.length - ctr < bufferSize)) {
				byte[] newBuf = new byte[Math.min(buf.length * 2, maxLength)];
				System.arraycopy(buf, 0, newBuf, 0, ctr);
				buf = newBuf;
			}
		}
	}

	protected String readLineWithoutMarking(int maxLength, int bufferSize, boolean utf) throws IOException {
		if(maxLength < bufferSize)
			bufferSize = maxLength + 1; // Buffer too big, shrink it (add 1 for the optional \r)
		byte[] buf = new byte[Math.max(Math.min(128, maxLength), Math.min(1024, bufferSize))];
		int ctr = 0;
		while(true) {
			int x = read();
			if(x == -1) {
				if(ctr == 0)
					return null;
				return new String(buf, 0, ctr, utf ? StandardCharsets.UTF_8 : StandardCharsets.ISO_8859_1);
			}
			// REDFLAG this is definitely safe with the above charsets, it may not be safe with some wierd ones.
			if(x == '\n') {
				if(ctr == 0)
					return "";
				if(buf[ctr - 1] == '\r')
					ctr--;
				return new String(buf, 0, ctr, utf ? StandardCharsets.UTF_8 : StandardCharsets.ISO_8859_1);
			}
			if(ctr >= maxLength)
					throw new TooLongException("We reached maxLength="+maxLength+ " parsing\n "+HexUtil.bytesToHex(buf, 0, ctr) + "\n" + new String(buf, 0, ctr, utf ? StandardCharsets.UTF_8 : StandardCharsets.ISO_8859_1));
			if(ctr >= buf.length) {
				buf = Arrays.copyOf(buf, Math.min(buf.length * 2, maxLength));
			}
			buf[ctr++] = (byte) x;
		}
	}
}
