// 
// Decompiled by Procyon v0.6.0
// 

package com.google.crypto.tink.internal;

import com.google.gson.stream.JsonWriter;
import java.util.Deque;
import java.util.ArrayDeque;
import com.google.gson.JsonNull;
import com.google.gson.JsonPrimitive;
import javax.annotation.Nullable;
import com.google.gson.JsonObject;
import com.google.gson.JsonArray;
import com.google.gson.stream.JsonToken;
import com.google.gson.TypeAdapter;
import java.io.ObjectInputStream;
import java.io.NotSerializableException;
import java.math.BigDecimal;
import java.io.IOException;
import java.io.Reader;
import com.google.gson.stream.JsonReader;
import java.io.StringReader;
import com.google.gson.JsonElement;

public final class JsonParser
{
    private static final JsonElementTypeAdapter JSON_ELEMENT;
    
    public static boolean isValidString(final String s) {
        for (int length = s.length(), i = 0; i != length; ++i) {
            final char ch = s.charAt(i);
            ++i;
            if (Character.isSurrogate(ch)) {
                if (Character.isLowSurrogate(ch) || i == length || !Character.isLowSurrogate(s.charAt(i))) {
                    return false;
                }
            }
        }
        return true;
    }
    
    public static JsonElement parse(final String json) throws IOException {
        try {
            final JsonReader jsonReader = new JsonReader(new StringReader(json));
            jsonReader.setLenient(false);
            return JsonParser.JSON_ELEMENT.read(jsonReader);
        }
        catch (final NumberFormatException e) {
            throw new IOException(e);
        }
    }
    
    public static long getParsedNumberAsLongOrThrow(final Number number) {
        if (!(number instanceof LazilyParsedNumber)) {
            throw new IllegalArgumentException("does not contain a parsed number.");
        }
        return Long.parseLong(number.toString());
    }
    
    private JsonParser() {
    }
    
    static {
        JSON_ELEMENT = new JsonElementTypeAdapter();
    }
    
    private static final class LazilyParsedNumber extends Number
    {
        private final String value;
        
        public LazilyParsedNumber(final String value) {
            this.value = value;
        }
        
        @Override
        public int intValue() {
            try {
                return Integer.parseInt(this.value);
            }
            catch (final NumberFormatException e) {
                try {
                    return (int)Long.parseLong(this.value);
                }
                catch (final NumberFormatException nfe) {
                    return new BigDecimal(this.value).intValue();
                }
            }
        }
        
        @Override
        public long longValue() {
            try {
                return Long.parseLong(this.value);
            }
            catch (final NumberFormatException e) {
                return new BigDecimal(this.value).longValue();
            }
        }
        
        @Override
        public float floatValue() {
            return Float.parseFloat(this.value);
        }
        
        @Override
        public double doubleValue() {
            return Double.parseDouble(this.value);
        }
        
        @Override
        public String toString() {
            return this.value;
        }
        
        private Object writeReplace() throws NotSerializableException {
            throw new NotSerializableException("serialization is not supported");
        }
        
        private void readObject(final ObjectInputStream in) throws NotSerializableException {
            throw new NotSerializableException("serialization is not supported");
        }
        
        @Override
        public int hashCode() {
            return this.value.hashCode();
        }
        
        @Override
        public boolean equals(final Object obj) {
            if (this == obj) {
                return true;
            }
            if (obj instanceof LazilyParsedNumber) {
                final LazilyParsedNumber other = (LazilyParsedNumber)obj;
                return this.value.equals(other.value);
            }
            return false;
        }
    }
    
    private static final class JsonElementTypeAdapter extends TypeAdapter<JsonElement>
    {
        private static final int RECURSION_LIMIT = 100;
        
        @Nullable
        private JsonElement tryBeginNesting(final JsonReader in, final JsonToken peeked) throws IOException {
            switch (peeked) {
                case BEGIN_ARRAY: {
                    in.beginArray();
                    return new JsonArray();
                }
                case BEGIN_OBJECT: {
                    in.beginObject();
                    return new JsonObject();
                }
                default: {
                    return null;
                }
            }
        }
        
        private JsonElement readTerminal(final JsonReader in, final JsonToken peeked) throws IOException {
            switch (peeked) {
                case STRING: {
                    final String value = in.nextString();
                    if (!JsonParser.isValidString(value)) {
                        throw new IOException("illegal characters in string");
                    }
                    return new JsonPrimitive(value);
                }
                case NUMBER: {
                    final String number = in.nextString();
                    return new JsonPrimitive(new LazilyParsedNumber(number));
                }
                case BOOLEAN: {
                    return new JsonPrimitive(in.nextBoolean());
                }
                case NULL: {
                    in.nextNull();
                    return JsonNull.INSTANCE;
                }
                default: {
                    throw new IllegalStateException("Unexpected token: " + peeked);
                }
            }
        }
        
        @Override
        public JsonElement read(final JsonReader in) throws IOException {
            JsonToken peeked = in.peek();
            JsonElement current = this.tryBeginNesting(in, peeked);
            if (current == null) {
                return this.readTerminal(in, peeked);
            }
            final Deque<JsonElement> stack = new ArrayDeque<JsonElement>();
            while (true) {
                if (in.hasNext()) {
                    String name = null;
                    if (current instanceof JsonObject) {
                        name = in.nextName();
                        if (!JsonParser.isValidString(name)) {
                            throw new IOException("illegal characters in string");
                        }
                    }
                    peeked = in.peek();
                    JsonElement value = this.tryBeginNesting(in, peeked);
                    final boolean isNesting = value != null;
                    if (value == null) {
                        value = this.readTerminal(in, peeked);
                    }
                    if (current instanceof JsonArray) {
                        ((JsonArray)current).add(value);
                    }
                    else {
                        if (((JsonObject)current).has(name)) {
                            throw new IOException("duplicate key: " + name);
                        }
                        ((JsonObject)current).add(name, value);
                    }
                    if (!isNesting) {
                        continue;
                    }
                    stack.addLast(current);
                    if (stack.size() > 100) {
                        throw new IOException("too many recursions");
                    }
                    current = value;
                }
                else {
                    if (current instanceof JsonArray) {
                        in.endArray();
                    }
                    else {
                        in.endObject();
                    }
                    if (stack.isEmpty()) {
                        return current;
                    }
                    current = stack.removeLast();
                }
            }
        }
        
        @Override
        public void write(final JsonWriter out, final JsonElement value) {
            throw new UnsupportedOperationException("write is not supported");
        }
    }
}
