/*
 * Copyright (c) 1995, 2013, Oracle and/or its affiliates. All rights reserved.
 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 */

package net.lax1dude.eaglercraft.v1_8;

import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.util.Properties;

public class EaglerProperties extends Properties {

	/*
	 * Read in a "logical line" from an InputStream/Reader, skip all comment and
	 * blank lines and filter out those leading whitespace characters (\u0020,
	 * \u0009 and \u000c) from the beginning of a "natural line". Method returns the
	 * char length of the "logical line" and stores the line in "lineBuf".
	 */
	private static class LineReader {
		char[] lineBuf = new char[1024];

		private byte[] inByteBuf;

		private char[] inCharBuf;
		private int inLimit = 0;
		private int inOff = 0;
		private InputStream inStream;
		private Reader reader;

		LineReader(InputStream inStream) {
			this.inStream = inStream;
			inByteBuf = new byte[8192];
		}

		LineReader(Reader reader) {
			this.reader = reader;
			inCharBuf = new char[8192];
		}

		int readLine() throws IOException {
			// use locals to optimize for interpreted performance
			int len = 0;
			int off = inOff;
			int limit = inLimit;

			boolean skipWhiteSpace = true;
			boolean appendedLineBegin = false;
			boolean precedingBackslash = false;
			boolean fromStream = inStream != null;
			byte[] byteBuf = inByteBuf;
			char[] charBuf = inCharBuf;
			char[] lineBuf = this.lineBuf;
			char c;

			while (true) {
				if (off >= limit) {
					inLimit = limit = fromStream ? inStream.read(byteBuf) : reader.read(charBuf);
					if (limit <= 0) {
						if (len == 0) {
							return -1;
						}
						return precedingBackslash ? len - 1 : len;
					}
					off = 0;
				}

				// (char)(byte & 0xFF) is equivalent to calling a ISO8859-1 decoder.
				c = (fromStream) ? (char) (byteBuf[off++] & 0xFF) : charBuf[off++];

				if (skipWhiteSpace) {
					if (c == ' ' || c == '\t' || c == '\f') {
						continue;
					}
					if (!appendedLineBegin && (c == '\r' || c == '\n')) {
						continue;
					}
					skipWhiteSpace = false;
					appendedLineBegin = false;

				}
				if (len == 0) { // Still on a new logical line
					if (c == '#' || c == '!') {
						// Comment, quickly consume the rest of the line

						// When checking for new line characters a range check,
						// starting with the higher bound ('\r') means one less
						// branch in the common case.
						commentLoop: while (true) {
							if (fromStream) {
								byte b;
								while (off < limit) {
									b = byteBuf[off++];
									if (b <= '\r' && (b == '\r' || b == '\n'))
										break commentLoop;
								}
								if (off == limit) {
									inLimit = limit = inStream.read(byteBuf);
									if (limit <= 0) { // EOF
										return -1;
									}
									off = 0;
								}
							} else {
								while (off < limit) {
									c = charBuf[off++];
									if (c <= '\r' && (c == '\r' || c == '\n'))
										break commentLoop;
								}
								if (off == limit) {
									inLimit = limit = reader.read(charBuf);
									if (limit <= 0) { // EOF
										return -1;
									}
									off = 0;
								}
							}
						}
						skipWhiteSpace = true;
						continue;
					}
				}

				if (c != '\n' && c != '\r') {
					lineBuf[len++] = c;
					if (len == lineBuf.length) {
						lineBuf = new char[len + (len << 1)];
						System.arraycopy(this.lineBuf, 0, lineBuf, 0, len);
						this.lineBuf = lineBuf;
					}
					// flip the preceding backslash flag
					precedingBackslash = (c == '\\') ? !precedingBackslash : false;
				} else {
					// reached EOL
					if (len == 0) {
						skipWhiteSpace = true;
						continue;
					}
					if (off >= limit) {
						inLimit = limit = fromStream ? inStream.read(byteBuf) : reader.read(charBuf);
						off = 0;
						if (limit <= 0) { // EOF
							return precedingBackslash ? len - 1 : len;
						}
					}
					if (precedingBackslash) {
						// backslash at EOL is not part of the line
						len -= 1;
						// skip leading whitespace characters in the following line
						skipWhiteSpace = true;
						appendedLineBegin = true;
						precedingBackslash = false;
						// take care not to include any subsequent \n
						if (c == '\r') {
							if (fromStream) {
								if (byteBuf[off] == '\n') {
									off++;
								}
							} else {
								if (charBuf[off] == '\n') {
									off++;
								}
							}
						}
					} else {
						inOff = off;
						return len;
					}
				}
			}
		}
	}

