package org.xbib.datastructures.json.noggit;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
import org.junit.jupiter.api.Test;
import java.io.IOException;
import java.io.StringReader;
import java.util.Random;

public class TestJSONParser {

    public static Random r = new Random();

    // these are to aid in debugging if an unexpected error occurs
    static int parserType;
    static int bufferSize;
    static String parserInput;
    static JSONParser lastParser;

    static int flags = JSONParser.FLAGS_DEFAULT;  // the default

    public static String lastParser() {
        return "parserType=" + parserType
                + (parserType == 1 ? " bufferSize=" + bufferSize : "")
                + " parserInput='" + parserInput + "'" + "flags : " + lastParser.flags;
    }

    public static JSONParser getParser(String s) {
        return getParser(s, r.nextInt(2), -1);
    }

    public static JSONParser getParser(String s, int type, int bufSize) {
        parserInput = s;
        parserType = type;

        JSONParser parser = null;
        switch (type) {
            case 0:
                // test directly using input buffer
                parser = new JSONParser(s.toCharArray(), 0, s.length());
                break;
            case 1:
                // test using Reader...
                // small input buffers can help find bugs on boundary conditions

              if (bufSize < 1) {
                bufSize = r.nextInt(25) + 1;
              }
                bufferSize = bufSize;// record in case there is an error
                parser = new JSONParser(new StringReader(s), new char[bufSize]);
                break;
        }
      if (parser == null) {
        return null;
      }

        lastParser = parser;

        if (flags != JSONParser.FLAGS_DEFAULT) {
            parser.setFlags(flags);
        }

        return parser;
    }

    /**
     * for debugging purposes
     * public void testSpecific() throws Exception {
     * JSONParser parser = getParser("[0",1,1);
     * for (;;) {
     * int ev = parser.nextEvent();
     * if (ev == JSONParser.EOF) {
     * break;
     * } else {
     * System.out.println("got " + JSONParser.getEventString(ev));
     * }
     * }
     * }
     **/

    public static byte[] events = new byte[256];

    static {
        events['{'] = JSONParser.OBJECT_START;
        events['}'] = JSONParser.OBJECT_END;
        events['['] = JSONParser.ARRAY_START;
        events[']'] = JSONParser.ARRAY_END;
        events['s'] = JSONParser.STRING;
        events['b'] = JSONParser.BOOLEAN;
        events['l'] = JSONParser.LONG;
        events['n'] = JSONParser.NUMBER;
        events['N'] = JSONParser.BIGNUMBER;
        events['0'] = JSONParser.NULL;
        events['e'] = JSONParser.EOF;
    }

    // match parser states with the expected states
    public static void parse(JSONParser p, String input, String expected) throws IOException {
        expected += "e";
        for (int i = 0; i < expected.length(); i++) {
            int ev = p.nextEvent();
            int expect = events[expected.charAt(i)];
            if (ev != expect) {
                fail("Expected " + expect + ", got " + ev
                        + "\n\tINPUT=" + input
                        + "\n\tEXPECTED=" + expected
                        + "\n\tAT=" + i + " (" + expected.charAt(i) + ")");
            }
        }
    }

    public static void parse(String input, String expected) throws IOException {
        String in = input;
        if ((flags & JSONParser.ALLOW_SINGLE_QUOTES) == 0 || r.nextBoolean()) {
            in = in.replace('\'', '"');
        }

        for (int i = 0; i < Integer.MAX_VALUE; i++) {
            JSONParser p = getParser(in, i, -1);
          if (p == null) {
            break;
          }
            parse(p, in, expected);
        }

        testCorruption(input, 100000);

    }

