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

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

    Это свободная программа: вы можете перераспространять ее и/или изменять
    ее на условиях Меньшей Стандартной общественной лицензии 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.*;
import malik.emulator.util.*;

public final class PropertiesDecoder extends CustomKeyValueList
{
    public static final String ENCODING = "ISO 8859-1";

    private static int skipWhiteSpace(char[] stream, int offset) {
        int i = offset;
        for(int c; (c = stream[i]) > '\0' && c <= ' ' && c != '\r' && c != '\n'; i++);
        return i;
    }

    private static int skipEmptySpace(char[] stream, int offset) {
        int i = offset;
        for(int c; (c = stream[i]) > '\0' && c <= ' '; i++);
        return i;
    }

    private static int skipComment(char[] stream, int offset) {
        int c;
        int i = offset + 1;
        for(; (c = stream[i]) > '\0' && c != '\r' && c != '\n'; i++);
        if(c > '\0') i += c == '\r' && stream[i + 1] == '\n' ? 2 : 1;
        return i;
    }

    private String parsed;
    private final Object monitor;

    public PropertiesDecoder() {
        this.monitor = new Object();
    }

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

    public void loadFromInputStream(InputStream stream, String encoding) throws IOException {
        int size;
        char[] s;
        byte[] b;
        stream.read(b = new byte[(size = stream.available()) + 4], 0, size);
        size = (s = Helper.byteToCharArray(b, 0, size + 4, encoding)).length;
        synchronized(monitor)
        {
            clear();
            try
            {
                String key;
                String value;
                for(int i = size >= 1 && s[0] == '\ufeff' ? 1 : 0; s[i = skipEmptySpace(s, i)] != 0; set(key, value))
                {
                    int c;
                    while((c = s[i]) == '!' || c == '#') i = skipEmptySpace(s, skipComment(s, i));
                    if(i >= size || s[i] == 0) break;
                    if((c = s[i = skipWhiteSpace(s, parseKey(s, i))]) == ':' || c == '=') i = skipWhiteSpace(s, i + 1);
                    key = parsed.intern();
                    i = parseValue(s, i);
                    value = parsed.intern();
                }
            }
            finally
            {
                parsed = null;
            }
        }
    }

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

    public void loadFromDataStream(ExtendedDataInputStream stream, String encoding) throws IOException {
        loadFromInputStream(stream, encoding);
    }

    private int parseKey(char[] stream, int offset) throws InvalidDataFormatException {
        int i = offset;
        StringBuilder result = new StringBuilder();
        for(; ; )
        {
            int c;
            int u;
            for(; (c = stream[i]) > ' ' && c != '\\' && c != ':' && c != '='; i++);
            if(i > offset) result.append(stream, offset, i - offset);
            if(c != '\\') break;
            switch(c = stream[++i])
            {
            default:
                break;
            case '\r':
                if(stream[i + 1] == '\n') i++;
                /* fall through */
            case '\n':
                i = skipWhiteSpace(stream, i + 1);
                break;
            case 'u':
                i++;
                u = 0;
                for(int 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 '0':
                i++;
                result.append('\0');
                break;
            case 'b':
                i++;
                result.append('\b');
                break;
            case 't':
                i++;
                result.append('\t');
                break;
            case 'n':
                i++;
                result.append('\n');
                break;
            case 'f':
                i++;
                result.append('\f');
                break;
            case 'r':
                i++;
                result.append('\r');
                break;
            case '\\':
            case '\"':
            case '\'':
            case '!':
            case '#':
            case ' ':
            case ':':
            case '=':
                i++;
                result.append((char) c);
                break;
            }
            offset = i;
        }
        parsed = result.toString();
        return i;
    }

    private int parseValue(char[] stream, int offset) {
        int i = offset;
        StringBuilder result = new StringBuilder();
        for(; ; )
        {
            int c;
            int u;
            for(; (c = stream[i]) >= ' ' && c != '\\'; i++);
            if(i > offset) result.append(stream, offset, i - offset);
            if(c != '\\') break;
            switch(c = stream[++i])
            {
            default:
                break;
            case '\r':
                if(stream[i + 1] == '\n') i++;
                /* fall through */
            case '\n':
                i = skipWhiteSpace(stream, i + 1);
                break;
            case 'u':
                i++;
                u = 0;
                for(int 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 '0':
                i++;
                result.append('\0');
                break;
            case 'b':
                i++;
                result.append('\b');
                break;
            case 't':
                i++;
                result.append('\t');
                break;
            case 'n':
                i++;
                result.append('\n');
                break;
            case 'f':
                i++;
                result.append('\f');
                break;
            case 'r':
                i++;
                result.append('\r');
                break;
            case '\\':
            case '\"':
            case '\'':
                i++;
                result.append((char) c);
                break;
            }
            offset = i;
        }
        parsed = result.toString();
        return i;
    }
}
