/*
	Реализация спецификаций CLDC версии 1.1 (JSR-139), MIDP версии 2.1 (JSR-118)
	и других спецификаций для функционирования компактных приложений на языке
	Java (мидлетов) в среде программного обеспечения Малик Эмулятор.

	Copyright © 2016, 2019 Малик Разработчик

	Это свободная программа: вы можете перераспространять ее и/или изменять
	ее на условиях Меньшей Стандартной общественной лицензии GNU в том виде,
	в каком она была опубликована Фондом свободного программного обеспечения;
	либо версии 3 лицензии, либо (по вашему выбору) любой более поздней версии.

	Эта программа распространяется в надежде, что она будет полезной,
	но БЕЗО ВСЯКИХ ГАРАНТИЙ; даже без неявной гарантии ТОВАРНОГО ВИДА
	или ПРИГОДНОСТИ ДЛЯ ОПРЕДЕЛЕННЫХ ЦЕЛЕЙ. Подробнее см. в Меньшей Стандартной
	общественной лицензии GNU.

	Вы должны были получить копию Меньшей Стандартной общественной лицензии GNU
	вместе с этой программой. Если это не так, см.
	<https://www.gnu.org/licenses/>.
*/


package malik.emulator.fileformats.text.mapped;

import java.io.*;
import malik.emulator.fileformats.*;
import malik.emulator.i18n.*;

public class PropertiesFile extends CustomKeyValueList
{
	public static final char ESCAPE = '\\';
	public static final char COMMENT_START_1 = '!';
	public static final char COMMENT_START_2 = '#';
	public static final char KEY_VALUE_SEPARATOR_1 = ' ';
	public static final char KEY_VALUE_SEPARATOR_2 = ':';
	public static final char KEY_VALUE_SEPARATOR_3 = '=';
	public static final char UNICODE_CHAR = 'u';
	public static final String ENCODING = "ISO-8859-1";

	/* Условие выхода из этого метода:
	 * c == 0 || c == 10 || c == 13 || c > 32
	 * где c = s[i], i - возвращаемое значение метода. */
	private static int skipWhiteSpace(char[] stream, int offset)
	{
		int c;
		int i = offset;
		for(; (c = stream[i]) > 0 && c <= 32 && c != 10 && c != 13; i++);
		return i;
	}

	/* Условие выхода из этого метода:
	 * c == 0 || c > 32
	 * где c = s[i], i - возвращаемое значение метода. */
	private static int skipEmptySpace(char[] stream, int offset)
	{
		int c;
		int i = offset;
		for(; (c = stream[i]) > 0 && c <= 32; i++);
		return i;
	}

	/* Этот метод читает до конца строки
	 * и останавливается после символа перевода строки. */
	private static int skipComment(char[] stream, int offset)
	{
		int c;
		int i = offset;
		for(i++; (c = stream[i]) != 0 && c != 10 && c != 13; i++);
		if(c != 0)
		{
			i += c == 13 && stream[i + 1] == 10 ? 2 : 1;
		}
		return i;
	}


	private String key;
	private String value;

	public PropertiesFile()
	{
	}

	public void loadFromInputStream(InputStream stream)
			throws IOException, InvalidDataFormatException
	{
		loadFromInputStream(stream, ENCODING);
	}

	public void loadFromInputStream(InputStream stream, String encoding)
			throws IOException, InvalidDataFormatException, UnsupportedEncodingException
	{
		int c;
		int i;
		int size;
		char[] s;
		byte[] b;
		stream.read(b = new byte[(size = stream.available()) + 4], 0, size);
		s = Helper.byteToCharArray(b, 0, size + 4, encoding);
		clear();
		try
		{
			i = size >= 1 && s[0] == 0xfeff ? 1 : 0;
			for(; s[i = skipEmptySpace(s, i)] != 0 && i < size; set(key, value))
			{
				while((c = s[i]) == COMMENT_START_1 || c == COMMENT_START_2)
				{
					i = skipEmptySpace(s, skipComment(s, i));
				}
				if(s[i] == 0 || i >= size)
				{
					break;
				}
				if((c = s[i = skipWhiteSpace(s, parseKey(s, i))]) == KEY_VALUE_SEPARATOR_2 ||
						c == KEY_VALUE_SEPARATOR_3)
				{
					i = skipWhiteSpace(s, i + 1);
				}
				i = parseValue(s, i);
			}
		}
		finally
		{
			key = null;
			value = null;
		}
	}