	@Override
	public void load(InputStream inStream) throws IOException {
		load0(new LineReader(inStream));
	}

	@Override
	public void load(Reader reader) throws IOException {
		load0(new LineReader(reader));
	}

	private void load0(LineReader lr) throws IOException {
		StringBuilder outBuffer = new StringBuilder();
		int limit;
		int keyLen;
		int valueStart;
		boolean hasSep;
		boolean precedingBackslash;
		boolean first = true;

		while ((limit = lr.readLine()) >= 0) {
			keyLen = 0;
			valueStart = limit;
			hasSep = false;
			if (first && limit > 0) {
				if (lr.lineBuf[0] == 65279) {
					keyLen = 1;
				}
			}
			first = false;

			// System.out.println("line=<" + new String(lineBuf, 0, limit) + ">");
			precedingBackslash = false;
			while (keyLen < limit) {
				char c = lr.lineBuf[keyLen];
				// need check if escaped.
				if ((c == '=' || c == ':') && !precedingBackslash) {
					valueStart = keyLen + 1;
					hasSep = true;
					break;
				} else if ((c == ' ' || c == '\t' || c == '\f') && !precedingBackslash) {
					valueStart = keyLen + 1;
					break;
				}
				if (c == '\\') {
					precedingBackslash = !precedingBackslash;
				} else {
					precedingBackslash = false;
				}
				keyLen++;
			}
			while (valueStart < limit) {
				char c = lr.lineBuf[valueStart];
				if (c != ' ' && c != '\t' && c != '\f') {
					if (!hasSep && (c == '=' || c == ':')) {
						hasSep = true;
					} else {
						break;
					}
				}
				valueStart++;
			}
			String key = loadConvert(lr.lineBuf, 0, keyLen, outBuffer);
			String value = loadConvert(lr.lineBuf, valueStart, limit - valueStart, outBuffer);
			put(key, value);
		}
	}

	/*
	 * Converts encoded &#92;uxxxx to unicode chars and changes special saved chars
	 * to their original forms
	 */
	private String loadConvert(char[] in, int off, int len, StringBuilder out) {
		char aChar;
		int end = off + len;
		int start = off;
		while (off < end) {
			aChar = in[off++];
			if (aChar == '\\') {
				break;
			}
		}
		if (off == end) { // No backslash
			return new String(in, start, len);
		}

		// backslash found at off - 1, reset the shared buffer, rewind offset
		out.setLength(0);
		off--;
		out.append(in, start, off - start);

		while (off < end) {
			aChar = in[off++];
			if (aChar == '\\') {
				// No need to bounds check since LineReader::readLine excludes
				// unescaped \s at the end of the line
				aChar = in[off++];
				if (aChar == 'u') {
					// Read the xxxx
					if (off > end - 4)
						throw new IllegalArgumentException("Malformed \\uxxxx encoding.");
					int value = 0;
					for (int i = 0; i < 4; i++) {
						aChar = in[off++];
						switch (aChar) {
						case '0':
						case '1':
						case '2':
						case '3':
						case '4':
						case '5':
						case '6':
						case '7':
						case '8':
						case '9':
							value = (value << 4) + aChar - '0';
							break;
						case 'a':
						case 'b':
						case 'c':
						case 'd':
						case 'e':
						case 'f':
							value = (value << 4) + 10 + aChar - 'a';
							break;
						case 'A':
						case 'B':
						case 'C':
						case 'D':
						case 'E':
						case 'F':
							value = (value << 4) + 10 + aChar - 'A';
							break;
						default:
							throw new IllegalArgumentException("Malformed \\uxxxx encoding.");
						}
						;
					}
					out.append((char) value);
				} else {
					if (aChar == 't')
						aChar = '\t';
					else if (aChar == 'r')
						aChar = '\r';
					else if (aChar == 'n')
						aChar = '\n';
					else if (aChar == 'f')
						aChar = '\f';
					out.append(aChar);
				}
			} else {
				out.append(aChar);
			}
		}
		return out.toString();
	}

}