    @Test
    public static void testCorruption(String input, int iter) {
        char[] arr = new char[input.length()];

        for (int i = 0; i < iter; i++) {
            input.getChars(0, arr.length, arr, 0);
            int changes = r.nextInt(arr.length >> 1) + 1;
            for (int j = 0; j < changes; j++) {
                char ch;
                switch (r.nextInt(31)) {
                    case 0:
                        ch = 0;
                        break;
                    case 1:
                        ch = '[';
                        break;
                    case 2:
                        ch = ']';
                        break;
                    case 3:
                        ch = '{';
                        break;
                    case 4:
                        ch = '}';
                        break;
                    case 5:
                        ch = '"';
                        break;
                    case 6:
                        ch = '\'';
                        break;
                    case 7:
                        ch = ' ';
                        break;
                    case 8:
                        ch = '\r';
                        break;
                    case 9:
                        ch = '\n';
                        break;
                    case 10:
                        ch = '\t';
                        break;
                    case 11:
                        ch = ',';
                        break;
                    case 12:
                        ch = ':';
                        break;
                    case 13:
                        ch = '.';
                        break;
                    case 14:
                        ch = 'a';
                        break;
                    case 15:
                        ch = 'e';
                        break;
                    case 16:
                        ch = '0';
                        break;
                    case 17:
                        ch = '1';
                        break;
                    case 18:
                        ch = '+';
                        break;
                    case 19:
                        ch = '-';
                        break;
                    case 20:
                        ch = 't';
                        break;
                    case 21:
                        ch = 'f';
                        break;
                    case 22:
                        ch = 'n';
                        break;
                    case 23:
                        ch = '/';
                        break;
                    case 24:
                        ch = '\\';
                        break;
                    case 25:
                        ch = 'u';
                        break;
                    case 26:
                        ch = '\u00a0';
                        break;
                    default:
                        ch = (char) r.nextInt(256);
                }

                arr[r.nextInt(arr.length)] = ch;
            }


            JSONParser parser = getParser(new String(arr));
            parser.setFlags(r.nextInt());  // set random parser flags

            int ret = 0;
            try {
                for (; ; ) {
                    int ev = parser.nextEvent();
                    if (r.nextBoolean()) {
                        // see if we can read the event
                        switch (ev) {
                            case JSONParser.STRING:
                                ret += parser.getString().length();
                                break;
                            case JSONParser.BOOLEAN:
                                ret += parser.getBoolean() ? 1 : 2;
                                break;
                            case JSONParser.BIGNUMBER:
                                ret += parser.getNumberChars().length();
                                break;
                            case JSONParser.NUMBER:
                                ret = ret + (int)parser.getDouble();
                                break;
                            case JSONParser.LONG:
                                ret = ret + (int)parser.getLong();
                                break;
                            default:
                                ret += ev;
                        }
                    }

                  if (ev == JSONParser.EOF) {
                    break;
                  }
                }
            } catch (IOException ex) {
                // shouldn't happen
                System.out.println(ret);  // use ret
            } catch (JSONParser.ParseException ex) {
                // OK
            } catch (Throwable ex) {
                ex.printStackTrace();
                System.out.println(lastParser());
                throw new RuntimeException(ex);
            }
        }
    }


    public static class Num {
        public String digits;

        public Num(String digits) {
            this.digits = digits;
        }

        @Override
        public String toString() {
            return "NUMBERSTRING(" + digits + ")";
        }

        @Override
        public boolean equals(Object o) {
            return (getClass() == o.getClass() && digits.equals(((Num) o).digits));
        }

        @Override
        public int hashCode() {
            return digits.hashCode();
        }
    }

    public static class BigNum extends Num {
        @Override
        public String toString() {
            return "BIGNUM(" + digits + ")";
        }

        public BigNum(String digits) {
            super(digits);
        }
    }

    // Oh, what I wouldn't give for Java5 varargs and autoboxing
    public static Long o(int l) {
        return (long) l;
    }

    public static Long o(long l) {
        return l;
    }

    public static Double o(double d) {
        return d;
    }

    public static Boolean o(boolean b) {
        return b;
    }

    public static Num n(String digits) {
        return new Num(digits);
    }

    public static Num bn(String digits) {
        return new BigNum(digits);
    }