	public void loadFromDataStream(DataInputStream stream)
			throws IOException, InvalidDataFormatException
	{
		loadFromInputStream(stream, ENCODING);
	}

	public void loadFromDataStream(DataInputStream stream, String encoding)
			throws IOException, InvalidDataFormatException, UnsupportedEncodingException
	{
		loadFromInputStream(stream, encoding);
	}

	private int parseKey(char[] stream, int offset)
	{
		int i = offset;
		int j;
		int c;
		int u;
		StringBuffer result = new StringBuffer();
		do
		{
			for(; (c = stream[i]) > 32 && c != ESCAPE &&
					c != KEY_VALUE_SEPARATOR_2 && c != KEY_VALUE_SEPARATOR_3; i++);
			if(i > offset)
			{
				result.append(stream, offset, i - offset);
			}
			if(c != ESCAPE)
			{
				break;
			}
			switch(c = stream[++i])
			{
			default:
				break;
			case 13:
				if(stream[i + 1] == 10)
				{
					i++;
				}
				/* fall through */
			case 10:
				i = skipWhiteSpace(stream, i + 1);
				break;
			case UNICODE_CHAR:
				i++;
				u = 0;
				for(j = 4; j-- > 0; i++)
				{
					if((c = stream[i]) >= '0' && c <= '9')
					{
						u = (u << 4) + (c - '0');
					}
					else if(c >= 'A' && c <= 'F')
					{
						u = (u << 4) + (c - ('A' - 0x0a));
					}
					else if(c >= 'a' && c <= 'f')
					{
						u = (u << 4) + (c - ('a' - 0x0a));
					}
					else
					{
						break;
					}
				}
				result.append((char) u);
				break;
			case ESCAPE:
			case COMMENT_START_1:
			case COMMENT_START_2:
			case KEY_VALUE_SEPARATOR_1:
			case KEY_VALUE_SEPARATOR_2:
			case KEY_VALUE_SEPARATOR_3:
				i++;
				result.append((char) c);
				break;
			}
			offset = i;
		} while(true);
		key = result.toString().intern();
		return i;
	}

	private int parseValue(char[] stream, int offset)
	{
		int i = offset;
		int j;
		int c;
		int u;
		StringBuffer result = new StringBuffer();
		do
		{
			for(; (c = stream[i]) >= 32 && c != ESCAPE; i++);
			if(i > offset)
			{
				result.append(stream, offset, i - offset);
			}
			if(c != ESCAPE)
			{
				break;
			}
			switch(c = stream[++i])
			{
			default:
				break;
			case 13:
				if(stream[i + 1] == 10)
				{
					i++;
				}
				/* fall through */
			case 10:
				i = skipWhiteSpace(stream, i + 1);
				break;
			case UNICODE_CHAR:
				i++;
				u = 0;
				for(j = 4; j-- > 0; i++)
				{
					if((c = stream[i]) >= '0' && c <= '9')
					{
						u = (u << 4) + (c - '0');
					}
					else if(c >= 'A' && c <= 'F')
					{
						u = (u << 4) + (c - ('A' - 0x0a));
					}
					else if(c >= 'a' && c <= 'f')
					{
						u = (u << 4) + (c - ('a' - 0x0a));
					}
					else
					{
						break;
					}
				}
				result.append((char) u);
				break;
			case ESCAPE:
			case COMMENT_START_1:
			case COMMENT_START_2:
			case KEY_VALUE_SEPARATOR_1:
			case KEY_VALUE_SEPARATOR_2:
			case KEY_VALUE_SEPARATOR_3:
				i++;
				result.append((char) c);
				break;
			}
			offset = i;
		} while(true);
		value = result.toString().intern();
		return i;
	}
}