    public static Object t = Boolean.TRUE;
    public static Object f = Boolean.FALSE;
    public static Object a = new Object() {
        @Override
        public String toString() {
            return "ARRAY_START";
        }
    };
    public static Object A = new Object() {
        @Override
        public String toString() {
            return "ARRAY_END";
        }
    };
    public static Object m = new Object() {
        @Override
        public String toString() {
            return "OBJECT_START";
        }
    };
    public static Object M = new Object() {
        @Override
        public String toString() {
            return "OBJECT_END";
        }
    };
    public static Object N = new Object() {
        @Override
        public String toString() {
            return "NULL";
        }
    };
    public static Object e = new Object() {
        @Override
        public String toString() {
            return "EOF";
        }
    };

    // match parser states with the expected states
    private static void parse(JSONParser p, String input, Object[] expected) throws IOException {
        for (int i = 0; i < expected.length; i++) {
            int ev = p.nextEvent();
            Object exp = expected[i];
            Object got = null;

            switch (ev) {
                case JSONParser.ARRAY_START:
                    got = a;
                    break;
                case JSONParser.ARRAY_END:
                    got = A;
                    break;
                case JSONParser.OBJECT_START:
                    got = m;
                    break;
                case JSONParser.OBJECT_END:
                    got = M;
                    break;
                case JSONParser.LONG:
                    got = o(p.getLong());
                    break;
                case JSONParser.NUMBER:
                    if (exp instanceof Double) {
                        got = o(p.getDouble());
                    } else {
                        got = n(p.getNumberChars().toString());
                    }
                    break;
                case JSONParser.BIGNUMBER:
                    got = bn(p.getNumberChars().toString());
                    break;
                case JSONParser.NULL:
                    got = N;
                    p.getNull();
                    break; // optional
                case JSONParser.BOOLEAN:
                    got = o(p.getBoolean());
                    break;
                case JSONParser.EOF:
                    got = e;
                    break;
                case JSONParser.STRING:
                    got = p.getString();
                    break;
                default:
                    got = "Unexpected Event Number " + ev;
            }

            if (!(exp == got || exp.equals(got))) {
                fail("Fail: String='" + input + "'" + "\n\tINPUT=" + got + "\n\tEXPECTED=" + exp + "\n\tAT RULE " + i);
            }
        }
    }


    private static void parse(String input, Object[] expected) throws IOException {
        parse(input, (flags & JSONParser.ALLOW_SINGLE_QUOTES) == 0 || r.nextBoolean(), expected);
    }

    private static void parse(String input, boolean changeSingleQuote, Object[] expected) throws IOException {
        String in = input;
        if (changeSingleQuote) {
            in = in.replace('\'', '"');
        }
        for (int i = 0; i < Integer.MAX_VALUE; i++) {
            JSONParser p = getParser(in, i, -1);
          if (p == null) {
            break;
          }
            parse(p, in, expected);
        }
    }


    private static void err(String input) throws IOException {
        try {
            JSONParser p = getParser(input);
            while (p.nextEvent() != JSONParser.EOF) {
            }
        } catch (Exception e) {
            return;
        }
        fail("Input should failed:'" + input + "'");
    }

    @Test
    public void testNull() throws IOException {
        flags = JSONParser.FLAGS_STRICT;
        err("nul");
        err("n");
        err("nullz");
        err("[nullz]");
        flags = JSONParser.FLAGS_DEFAULT;

        parse("[null]", "[0]");
        parse("{'hi':null}", new Object[]{m, "hi", N, M, e});
    }

    @Test
    public void testBool() throws IOException {
        flags = JSONParser.FLAGS_STRICT;
        err("[True]");
        err("[False]");
        err("[TRUE]");
        err("[FALSE]");
        err("[truex]");
        err("[falsex]");
        err("[tru]");
        err("[fals]");
        err("[tru");
        err("[fals");
        err("t");
        err("f");
        flags = JSONParser.FLAGS_DEFAULT;

        parse("[false,true, false , true ]", new Object[]{a, f, t, f, t, A, e});
    }

    @Test
    public void testString() throws IOException {
        // NOTE: single quotes are converted to double quotes by this
        // testsuite!
        err("[']");
        err("[',]");
        err("{'}");
        err("{',}");

        err("['\\u111']");
        err("['\\u11']");
        err("['\\u1']");
        err("['\\']");


        flags = JSONParser.FLAGS_STRICT;
        err("['\\ ']");  // escape of non-special char
        err("['\\U1111']");  // escape of non-special char
        flags = JSONParser.FLAGS_DEFAULT;

        parse("['\\ ']", new Object[]{a, " ", A, e});  // escape of non-special char
        parse("['\\U1111']", new Object[]{a, "U1111", A, e});  // escape of non-special char


        parse("['']", new Object[]{a, "", A, e});
        parse("['\\\\']", new Object[]{a, "\\", A, e});
        parse("['X\\\\']", new Object[]{a, "X\\", A, e});
        parse("['\\\\X']", new Object[]{a, "\\X", A, e});
        parse("[\"\\\"\"]", new Object[]{a, "\"", A, e});

        parse("['\\'']", true, new Object[]{a, "\"", A, e});
        parse("['\\'']", false, new Object[]{a, "'", A, e});


        String esc = "\\n\\r\\tX\\b\\f\\/\\\\X\\\"";
        String exp = "\n\r\tX\b\f/\\X\"";
        parse("['" + esc + "']", new Object[]{a, exp, A, e});
        parse("['" + esc + esc + esc + esc + esc + "']", new Object[]{a, exp + exp + exp + exp + exp, A, e});

        esc = "\\u004A";
        exp = "\u004A";
        parse("['" + esc + "']", new Object[]{a, exp, A, e});

        esc = "\\u0000\\u1111\\u2222\\u12AF\\u12BC\\u19DE";
        exp = "\u0000\u1111\u2222\u12AF\u12BC\u19DE";
        parse("['" + esc + "']", new Object[]{a, exp, A, e});

    }

    @Test
    public void testNumbers() throws IOException {
        flags = JSONParser.FLAGS_STRICT;

        err("[00]");
        err("[003]");
        err("[00.3]");
        err("[1e1.1]");
        err("[+1]");
        err("[NaN]");
        err("[Infinity]");
        err("[--1]");

        flags = JSONParser.FLAGS_DEFAULT;

        String lmin = "-9223372036854775808";
        String lminNot = "-9223372036854775809";
        String lmax = "9223372036854775807";
        String lmaxNot = "9223372036854775808";

        String bignum = "12345678987654321357975312468642099775533112244668800152637485960987654321";

        parse("[0,1,-1,543,-876]", new Object[]{a, o(0), o(1), o(-1), o(543), o(-876), A, e});
        parse("[-0]", new Object[]{a, o(0), A, e});


        parse("[" + lmin + "," + lmax + "]",
                new Object[]{a, o(Long.MIN_VALUE), o(Long.MAX_VALUE), A, e});

        parse("[" + bignum + "]", new Object[]{a, bn(bignum), A, e});
        parse("[" + "-" + bignum + "]", new Object[]{a, bn("-" + bignum), A, e});

        parse("[" + lminNot + "]", new Object[]{a, bn(lminNot), A, e});
        parse("[" + lmaxNot + "]", new Object[]{a, bn(lmaxNot), A, e});

        parse("[" + lminNot + "," + lmaxNot + "]",
                new Object[]{a, bn(lminNot), bn(lmaxNot), A, e});

        // bignum many digits on either side of decimal
        String t = bignum + "." + bignum;
        parse("[" + t + "," + "-" + t + "]", new Object[]{a, bn(t), bn("-" + t), A, e});
        err("[" + t + ".1" + "]"); // extra decimal
        err("[" + "-" + t + ".1" + "]");

        // bignum exponent w/o fraction
        t = "1" + "e+" + bignum;
        parse("[" + t + "," + "-" + t + "]", new Object[]{a, bn(t), bn("-" + t), A, e});
        t = "1" + "E+" + bignum;
        parse("[" + t + "," + "-" + t + "]", new Object[]{a, bn(t), bn("-" + t), A, e});
        t = "1" + "e" + bignum;
        parse("[" + t + "," + "-" + t + "]", new Object[]{a, bn(t), bn("-" + t), A, e});
        t = "1" + "E" + bignum;
        parse("[" + t + "," + "-" + t + "]", new Object[]{a, bn(t), bn("-" + t), A, e});
        t = "1" + "e-" + bignum;
        parse("[" + t + "," + "-" + t + "]", new Object[]{a, bn(t), bn("-" + t), A, e});
        t = "1" + "E-" + bignum;
        parse("[" + t + "," + "-" + t + "]", new Object[]{a, bn(t), bn("-" + t), A, e});

        t = bignum + "e+" + bignum;
        parse("[" + t + "," + "-" + t + "]", new Object[]{a, bn(t), bn("-" + t), A, e});
        t = bignum + "E-" + bignum;
        parse("[" + t + "," + "-" + t + "]", new Object[]{a, bn(t), bn("-" + t), A, e});
        t = bignum + "e" + bignum;
        parse("[" + t + "," + "-" + t + "]", new Object[]{a, bn(t), bn("-" + t), A, e});

        t = bignum + "." + bignum + "e" + bignum;
        parse("[" + t + "," + "-" + t + "]", new Object[]{a, bn(t), bn("-" + t), A, e});

        err("[1E]");
        err("[1E-]");
        err("[1E+]");
        err("[1E+.3]");
        err("[1E+0.3]");
        err("[1E+1e+3]");
        err("[" + bignum + "e" + "]");
        err("[" + bignum + "e-" + "]");
        err("[" + bignum + "e+" + "]");
        err("[" + bignum + "." + bignum + "." + bignum + "]");


        double[] vals = new double[]{0, 0.1, 1.1,
                Double.MAX_VALUE,
                Double.MIN_VALUE,
                2.2250738585072014E-308, /* Double.MIN_NORMAL */
        };
        for (int i = 0; i < vals.length; i++) {
            double d = vals[i];
            parse("[" + d + "," + -d + "]", new Object[]{a, o(d), o(-d), A, e});
        }

        // MIN_NORMAL has the max number of digits (23), so check that
        // adding an extra digit causes BIGNUM to be returned.
        t = "2.2250738585072014E-308" + "0";
        parse("[" + t + "," + "-" + t + "]", new Object[]{a, bn(t), bn("-" + t), A, e});
        // check it works with a leading zero too
        t = "0.2250738585072014E-308" + "0";
        parse("[" + t + "," + "-" + t + "]", new Object[]{a, bn(t), bn("-" + t), A, e});

        // check that overflow detection is working properly w/ numbers that don't cause a wrap to negatives
        // when multiplied by 10
        t = "1910151821265210155" + "0";
        parse("[" + t + "," + "-" + t + "]", new Object[]{a, bn(t), bn("-" + t), A, e});

        for (int i = 0; i < 1000000; i++) {
            long val = r.nextLong();
            String sval = Long.toString(val);
            JSONParser parser = getParser("[" + val + "]");
            parser.nextEvent();
            assertTrue(parser.nextEvent() == JSONParser.LONG);
            if (r.nextBoolean()) {
                assertEquals(val, parser.getLong());
            } else {
                CharArr chars = parser.getNumberChars();
                assertEquals(sval, chars.toString());
            }
        }

    }

    @Test
    public void testArray() throws IOException {
        parse("[]", "[]");
        parse("[ ]", "[]");
        parse(" \r\n\t[\r\t\n ]\r\n\t ", "[]");

        parse("[0]", "[l]");
        parse("['0']", "[s]");
        parse("[0,'0',0.1]", "[lsn]");

        parse("[[[[[]]]]]", "[[[[[]]]]]");
        parse("[[[[[0]]]]]", "[[[[[l]]]]]");

        err("]");
        err("[");
        err("[[]");
        err("[]]");
        err("[}");
        err("{]");
        err("['a':'b']");

        flags = JSONParser.FLAGS_STRICT;
        err("[,]");         // test that extra commas fail
        err("[[],]");
        err("['a',]");
        err("['a',]");
        flags = JSONParser.FLAGS_DEFAULT;

        parse("[,]", "[]");  // test extra commas
        parse("[,,]", "[]");
        parse("[,,,]", "[]");
        parse("[[],]", "[[]]");
        parse("[[,],]", "[[]]");
        parse("[[,,],,]", "[[]]");
        parse("[,[,,],,]", "[[]]");
        parse("[,5,[,,5],,]", "[l[l]]");

    }

    @Test
    public void testObject() throws IOException {
        parse("{}", "{}");
        parse("{}", "{}");
        parse(" \r\n\t{\r\t\n }\r\n\t ", "{}");

        parse("{'':null}", "{s0}");

        err("}");
        err("[}]");
        err("{");
        err("[{]");
        err("{{}");
        err("[{{}]");
        err("{}}");
        err("[{}}]");
        err("{1}");
        err("[{1}]");
        err("{'a'}");
        err("{'a','b'}");
        err("{[]:'b'}");
        err("{{'a':'b'}:'c'}");
        err("{'a','b'}}");

        // bare strings allow these to pass
        flags = JSONParser.FLAGS_STRICT;
        err("{null:'b'}");
        err("{true:'b'}");
        err("{false:'b'}");
        err("{,}");         // test that extra commas fail
        err("{{},}");
        err("{'a':'b',}");
        flags = JSONParser.FLAGS_DEFAULT;

        parse("{}", new Object[]{m, M, e});
        parse("{,}", new Object[]{m, M, e});
        parse("{,,}", new Object[]{m, M, e});
        parse("{'a':{},}", new Object[]{m, "a", m, M, M, e});
        parse("{'a':{},,}", new Object[]{m, "a", m, M, M, e});
        parse("{,'a':{,},,}", new Object[]{m, "a", m, M, M, e});
        parse("{'a':'b'}", new Object[]{m, "a", "b", M, e});
        parse("{'a':5}", new Object[]{m, "a", o(5), M, e});
        parse("{'a':null}", new Object[]{m, "a", N, M, e});
        parse("{'a':[]}", new Object[]{m, "a", a, A, M, e});
        parse("{'a':{'b':'c'}}", new Object[]{m, "a", m, "b", "c", M, M, e});

        String big = "Now is the time for all good men to come to the aid of their country!";
        String bigger = big + big + big + big + big;
        parse("{'" + bigger + "':'" + bigger + "','a':'b'}", new Object[]{m, bigger, bigger, "a", "b", M, e});


        flags = JSONParser.ALLOW_UNQUOTED_KEYS;
        parse("{a:'b'}", new Object[]{m, "a", "b", M, e});
        parse("{null:'b'}", new Object[]{m, "null", "b", M, e});
        parse("{true: 'b'}", new Object[]{m, "true", "b", M, e});
        parse("{ false :'b'}", new Object[]{m, "false", "b", M, e});
        parse("{null:null, true : true , false : false , x:'y',a:'b'}", new Object[]{m, "null", N, "true", t, "false", f, "x", "y", "a", "b", M, e});
        flags = JSONParser.FLAGS_DEFAULT | JSONParser.ALLOW_MISSING_COLON_COMMA_BEFORE_OBJECT;
        parse("{'a'{'b':'c'}}", new Object[]{m, "a", m, "b", "c", M, M, e});
        parse("{'a': [{'b':'c'} {'b':'c'}]}", new Object[]{m, "a", a, m, "b", "c", M, m, "b", "c", M, A, M, e});
        parse("{'a' [{'b':'c'} {'b':'c'}]}", new Object[]{m, "a", a, m, "b", "c", M, m, "b", "c", M, A, M, e});
        parse("{'a':[['b']['c']]}", new Object[]{m, "a", a, a, "b", A, a, "c", A, A, M, e});
        parse("{'a': {'b':'c'} 'd': {'e':'f'}}", new Object[]{m, "a", m, "b", "c", M, "d", m, "e", "f", M, M, e});
        parse("{'a': {'b':'c'} d: {'e':'f'}}", new Object[]{m, "a", m, "b", "c", M, "d", m, "e", "f", M, M, e});

        flags = JSONParser.FLAGS_DEFAULT | JSONParser.ALLOW_MISSING_COLON_COMMA_BEFORE_OBJECT | JSONParser.OPTIONAL_OUTER_BRACES;
        parse("'a':{'b':'c'}", new Object[]{m, "a", m, "b", "c", M, M, e});
        parse("'a':{'b':'c'}", true, new Object[]{m, "a", m, "b", "c", M, M, e});
        parse("a:'b'", new Object[]{m, "a", "b", M, e});


        flags = JSONParser.FLAGS_DEFAULT;

    }

    public void testBareString() throws Exception {
        flags = JSONParser.ALLOW_UNQUOTED_STRING_VALUES | JSONParser.ALLOW_BACKSLASH_ESCAPING_ANY_CHARACTER;
        String[] strings = new String[]{"t", "f", "n", "a", "tru", "fals", "nul", "abc", "trueX", "falseXY", "nullZ", "truetrue", "$true", "a.bc.def", "a_b-c/d"};

        for (String s : strings) {
            parse(s, new Object[]{s, e});
            parse("[" + s + "]", new Object[]{a, s, A, e});
            parse("[ " + s + ", " + s + " ]", new Object[]{a, s, s, A, e});
            parse("[" + s + "," + s + "]", new Object[]{a, s, s, A, e});
            parse("\u00a0[\u00a0\r\n\t\u00a0" + s + "\u00a0,\u00a0\u00a0" + s + "\u00a0]\u00a0", new Object[]{a, s, s, A, e});
        }

        flags |= JSONParser.ALLOW_UNQUOTED_KEYS;
        for (String s : strings) {
            parse("{" + s + ":" + s + "}", new Object[]{m, s, s, M, e});
            parse("{ " + s + " \t\n\r:\t\n\r " + s + "\t\n\r}", new Object[]{m, s, s, M, e});
        }

        parse("{true:true, false:false, null:null}", new Object[]{m, "true", t, "false", f, "null", N, M, e});

        flags = JSONParser.FLAGS_DEFAULT;
    }


    public void testAPI() throws IOException {
        JSONParser p = new JSONParser("[1,2]");
        assertEquals(JSONParser.ARRAY_START, p.nextEvent());
        // no nextEvent for the next objects...
        assertEquals(1, p.getLong());
        assertEquals(2, p.getLong());
    }


    public void testComments() throws IOException {
        parse("#pre comment\n{//before a\n  'a' /* after a **/ #before separator\n  : /* after separator */ {/*before b*/'b'#after b\n://before c\n'c'/*after c*/}/*after close*/}#post comment no EOL", new Object[]{m, "a", m, "b", "c", M, M, e});
    }

    // rfc7159 permits any JSON values to be top level values
    @Test
    public void testTopLevelValues() throws Exception {
        parse("\"\"", new Object[]{""});
        parse("\"hello\"", new Object[]{"hello"});
        parse("true", new Object[]{t});
        parse("false", new Object[]{f});
        parse("null", new Object[]{N});
        parse("42", new Object[]{42L});
        parse("1.414", new Object[]{1.414d});
        parse("/*comment*/1.414/*more comment*/", new Object[]{1.414d});
    }
}
