package com.dslplatform.json;

import java.io.IOException;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;

public abstract class NumberConverter {

    public final static short[] SHORT_EMPTY_ARRAY = new short[0];
    public final static int[] INT_EMPTY_ARRAY = new int[0];
    public final static long[] LONG_EMPTY_ARRAY = new long[0];
    public final static float[] FLOAT_EMPTY_ARRAY = new float[0];
    public final static double[] DOUBLE_EMPTY_ARRAY = new double[0];
    public final static Short SHORT_ZERO = 0;
    public final static Integer INT_ZERO = 0;
    public final static Long LONG_ZERO = 0L;
    public final static Float FLOAT_ZERO = 0f;
    public final static Double DOUBLE_ZERO = 0.0;

    private final static int[] DIGITS = new int[1000];
    private final static int[] DIFF = {111, 222, 444, 888, 1776};
    private final static int[] ERROR = {50, 100, 200, 400, 800};
    private final static int[] SCALE_10 = {10000, 1000, 100, 10, 1};
    private final static double[] POW_10 = {
            1e1, 1e2, 1e3, 1e4, 1e5, 1e6, 1e7, 1e8, 1e9,
            1e10, 1e11, 1e12, 1e13, 1e14, 1e15, 1e16, 1e17, 1e18, 1e19,
            1e20, 1e21, 1e22, 1e23, 1e24, 1e25, 1e26, 1e27, 1e28, 1e29,
            1e30, 1e31, 1e32, 1e33, 1e34, 1e35, 1e36, 1e37, 1e38, 1e39,
            1e40, 1e41, 1e42, 1e43, 1e44, 1e45, 1e46, 1e47, 1e48, 1e49,
            1e50, 1e51, 1e52, 1e53, 1e54, 1e55, 1e56, 1e57, 1e58, 1e59,
            1e60, 1e61, 1e62, 1e63, 1e64, 1e65
    };
    public static final JsonReader.ReadObject<Double> DOUBLE_READER = new JsonReader.ReadObject<Double>() {
        @Nullable
        @Override
        public Double read(JsonReader reader) throws IOException {
            return deserializeDouble(reader);
        }
    };
    public static final JsonReader.ReadObject<Double> NULLABLE_DOUBLE_READER = new JsonReader.ReadObject<Double>() {
        @Nullable
        @Override
        public Double read(JsonReader reader) throws IOException {
            return reader.wasNull() ? null : deserializeDouble(reader);
        }
    };
    public static final JsonWriter.WriteObject<Double> DOUBLE_WRITER = new JsonWriter.WriteObject<Double>() {
        @Override
        public void write(JsonWriter writer, @Nullable Double value) {
            serializeNullable(value, writer);
        }
    };
    public static final JsonReader.ReadObject<double[]> DOUBLE_ARRAY_READER = new JsonReader.ReadObject<double[]>() {
        @Nullable
        @Override
        public double[] read(JsonReader reader) throws IOException {
            if (reader.wasNull()) {
                return null;
            }
            if (reader.last() != '[') {
                throw reader.newParseError("Expecting '[' for double array start");
            }
            reader.getNextToken();
            return deserializeDoubleArray(reader);
        }
    };
    public static final JsonWriter.WriteObject<double[]> DOUBLE_ARRAY_WRITER = new JsonWriter.WriteObject<double[]>() {
        @Override
        public void write(JsonWriter writer, @Nullable double[] value) {
            serialize(value, writer);
        }
    };

    public static final JsonReader.ReadObject<Float> FLOAT_READER = new JsonReader.ReadObject<Float>() {
        @Override
        public Float read(JsonReader reader) throws IOException {
            return deserializeFloat(reader);
        }
    };
    public static final JsonReader.ReadObject<Float> NULLABLE_FLOAT_READER = new JsonReader.ReadObject<Float>() {
        @Nullable
        @Override
        public Float read(JsonReader reader) throws IOException {
            return reader.wasNull() ? null : deserializeFloat(reader);
        }
    };
    public static final JsonWriter.WriteObject<Float> FLOAT_WRITER = new JsonWriter.WriteObject<Float>() {
        @Override
        public void write(JsonWriter writer, @Nullable Float value) {
            serializeNullable(value, writer);
        }
    };
    public static final JsonReader.ReadObject<float[]> FLOAT_ARRAY_READER = new JsonReader.ReadObject<float[]>() {
        @Nullable
        @Override
        public float[] read(JsonReader reader) throws IOException {
            if (reader.wasNull()) {
                return null;
            }
            if (reader.last() != '[') {
                throw reader.newParseError("Expecting '[' for float array start");
            }
            reader.getNextToken();
            return deserializeFloatArray(reader);
        }
    };
    public static final JsonWriter.WriteObject<float[]> FLOAT_ARRAY_WRITER = new JsonWriter.WriteObject<float[]>() {
        @Override
        public void write(JsonWriter writer, @Nullable float[] value) {
            serialize(value, writer);
        }
    };
    public static final JsonReader.ReadObject<Integer> INT_READER = new JsonReader.ReadObject<Integer>() {
        @Override
        public Integer read(JsonReader reader) throws IOException {
            return deserializeInt(reader);
        }
    };
    public static final JsonReader.ReadObject<Integer> NULLABLE_INT_READER = new JsonReader.ReadObject<Integer>() {
        @Nullable
        @Override
        public Integer read(JsonReader reader) throws IOException {
            return reader.wasNull() ? null : deserializeInt(reader);
        }
    };
    public static final JsonWriter.WriteObject<Integer> INT_WRITER = new JsonWriter.WriteObject<Integer>() {
        @Override
        public void write(JsonWriter writer, @Nullable Integer value) {
            serializeNullable(value, writer);
        }
    };
    public static final JsonReader.ReadObject<int[]> INT_ARRAY_READER = new JsonReader.ReadObject<int[]>() {
        @Nullable
        @Override
        public int[] read(JsonReader reader) throws IOException {
            if (reader.wasNull()) {
                return null;
            }
            if (reader.last() != '[') {
                throw reader.newParseError("Expecting '[' for int array start");
            }
            reader.getNextToken();
            return deserializeIntArray(reader);
        }
    };
    public static final JsonWriter.WriteObject<int[]> INT_ARRAY_WRITER = new JsonWriter.WriteObject<int[]>() {
        @Override
        public void write(JsonWriter writer, @Nullable int[] value) {
            serialize(value, writer);
        }
    };
    public static final JsonReader.ReadObject<Short> SHORT_READER = new JsonReader.ReadObject<Short>() {
        @Override
        public Short read(JsonReader reader) throws IOException {
            return deserializeShort(reader);
        }
    };
    public static final JsonReader.ReadObject<Short> NULLABLE_SHORT_READER = new JsonReader.ReadObject<Short>() {
        @Nullable
        @Override
        public Short read(JsonReader reader) throws IOException {
            return reader.wasNull() ? null : deserializeShort(reader);
        }
    };
    public static final JsonWriter.WriteObject<Short> SHORT_WRITER = new JsonWriter.WriteObject<Short>() {
        @Override
        public void write(JsonWriter writer, @Nullable Short value) {
            if (value == null) {
                writer.writeNull();
            } else {
                serialize(value.intValue(), writer);
            }
        }
    };
    public static final JsonReader.ReadObject<short[]> SHORT_ARRAY_READER = new JsonReader.ReadObject<short[]>() {
        @Nullable
        @Override
        public short[] read(JsonReader reader) throws IOException {
            if (reader.wasNull()) {
                return null;
            }
            if (reader.last() != '[') {
                throw reader.newParseError("Expecting '[' for short array start");
            }
            reader.getNextToken();
            return deserializeShortArray(reader);
        }
    };
    public static final JsonWriter.WriteObject<short[]> SHORT_ARRAY_WRITER = new JsonWriter.WriteObject<short[]>() {
        @Override
        public void write(JsonWriter writer, @Nullable short[] value) {
            serialize(value, writer);
        }
    };

    public static final JsonReader.ReadObject<Long> LONG_READER = new JsonReader.ReadObject<Long>() {
        @Override
        public Long read(JsonReader reader) throws IOException {
            return deserializeLong(reader);
        }
    };
    public static final JsonReader.ReadObject<Long> NULLABLE_LONG_READER = new JsonReader.ReadObject<Long>() {
        @Nullable
        @Override
        public Long read(JsonReader reader) throws IOException {
            return reader.wasNull() ? null : deserializeLong(reader);
        }
    };
    public static final JsonWriter.WriteObject<Long> LONG_WRITER = new JsonWriter.WriteObject<Long>() {
        @Override
        public void write(JsonWriter writer, @Nullable Long value) {
            serializeNullable(value, writer);
        }
    };
    public static final JsonReader.ReadObject<long[]> LONG_ARRAY_READER = new JsonReader.ReadObject<long[]>() {
        @Nullable
        @Override
        public long[] read(JsonReader reader) throws IOException {
            if (reader.wasNull()) {
                return null;
            }
            if (reader.last() != '[') {
                throw reader.newParseError("Expecting '[' for long array start");
            }
            reader.getNextToken();
            return deserializeLongArray(reader);
        }
    };
    public static final JsonWriter.WriteObject<long[]> LONG_ARRAY_WRITER = new JsonWriter.WriteObject<long[]>() {
        @Override
        public void write(JsonWriter writer, @Nullable long[] value) {
            serialize(value, writer);
        }
    };

    public static final JsonReader.ReadObject<BigDecimal> DecimalReader = new JsonReader.ReadObject<BigDecimal>() {
        @Nullable
        @Override
        public BigDecimal read(JsonReader reader) throws IOException {
            return reader.wasNull() ? null : deserializeDecimal(reader);
        }
    };
    public static final JsonWriter.WriteObject<BigDecimal> DecimalWriter = new JsonWriter.WriteObject<BigDecimal>() {
        @Override
        public void write(JsonWriter writer, @Nullable BigDecimal value) {
            serializeNullable(value, writer);
        }
    };
    static final JsonReader.ReadObject<Number> NumberReader = new JsonReader.ReadObject<Number>() {
        @Nullable
        @Override
        public Number read(JsonReader reader) throws IOException {
            return reader.wasNull() ? null : deserializeNumber(reader);
        }
    };

    static {
        for (int i = 0; i < DIGITS.length; i++) {
            DIGITS[i] = (i < 10 ? (2 << 24) : i < 100 ? (1 << 24) : 0)
                    + (((i / 100) + '0') << 16)
                    + ((((i / 10) % 10) + '0') << 8)
                    + i % 10 + '0';
        }
    }

    static void write4(final int value, final byte[] buf, final int pos) {
        if (value > 9999) {
            throw new IllegalArgumentException("Only 4 digits numbers are supported. Provided: " + value);
        }
        final int q = value / 1000;
        final int v = DIGITS[value - q * 1000];
        buf[pos] = (byte) (q + '0');
        buf[pos + 1] = (byte) (v >> 16);
        buf[pos + 2] = (byte) (v >> 8);
        buf[pos + 3] = (byte) v;
    }

    static void write3(final int number, final byte[] buf, int pos) {
        final int v = DIGITS[number];
        buf[pos] = (byte) (v >> 16);
        buf[pos + 1] = (byte) (v >> 8);
        buf[pos + 2] = (byte) v;
    }

    static void write2(final int value, final byte[] buf, final int pos) {
        final int v = DIGITS[value];
        buf[pos] = (byte) (v >> 8);
        buf[pos + 1] = (byte) v;
    }

    static int read2(final char[] buf, final int pos) {
        final int v1 = buf[pos] - 48;
        return (v1 << 3) + (v1 << 1) + buf[pos + 1] - 48;
    }

    static int read4(final char[] buf, final int pos) {
        final int v2 = buf[pos + 1] - 48;
        final int v3 = buf[pos + 2] - 48;
        return (buf[pos] - 48) * 1000
                + (v2 << 6) + (v2 << 5) + (v2 << 2)
                + (v3 << 3) + (v3 << 1)
                + buf[pos + 3] - 48;
    }

    static void numberException(final JsonReader reader, final int start, final int end, String message) throws ParsingException {
        final int len = end - start;
        if (len > reader.maxNumberDigits) {
            throw reader.newParseErrorWith("Too many digits detected in number", len, "", "Too many digits detected in number", end, "");
        }
        throw reader.newParseErrorWith("Error parsing number", len, "", message, null, ". Error parsing number");
    }

    static void numberException(final JsonReader reader, final int start, final int end, String message, Object messageArgument) throws ParsingException {
        final int len = end - start;
        if (len > reader.maxNumberDigits) {
            throw reader.newParseErrorWith("Too many digits detected in number", len, "", "Too many digits detected in number", end, "");
        }
        throw reader.newParseErrorWith("Error parsing number", len, "", message, messageArgument, ". Error parsing number");
    }

    public static void serializeNullable(@Nullable final Double value, final JsonWriter sw) {
        if (value == null) {
            sw.writeNull();
        } else {
            serialize(value, sw);
        }
    }

    private static BigDecimal parseNumberGeneric(final char[] buf, final int len, final JsonReader reader, final boolean withQuotes) throws ParsingException {
        int end = len;
        while (end > 0 && Character.isWhitespace(buf[end - 1])) {
            end--;
        }
        if (end > reader.maxNumberDigits) {
            throw reader.newParseErrorWith("Too many digits detected in number", len, "", "Too many digits detected in number", end, "");
        }
        final int offset = buf[0] == '-' ? 1 : 0;
        if (buf[offset] == '0' && end > offset + 1 && buf[offset + 1] >= '0' && buf[offset + 1] <= '9') {
            throw reader.newParseErrorAt("Leading zero is not allowed. Error parsing number", len + (withQuotes ? 2 : 0));
        }
        try {
            return new BigDecimal(buf, 0, end);
        } catch (NumberFormatException nfe) {
            throw reader.newParseErrorAt("Error parsing number", len + (withQuotes ? 2 : 0), nfe);
        }
    }

    public static void serialize(final double value, final JsonWriter sw) {
        sw.writeDouble(value);
    }

    public static void serialize(@Nullable final double[] value, final JsonWriter sw) {
        if (value == null) {
            sw.writeNull();
        } else if (value.length == 0) {
            sw.writeAscii("[]");
        } else {
            sw.writeByte(JsonWriter.ARRAY_START);
            serialize(value[0], sw);
            for (int i = 1; i < value.length; i++) {
                sw.writeByte(JsonWriter.COMMA);
                serialize(value[i], sw);
            }
            sw.writeByte(JsonWriter.ARRAY_END);
        }
    }

    private static class NumberInfo {
        final char[] buffer;
        final int length;

        NumberInfo(final char[] buffer, final int length) {
            this.buffer = buffer;
            this.length = length;
        }
    }

    private static NumberInfo readLongNumber(final JsonReader reader, final int start) throws IOException {
        int len = reader.length() - start;
        char[] result = reader.prepareBuffer(start, len);
        while (reader.length() == reader.getCurrentIndex()) {
            if (reader.isEndOfStream()) {
                break;
            }
            reader.scanNumber(); // peek, do not read
            int end = reader.getCurrentIndex();
            int oldLen = len;
            len += end;
            if (len > reader.maxNumberDigits) {
                throw reader.newParseErrorFormat("Too many digits detected in number", len, "Number of digits larger than %d. Unable to read number", reader.maxNumberDigits);
            }
            char[] tmp = result;
            result = new char[len];
            System.arraycopy(tmp, 0, result, 0, oldLen);
            System.arraycopy(reader.prepareBuffer(0, end), 0, result, oldLen, end);
        }
        return new NumberInfo(result, len);
    }

    public static double deserializeDouble(final JsonReader reader) throws IOException {
        if (reader.last() == '"') {
            final int position = reader.getCurrentIndex();
            final char[] buf = reader.readSimpleQuote();
            return parseDoubleGeneric(buf, reader.getCurrentIndex() - position - 1, reader, true);
        }
        final int start = reader.scanNumber();
        final int end = reader.getCurrentIndex();
        final byte[] buf = reader.buffer;
        final byte ch = buf[start];
        if (ch == '-') {
            return -parseDouble(buf, reader, start, end, 1);
        }
        return parseDouble(buf, reader, start, end, 0);
    }

    private static double parseDouble(final byte[] buf, final JsonReader reader, final int start, final int end, final int offset) throws IOException {
        if (end - start - offset > reader.doubleLengthLimit) {
            if (end == reader.length()) {
                final NumberInfo tmp = readLongNumber(reader, start + offset);
                return parseDoubleGeneric(tmp.buffer, tmp.length, reader, false);
            }
            return parseDoubleGeneric(reader.prepareBuffer(start + offset, end - start - offset), end - start - offset, reader, false);
        }
        long value = 0;
        byte ch = ' ';
        int i = start + offset;
        final boolean leadingZero = buf[start + offset] == 48;
        for (; i < end; i++) {
            ch = buf[i];
            if (ch == '.' || ch == 'e' || ch == 'E') {
                break;
            }
            final int ind = buf[i] - 48;
            if (ind < 0 || ind > 9) {
                if (leadingZero && i > start + offset + 1) {
                    numberException(reader, start, end, "Leading zero is not allowed");
                }
                if (i > start + offset && reader.allWhitespace(i, end)) {
                    return value;
                }
                numberException(reader, start, end, "Unknown digit", (char) ch);
            }
            value = (value << 3) + (value << 1) + ind;
        }
        if (i == start + offset) {
            numberException(reader, start, end, "Digit not found");
        } else if (leadingZero && ch != '.' && i > start + offset + 1) {
            numberException(reader, start, end, "Leading zero is not allowed");
        } else if (i == end) {
            return value;
        } else if (ch == '.') {
            i++;
            if (i == end) {
                numberException(reader, start, end, "Number ends with a dot");
            }
            final int maxLen;
            final double preciseDividor;
            final int expDiff;
            final int decPos = i;
            final int decOffset;
            if (value == 0) {
                maxLen = i + 15;
                ch = buf[i];
                if (ch == '0' && end > maxLen) {
                    return parseDoubleGeneric(reader.prepareBuffer(start + offset, end - start - offset), end - start - offset, reader, false);
                } else if (ch < '8') {
                    preciseDividor = 1e14;
                    expDiff = -1;
                    decOffset = 1;
                } else {
                    preciseDividor = 1e15;
                    expDiff = 0;
                    decOffset = 0;
                }
            } else {
                maxLen = start + offset + 16;
                if (buf[start + offset] < '8') {
                    preciseDividor = 1e14;
                    expDiff = i - maxLen + 14;
                    decOffset = 1;
                } else {
                    preciseDividor = 1e15;
                    expDiff = i - maxLen + 15;
                    decOffset = 0;
                }
            }
            final int numLimit = maxLen < end ? maxLen : end;
            //TODO zeros
            for (; i < numLimit; i++) {
                ch = buf[i];
                if (ch == 'e' || ch == 'E') {
                    break;
                }
                final int ind = ch - 48;
                if (ind < 0 || ind > 9) {
                    if (reader.allWhitespace(i, end)) {
                        return value / POW_10[i - decPos - 1];
                    }
                    numberException(reader, start, end, "Unknown digit", (char) buf[i]);
                }
                value = (value << 3) + (value << 1) + ind;
            }
            if (i == end) {
                return value / POW_10[i - decPos - 1];
            } else if (ch == 'e' || ch == 'E') {
                return doubleExponent(reader, value, i - decPos, 0, buf, start, end, offset, i);
            }
            if (reader.doublePrecision == JsonReader.DoublePrecision.HIGH) {
                return parseDoubleGeneric(reader.prepareBuffer(start + offset, end - start - offset), end - start - offset, reader, false);
            }
            int decimals = 0;
            final int decLimit = start + offset + 18 < end ? start + offset + 18 : end;
            final int remPos = i;
            for (; i < decLimit; i++) {
                ch = buf[i];
                if (ch == 'e' || ch == 'E') {
                    break;
                }
                final int ind = ch - 48;
                if (ind < 0 || ind > 9) {
                    if (reader.allWhitespace(i, end)) {
                        return approximateDouble(decimals, value / preciseDividor, i - remPos - decOffset);
                    }
                    numberException(reader, start, end, "Unknown digit", (char) buf[i]);
                }
                decimals = (decimals << 3) + (decimals << 1) + ind;
            }
            final double number = approximateDouble(decimals, value / preciseDividor, i - remPos - decOffset);
            while (i < end && ch >= '0' && ch <= '9') {
                ch = buf[i++];
            }
            if (ch == 'e' || ch == 'E') {
                return doubleExponent(reader, 0, expDiff, number, buf, start, end, offset, i);
            } else if (expDiff > 0) {
                return number * POW_10[expDiff - 1];
            } else if (expDiff < 0) {
                return number / POW_10[-expDiff - 1];
            } else {
                return number;
            }
        } else if (ch == 'e' || ch == 'E') {
            return doubleExponent(reader, value, 0, 0, buf, start, end, offset, i);
        }
        return value;
    }

    private static double approximateDouble(final int decimals, final double precise, final int digits) {
        final long bits = Double.doubleToRawLongBits(precise);
        final int exp = (int) (bits >> 52) - 1022;
        final int missing = (decimals * SCALE_10[digits + 1] + ERROR[exp]) / DIFF[exp];
        return Double.longBitsToDouble(bits + missing);
    }

    private static double doubleExponent(JsonReader reader, final long whole, final int decimals, double fraction, byte[] buf, int start, int end, int offset, int i) throws IOException {
        if (reader.doublePrecision == JsonReader.DoublePrecision.EXACT) {
            return parseDoubleGeneric(reader.prepareBuffer(start + offset, end - start - offset), end - start - offset, reader, false);
        }
        byte ch;
        ch = buf[++i];
        final int exp;
        if (ch == '-') {
            exp = parseNegativeInt(buf, reader, i, end) - decimals;
        } else if (ch == '+') {
            exp = parsePositiveInt(buf, reader, i, end, 1) - decimals;
        } else {
            exp = parsePositiveInt(buf, reader, i, end, 0) - decimals;
        }
        if (fraction == 0) {
            if (exp == 0 || whole == 0) {
                return whole;
            } else if (exp > 0 && exp < POW_10.length) {
                return whole * POW_10[exp - 1];
            } else if (exp < 0 && -exp < POW_10.length) {
                return whole / POW_10[-exp - 1];
            } else if (reader.doublePrecision != JsonReader.DoublePrecision.HIGH) {
                if (exp > 0 && exp < 300) {
                    return whole * Math.pow(10, exp);
                } else if (exp > -300 && exp < 0) {
                    return whole / Math.pow(10, exp);
                }
            }
        } else {
            if (exp == 0) {
                return whole + fraction;
            } else if (exp > 0 && exp < POW_10.length) {
                return fraction * POW_10[exp - 1] + whole * POW_10[exp - 1];
            } else if (exp < 0 && -exp < POW_10.length) {
                return fraction / POW_10[-exp - 1] + whole / POW_10[-exp - 1];
            } else if (reader.doublePrecision != JsonReader.DoublePrecision.HIGH) {
                if (exp > 0 && exp < 300) {
                    return whole * Math.pow(10, exp);
                } else if (exp > -300 && exp < 0) {
                    return whole / Math.pow(10, exp);
                }
            }
        }
        return parseDoubleGeneric(reader.prepareBuffer(start + offset, end - start - offset), end - start - offset, reader, false);
    }

    private static double parseDoubleGeneric(final char[] buf, final int len, final JsonReader reader, final boolean withQuotes) throws IOException {
        int end = len;
        while (end > 0 && Character.isWhitespace(buf[end - 1])) {
            end--;
        }
        if (end > reader.maxNumberDigits) {
            throw reader.newParseErrorWith("Too many digits detected in number", len, "", "Too many digits detected in number", end, "");
        }
        final int offset = buf[0] == '-' ? 1 : 0;
        if (buf[offset] == '0' && end > offset + 1 && buf[offset + 1] >= '0' && buf[offset + 1] <= '9') {
            throw reader.newParseErrorAt("Leading zero is not allowed. Error parsing number", len + (withQuotes ? 2 : 0));
        }
        try {
            return Double.parseDouble(new String(buf, 0, end));
        } catch (NumberFormatException nfe) {
            throw reader.newParseErrorAt("Error parsing number", len + (withQuotes ? 2 : 0), nfe);
        }
    }

    @SuppressWarnings("unchecked")
    public static ArrayList<Double> deserializeDoubleCollection(final JsonReader reader) throws IOException {
        return reader.deserializeCollection(DOUBLE_READER);
    }

    public static void deserializeDoubleCollection(final JsonReader reader, final Collection<Double> res) throws IOException {
        reader.deserializeCollection(DOUBLE_READER, res);
    }

    @SuppressWarnings("unchecked")
    public static ArrayList<Double> deserializeDoubleNullableCollection(final JsonReader reader) throws IOException {
        return reader.deserializeNullableCollection(DOUBLE_READER);
    }

    public static void deserializeDoubleNullableCollection(final JsonReader reader, final Collection<Double> res) throws IOException {
        reader.deserializeNullableCollection(DOUBLE_READER, res);
    }

    public static void serializeNullable(@Nullable final Float value, final JsonWriter sw) {
        if (value == null) {
            sw.writeNull();
        } else {
            serialize(value, sw);
        }
    }

    public static void serialize(final float value, final JsonWriter sw) {
        if (value == Float.POSITIVE_INFINITY) {
            sw.writeAscii("\"Infinity\"");
        } else if (value == Float.NEGATIVE_INFINITY) {
            sw.writeAscii("\"-Infinity\"");
        } else if (value != value) {
            sw.writeAscii("\"NaN\"");
        } else {
            sw.writeAscii(Float.toString(value));//TODO: better implementation required
        }
    }

    public static void serialize(@Nullable final float[] value, final JsonWriter sw) {
        if (value == null) {
            sw.writeNull();
        } else if (value.length == 0) {
            sw.writeAscii("[]");
        } else {
            sw.writeByte(JsonWriter.ARRAY_START);
            serialize(value[0], sw);
            for (int i = 1; i < value.length; i++) {
                sw.writeByte(JsonWriter.COMMA);
                serialize(value[i], sw);
            }
            sw.writeByte(JsonWriter.ARRAY_END);
        }
    }

    public static float deserializeFloat(final JsonReader reader) throws IOException {
        if (reader.last() == '"') {
            final int position = reader.getCurrentIndex();
            final char[] buf = reader.readSimpleQuote();
            return parseFloatGeneric(buf, reader.getCurrentIndex() - position - 1, reader, true);
        }
        final int start = reader.scanNumber();
        final int end = reader.getCurrentIndex();
        if (end == reader.length()) {
            final NumberInfo tmp = readLongNumber(reader, start);
            return parseFloatGeneric(tmp.buffer, tmp.length, reader, false);
        }
        final byte[] buf = reader.buffer;
        final byte ch = buf[start];
        if (ch == '-') {
            return -parseFloat(buf, reader, start, end, 1);
        }
        return parseFloat(buf, reader, start, end, 0);
    }

    private static float parseFloat(byte[] buf, final JsonReader reader, final int start, int end, int offset) throws IOException {
        long value = 0;
        byte ch = ' ';
        int i = start + offset;
        final int digitStart = i;
        final boolean leadingZero = buf[start + offset] == 48;
        for (; i < end; i++) {
            ch = buf[i];
            if (ch == '.' || ch == 'e' || ch == 'E') {
                break;
            }
            final int ind = ch - 48;
            if (ind < 0 || ind > 9) {
                if (leadingZero && i > start + offset + 1) {
                    numberException(reader, start, end, "Leading zero is not allowed");
                }
                if (i > start + offset && reader.allWhitespace(i, end)) {
                    return value;
                }
                numberException(reader, start, end, "Unknown digit", (char) ch);
            }
            value = (value << 3) + (value << 1) + ind;
        }
        if (i == digitStart) {
            numberException(reader, start, end, "Digit not found");
        } else if (leadingZero && ch != '.' && i > start + offset + 1) {
            numberException(reader, start, end, "Leading zero is not allowed");
        } else if (i > 18 + digitStart) {
            return parseFloatGeneric(reader.prepareBuffer(start + offset, end - start - offset), end - start - offset, reader, false);
        } else if (i == end) {
            return value;
        } else if (ch == '.') {
            i++;
            if (i == end) {
                numberException(reader, start, end, "Number ends with a dot");
            }
            final int decPos;
            final int maxLen;
            final int pointOffset;
            if (value == 0) {
                pointOffset = 0;
                decPos = i + 1;
                while (i < end && buf[i] == '0') {
                    i++;
                }
                maxLen = i + 17;
            } else {
                pointOffset = 1;
                maxLen = digitStart + 17;
                decPos = i;
            }
            final int numLimit = maxLen < end ? maxLen : end;
            boolean foundE = false;
            for (; i < numLimit; i++) {
                ch = buf[i];
                if (ch == 'e' || ch == 'E') {
                    foundE = true;
                    ++i;
                    break;
                }
                final int ind = ch - 48;
                if (ind < 0 || ind > 9) {
                    if (reader.allWhitespace(i, end)) {
                        return (float) (value / POW_10[i - decPos - pointOffset]);
                    }
                    numberException(reader, start, end, "Unknown digit", (char) ch);
                }
                value = (value << 3) + (value << 1) + ind;
            }
            final int endPos;
            if (i == numLimit && !foundE) {
                endPos = i + 1 - pointOffset;
                while (i < end && ch >= '0' && ch <= '9') {
                    ch = buf[i++];
                }
            } else {
                endPos = i - pointOffset;
            }
            while (i == end && reader.length() == end) {
                i = reader.scanNumber();
                end = reader.getCurrentIndex();
                buf = reader.buffer;
                while (i < end && ch >= '0' && ch <= '9') {
                    ch = buf[i++];
                }
            }
            if (ch == 'e' || ch == 'E') {
                return floatExponent(reader, value, endPos - decPos, buf, end, i);
            }
            final int expDiff = endPos - decPos;
            if (expDiff > 0) {
                return (float) (value / POW_10[expDiff - 1]);
            } else if (expDiff < 0) {
                return (float) (value * POW_10[-expDiff - 1]);
            } else {
                return value;
            }
        } else if (ch == 'e' || ch == 'E') {
            return floatExponent(reader, value, 0, buf, end, i + 1);
        }
        return value;
    }

    private static float floatExponent(JsonReader reader, final long whole, final int decimals, byte[] buf, int end, int i) throws IOException {
        byte ch;
        ch = buf[i];
        final int exp;
        if (ch == '-') {
            exp = parseNegativeInt(buf, reader, i, end) - decimals;
        } else if (ch == '+') {
            exp = parsePositiveInt(buf, reader, i, end, 1) - decimals;
        } else {
            exp = parsePositiveInt(buf, reader, i, end, 0) - decimals;
        }
        if (exp == 0 || whole == 0) {
            return whole;
        } else if (exp > 0 && exp < POW_10.length) {
            return (float) (whole * POW_10[exp - 1]);
        } else if (exp < 0 && -exp < POW_10.length) {
            return (float) (whole / POW_10[-exp - 1]);
        } else {
            return exp > 0 ? Float.POSITIVE_INFINITY : 0f;
        }
    }

    private static float parseFloatGeneric(final char[] buf, final int len, final JsonReader reader, final boolean withQuotes) throws ParsingException {
        int end = len;
        while (end > 0 && Character.isWhitespace(buf[end - 1])) {
            end--;
        }
        if (end > reader.maxNumberDigits) {
            throw reader.newParseErrorWith("Too many digits detected in number", len, "", "Too many digits detected in number", end, "");
        }
        final int offset = buf[0] == '-' ? 1 : 0;
        if (buf[offset] == '0' && end > offset + 1 && buf[offset + 1] >= '0' && buf[offset + 1] <= '9') {
            throw reader.newParseErrorAt("Leading zero is not allowed. Error parsing number", len + (withQuotes ? 2 : 0));
        }
        try {
            return Float.parseFloat(new String(buf, 0, end));
        } catch (NumberFormatException nfe) {
            throw reader.newParseErrorAt("Error parsing number", len + (withQuotes ? 2 : 0), nfe);
        }
    }

    @SuppressWarnings("unchecked")
    public static ArrayList<Float> deserializeFloatCollection(final JsonReader reader) throws IOException {
        return reader.deserializeCollection(FLOAT_READER);
    }

    public static void deserializeFloatCollection(final JsonReader reader, Collection<Float> res) throws IOException {
        reader.deserializeCollection(FLOAT_READER, res);
    }

    @SuppressWarnings("unchecked")
    public static ArrayList<Float> deserializeFloatNullableCollection(final JsonReader reader) throws IOException {
        return reader.deserializeNullableCollection(FLOAT_READER);
    }

    public static void deserializeFloatNullableCollection(final JsonReader reader, final Collection<Float> res) throws IOException {
        reader.deserializeNullableCollection(FLOAT_READER, res);
    }

    public static void serializeNullable(@Nullable final Integer value, final JsonWriter sw) {
        if (value == null) {
            sw.writeNull();
        } else {
            serialize(value, sw);
        }
    }

    private static final byte MINUS = '-';
    private static final byte[] MIN_INT = "-2147483648".getBytes();

    public static void serialize(final int value, final JsonWriter sw) {
        final byte[] buf = sw.ensureCapacity(11);
        final int position = sw.size();
        int current = serialize(buf, position, value);
        sw.advance(current - position);
    }

    private static int serialize(final byte[] buf, int pos, final int value) {
        int i;
        if (value < 0) {
            if (value == Integer.MIN_VALUE) {
                for (int x = 0; x < MIN_INT.length; x++) {
                    buf[pos + x] = MIN_INT[x];
                }
                return pos + MIN_INT.length;
            }
            i = -value;
            buf[pos++] = MINUS;
        } else {
            i = value;
        }
        final int q1 = i / 1000;
        if (q1 == 0) {
            pos += writeFirstBuf(buf, DIGITS[i], pos);
            return pos;
        }
        final int r1 = i - q1 * 1000;
        final int q2 = q1 / 1000;
        if (q2 == 0) {
            final int v1 = DIGITS[r1];
            final int v2 = DIGITS[q1];
            int off = writeFirstBuf(buf, v2, pos);
            writeBuf(buf, v1, pos + off);
            return pos + 3 + off;
        }
        final int r2 = q1 - q2 * 1000;
        final int q3 = q2 / 1000;
        final int v1 = DIGITS[r1];
        final int v2 = DIGITS[r2];
        if (q3 == 0) {
            pos += writeFirstBuf(buf, DIGITS[q2], pos);
        } else {
            final int r3 = q2 - q3 * 1000;
            buf[pos++] = (byte) (q3 + '0');
            writeBuf(buf, DIGITS[r3], pos);
            pos += 3;
        }
        writeBuf(buf, v2, pos);
        writeBuf(buf, v1, pos + 3);
        return pos + 6;
    }

    public static void serialize(@Nullable final int[] values, final JsonWriter sw) {
        if (values == null) {
            sw.writeNull();
        } else if (values.length == 0) {
            sw.writeAscii("[]");
        } else {
            final byte[] buf = sw.ensureCapacity(values.length * 11 + 2);
            int position = sw.size();
            buf[position++] = '[';
            position = serialize(buf, position, values[0]);
            for (int i = 1; i < values.length; i++) {
                buf[position++] = ',';
                position = serialize(buf, position, values[i]);
            }
            buf[position++] = ']';
            sw.advance(position - sw.size());
        }
    }

    public static void serialize(@Nullable final short[] value, final JsonWriter sw) {
        if (value == null) {
            sw.writeNull();
        } else if (value.length == 0) {
            sw.writeAscii("[]");
        } else {
            sw.writeByte(JsonWriter.ARRAY_START);
            serialize(value[0], sw);
            for (int i = 1; i < value.length; i++) {
                sw.writeByte(JsonWriter.COMMA);
                serialize(value[i], sw);
            }
            sw.writeByte(JsonWriter.ARRAY_END);
        }
    }

    public static short deserializeShort(final JsonReader reader) throws IOException {
        if (reader.last() == '"') {
            final int position = reader.getCurrentIndex();
            final char[] buf = reader.readSimpleQuote();
            try {
                return parseNumberGeneric(buf, reader.getCurrentIndex() - position - 1, reader, true).shortValueExact();
            } catch (ArithmeticException ignore) {
                throw reader.newParseErrorAt("Short overflow detected", reader.getCurrentIndex() - position);
            }
        }
        final int start = reader.scanNumber();
        final int end = reader.getCurrentIndex();
        final byte[] buf = reader.buffer;
        final byte ch = buf[start];
        final int value = ch == '-'
                ? parseNegativeInt(buf, reader, start, end)
                : parsePositiveInt(buf, reader, start, end, 0);
        if (value < Short.MIN_VALUE || value > Short.MAX_VALUE) {
            throw reader.newParseErrorAt("Short overflow detected", reader.getCurrentIndex());
        }
        return (short) value;
    }

    public static int deserializeInt(final JsonReader reader) throws IOException {
        if (reader.last() == '"') {
            final int position = reader.getCurrentIndex();
            final char[] buf = reader.readSimpleQuote();
            try {
                return parseNumberGeneric(buf, reader.getCurrentIndex() - position - 1, reader, true).intValueExact();
            } catch (ArithmeticException ignore) {
                throw reader.newParseErrorAt("Integer overflow detected", reader.getCurrentIndex() - position);
            }
        }
        final int start = reader.scanNumber();
        final int end = reader.getCurrentIndex();
        final byte[] buf = reader.buffer;
        final byte ch = buf[start];
        if (ch == '-') {
            if (end > start + 2 && buf[start + 1] == '0' && buf[start + 2] >= '0' && buf[start + 2] <= '9') {
                numberException(reader, start, end, "Leading zero is not allowed");
            }
            return parseNegativeInt(buf, reader, start, end);
        } else {
            if (ch == '0' && end > start + 1 && buf[start + 1] >= '0' && buf[start + 1] <= '9') {
                numberException(reader, start, end, "Leading zero is not allowed");
            }
            return parsePositiveInt(buf, reader, start, end, 0);
        }
    }

    private static int parsePositiveInt(final byte[] buf, final JsonReader reader, final int start, final int end, final int offset) throws IOException {
        int value = 0;
        int i = start + offset;
        if (i == end) {
            numberException(reader, start, end, "Digit not found");
        }
        for (; i < end; i++) {
            final int ind = buf[i] - 48;
            if (ind < 0 || ind > 9) {
                if (i > start + offset && reader.allWhitespace(i, end)) {
                    return value;
                } else if (i == end - 1 && buf[i] == '.') {
                    numberException(reader, start, end, "Number ends with a dot");
                }
                final BigDecimal v = parseNumberGeneric(reader.prepareBuffer(start, end - start), end - start, reader, false);
                if (v.scale() > 0) {
                    numberException(reader, start, end, "Expecting int but found decimal value", v);
                }
                return v.intValue();

            }
            value = (value << 3) + (value << 1) + ind;
            if (value < 0) {
                numberException(reader, start, end, "Integer overflow detected");
            }
        }
        return value;
    }

    private static int parseNegativeInt(final byte[] buf, final JsonReader reader, final int start, final int end) throws IOException {
        int value = 0;
        int i = start + 1;
        if (i == end) {
            numberException(reader, start, end, "Digit not found");
        }
        for (; i < end; i++) {
            final int ind = buf[i] - 48;
            if (ind < 0 || ind > 9) {
                if (i > start + 1 && reader.allWhitespace(i, end)) {
                    return value;
                } else if (i == end - 1 && buf[i] == '.') {
                    numberException(reader, start, end, "Number ends with a dot");
                }
                final BigDecimal v = parseNumberGeneric(reader.prepareBuffer(start, end - start), end - start, reader, false);
                if (v.scale() > 0) {
                    numberException(reader, start, end, "Expecting int but found decimal value", v);
                }
                return v.intValue();
            }
            value = (value << 3) + (value << 1) - ind;
            if (value > 0) {
                numberException(reader, start, end, "Integer overflow detected");
            }
        }
        return value;
    }

    @SuppressWarnings("unchecked")
    public static ArrayList<Integer> deserializeIntCollection(final JsonReader reader) throws IOException {
        return reader.deserializeCollection(INT_READER);
    }

    public static int[] deserializeIntArray(final JsonReader reader) throws IOException {
        if (reader.last() == ']') {
            return INT_EMPTY_ARRAY;
        }
        int[] buffer = new int[4];
        buffer[0] = deserializeInt(reader);
        int i = 1;
        while (reader.getNextToken() == ',') {
            reader.getNextToken();
            if (i == buffer.length) {
                buffer = Arrays.copyOf(buffer, buffer.length << 1);
            }
            buffer[i++] = deserializeInt(reader);
        }
        reader.checkArrayEnd();
        return Arrays.copyOf(buffer, i);
    }

    public static short[] deserializeShortArray(final JsonReader reader) throws IOException {
        if (reader.last() == ']') {
            return SHORT_EMPTY_ARRAY;
        }
        short[] buffer = new short[4];
        buffer[0] = (short) deserializeInt(reader);
        int i = 1;
        while (reader.getNextToken() == ',') {
            reader.getNextToken();
            if (i == buffer.length) {
                buffer = Arrays.copyOf(buffer, buffer.length << 1);
            }
            buffer[i++] = (short) deserializeInt(reader);
        }
        reader.checkArrayEnd();
        return Arrays.copyOf(buffer, i);
    }

    public static long[] deserializeLongArray(final JsonReader reader) throws IOException {
        if (reader.last() == ']') {
            return LONG_EMPTY_ARRAY;
        }
        long[] buffer = new long[4];
        buffer[0] = deserializeLong(reader);
        int i = 1;
        while (reader.getNextToken() == ',') {
            reader.getNextToken();
            if (i == buffer.length) {
                buffer = Arrays.copyOf(buffer, buffer.length << 1);
            }
            buffer[i++] = deserializeLong(reader);
        }
        reader.checkArrayEnd();
        return Arrays.copyOf(buffer, i);
    }

    public static float[] deserializeFloatArray(final JsonReader reader) throws IOException {
        if (reader.last() == ']') {
            return FLOAT_EMPTY_ARRAY;
        }
        float[] buffer = new float[4];
        buffer[0] = deserializeFloat(reader);
        int i = 1;
        while (reader.getNextToken() == ',') {
            reader.getNextToken();
            if (i == buffer.length) {
                buffer = Arrays.copyOf(buffer, buffer.length << 1);
            }
            buffer[i++] = deserializeFloat(reader);
        }
        reader.checkArrayEnd();
        return Arrays.copyOf(buffer, i);
    }

    public static double[] deserializeDoubleArray(final JsonReader reader) throws IOException {
        if (reader.last() == ']') {
            return DOUBLE_EMPTY_ARRAY;
        }
        double[] buffer = new double[4];
        buffer[0] = deserializeDouble(reader);
        int i = 1;
        while (reader.getNextToken() == ',') {
            reader.getNextToken();
            if (i == buffer.length) {
                buffer = Arrays.copyOf(buffer, buffer.length << 1);
            }
            buffer[i++] = deserializeDouble(reader);
        }
        reader.checkArrayEnd();
        return Arrays.copyOf(buffer, i);
    }

    public static void deserializeShortCollection(final JsonReader reader, final Collection<Short> res) throws IOException {
        reader.deserializeCollection(SHORT_READER, res);
    }

    @SuppressWarnings("unchecked")
    public static ArrayList<Short> deserializeShortNullableCollection(final JsonReader reader) throws IOException {
        return reader.deserializeNullableCollection(SHORT_READER);
    }

    public static void deserializeShortNullableCollection(final JsonReader reader, final Collection<Short> res) throws IOException {
        reader.deserializeNullableCollection(SHORT_READER, res);
    }

    public static void deserializeIntCollection(final JsonReader reader, final Collection<Integer> res) throws IOException {
        reader.deserializeCollection(INT_READER, res);
    }

    @SuppressWarnings("unchecked")
    public static ArrayList<Integer> deserializeIntNullableCollection(final JsonReader reader) throws IOException {
        return reader.deserializeNullableCollection(INT_READER);
    }

    public static void deserializeIntNullableCollection(final JsonReader reader, final Collection<Integer> res) throws IOException {
        reader.deserializeNullableCollection(INT_READER, res);
    }

    public static void serializeNullable(@Nullable final Long value, final JsonWriter sw) {
        if (value == null) {
            sw.writeNull();
        } else {
            serialize(value, sw);
        }
    }

    private static int writeFirstBuf(final byte[] buf, final int v, int pos) {
        final int start = v >> 24;
        if (start == 0) {
            buf[pos++] = (byte) (v >> 16);
            buf[pos++] = (byte) (v >> 8);
        } else if (start == 1) {
            buf[pos++] = (byte) (v >> 8);
        }
        buf[pos] = (byte) v;
        return 3 - start;
    }

    private static void writeBuf(final byte[] buf, final int v, int pos) {
        buf[pos] = (byte) (v >> 16);
        buf[pos + 1] = (byte) (v >> 8);
        buf[pos + 2] = (byte) v;
    }

    private static final byte[] MIN_LONG = "-9223372036854775808".getBytes();

    public static void serialize(final long value, final JsonWriter sw) {
        final byte[] buf = sw.ensureCapacity(21);
        final int position = sw.size();
        int current = serialize(buf, position, value);
        sw.advance(current - position);
    }

    private static int serialize(final byte[] buf, int pos, final long value) {
        long i;
        if (value < 0) {
            if (value == Long.MIN_VALUE) {
                for (int x = 0; x < MIN_LONG.length; x++) {
                    buf[pos + x] = MIN_LONG[x];
                }
                return pos + MIN_LONG.length;
            }
            i = -value;
            buf[pos++] = MINUS;
        } else {
            i = value;
        }
        final long q1 = i / 1000;
        if (q1 == 0) {
            pos += writeFirstBuf(buf, DIGITS[(int) i], pos);
            return pos;
        }
        final int r1 = (int) (i - q1 * 1000);
        final long q2 = q1 / 1000;
        if (q2 == 0) {
            final int v1 = DIGITS[r1];
            final int v2 = DIGITS[(int) q1];
            int off = writeFirstBuf(buf, v2, pos);
            writeBuf(buf, v1, pos + off);
            return pos + 3 + off;
        }
        final int r2 = (int) (q1 - q2 * 1000);
        final long q3 = q2 / 1000;
        if (q3 == 0) {
            final int v1 = DIGITS[r1];
            final int v2 = DIGITS[r2];
            final int v3 = DIGITS[(int) q2];
            pos += writeFirstBuf(buf, v3, pos);
            writeBuf(buf, v2, pos);
            writeBuf(buf, v1, pos + 3);
            return pos + 6;
        }
        final int r3 = (int) (q2 - q3 * 1000);
        final int q4 = (int) (q3 / 1000);
        if (q4 == 0) {
            final int v1 = DIGITS[r1];
            final int v2 = DIGITS[r2];
            final int v3 = DIGITS[r3];
            final int v4 = DIGITS[(int) q3];
            pos += writeFirstBuf(buf, v4, pos);
            writeBuf(buf, v3, pos);
            writeBuf(buf, v2, pos + 3);
            writeBuf(buf, v1, pos + 6);
            return pos + 9;
        }
        final int r4 = (int) (q3 - q4 * 1000);
        final int q5 = q4 / 1000;
        if (q5 == 0) {
            final int v1 = DIGITS[r1];
            final int v2 = DIGITS[r2];
            final int v3 = DIGITS[r3];
            final int v4 = DIGITS[r4];
            final int v5 = DIGITS[q4];
            pos += writeFirstBuf(buf, v5, pos);
            writeBuf(buf, v4, pos);
            writeBuf(buf, v3, pos + 3);
            writeBuf(buf, v2, pos + 6);
            writeBuf(buf, v1, pos + 9);
            return pos + 12;
        }
        final int r5 = q4 - q5 * 1000;
        final int q6 = q5 / 1000;
        final int v1 = DIGITS[r1];
        final int v2 = DIGITS[r2];
        final int v3 = DIGITS[r3];
        final int v4 = DIGITS[r4];
        final int v5 = DIGITS[r5];
        if (q6 == 0) {
            pos += writeFirstBuf(buf, DIGITS[q5], pos);
        } else {
            final int r6 = q5 - q6 * 1000;
            buf[pos++] = (byte) (q6 + '0');
            writeBuf(buf, DIGITS[r6], pos);
            pos += 3;
        }
        writeBuf(buf, v5, pos);
        writeBuf(buf, v4, pos + 3);
        writeBuf(buf, v3, pos + 6);
        writeBuf(buf, v2, pos + 9);
        writeBuf(buf, v1, pos + 12);
        return pos + 15;
    }

    public static void serialize(@Nullable final long[] values, final JsonWriter sw) {
        if (values == null) {
            sw.writeNull();
        } else if (values.length == 0) {
            sw.writeAscii("[]");
        } else {
            final byte[] buf = sw.ensureCapacity(values.length * 21 + 2);
            int position = sw.size();
            buf[position++] = '[';
            position = serialize(buf, position, values[0]);
            for (int i = 1; i < values.length; i++) {
                buf[position++] = ',';
                position = serialize(buf, position, values[i]);
            }
            buf[position++] = ']';
            sw.advance(position - sw.size());
        }
    }

    public static long deserializeLong(final JsonReader reader) throws IOException {
        if (reader.last() == '"') {
            final int position = reader.getCurrentIndex();
            final char[] buf = reader.readSimpleQuote();
            try {
                return parseNumberGeneric(buf, reader.getCurrentIndex() - position - 1, reader, true).longValueExact();
            } catch (ArithmeticException ignore) {
                throw reader.newParseErrorAt("Long overflow detected", reader.getCurrentIndex() - position);
            }
        }
        final int start = reader.scanNumber();
        final int end = reader.getCurrentIndex();
        final byte[] buf = reader.buffer;
        final byte ch = buf[start];
        int i = start;
        long value = 0;
        if (ch == '-') {
            i = start + 1;
            if (i == end) {
                numberException(reader, start, end, "Digit not found");
            }
            final boolean leadingZero = buf[i] == 48;
            for (; i < end; i++) {
                final int ind = buf[i] - 48;
                if (ind < 0 || ind > 9) {
                    if (leadingZero && i > start + 2) {
                        numberException(reader, start, end, "Leading zero is not allowed");
                    }
                    if (i > start + 1 && reader.allWhitespace(i, end)) {
                        return value;
                    }
                    return parseLongGeneric(reader, start, end);
                }
                value = (value << 3) + (value << 1) - ind;
                if (value > 0) {
                    numberException(reader, start, end, "Long overflow detected");
                }
            }
            if (leadingZero && i > start + 2) {
                numberException(reader, start, end, "Leading zero is not allowed");
            }
            return value;
        }
        if (i == end) {
            numberException(reader, start, end, "Digit not found");
        }
        final boolean leadingZero = buf[i] == 48;
        for (; i < end; i++) {
            final int ind = buf[i] - 48;
            if (ind < 0 || ind > 9) {
                if (leadingZero && i > start + 1) {
                    numberException(reader, start, end, "Leading zero is not allowed");
                }
                if (ch == '+' && i > start + 1 && reader.allWhitespace(i, end)) {
                    return value;
                } else if (ch != '+' && i > start && reader.allWhitespace(i, end)) {
                    return value;
                }
                return parseLongGeneric(reader, start, end);
            }
            value = (value << 3) + (value << 1) + ind;
            if (value < 0) {
                numberException(reader, start, end, "Long overflow detected");
            }
        }
        if (leadingZero && i > start + 1) {
            numberException(reader, start, end, "Leading zero is not allowed");
        }
        return value;
    }

    private static long parseLongGeneric(final JsonReader reader, final int start, final int end) throws IOException {
        final int len = end - start;
        final char[] buf = reader.prepareBuffer(start, len);
        if (len > 0 && buf[len - 1] == '.') {
            numberException(reader, start, end, "Number ends with a dot");
        }
        final BigDecimal v = parseNumberGeneric(buf, len, reader, false);
        if (v.scale() > 0) {
            numberException(reader, start, end, "Expecting long, but found decimal value ", v);
        }
        return v.longValue();
    }

    @SuppressWarnings("unchecked")
    public static ArrayList<Long> deserializeLongCollection(final JsonReader reader) throws IOException {
        return reader.deserializeCollection(LONG_READER);
    }

    public static void deserializeLongCollection(final JsonReader reader, final Collection<Long> res) throws IOException {
        reader.deserializeCollection(LONG_READER, res);
    }

    @SuppressWarnings("unchecked")
    public static ArrayList<Long> deserializeLongNullableCollection(final JsonReader reader) throws IOException {
        return reader.deserializeNullableCollection(LONG_READER);
    }

    public static void deserializeLongNullableCollection(final JsonReader reader, final Collection<Long> res) throws IOException {
        reader.deserializeNullableCollection(LONG_READER, res);
    }

    public static void serializeNullable(@Nullable final BigDecimal value, final JsonWriter sw) {
        if (value == null) {
            sw.writeNull();
        } else {
            sw.writeAscii(value.toString());
        }
    }

    public static void serialize(final BigDecimal value, final JsonWriter sw) {
        sw.writeAscii(value.toString());
    }

    public static BigDecimal deserializeDecimal(final JsonReader reader) throws IOException {
        if (reader.last() == '"') {
            final int len = reader.parseString();
            return parseNumberGeneric(reader.chars, len, reader, true);
        }
        final int start = reader.scanNumber();
        int end = reader.getCurrentIndex();
        if (end == reader.length()) {
            NumberInfo info = readLongNumber(reader, start);
            return parseNumberGeneric(info.buffer, info.length, reader, false);
        }
        int len = end - start;
        if (len > 18) {
            return parseNumberGeneric(reader.prepareBuffer(start, len), len, reader, false);
        }
        final byte[] buf = reader.buffer;
        final byte ch = buf[start];
        if (ch == '-') {
            return parseNegativeDecimal(buf, reader, start, end);
        }
        return parsePositiveDecimal(buf, reader, start, end);
    }

    private static BigDecimal parsePositiveDecimal(final byte[] buf, final JsonReader reader, final int start, final int end) throws IOException {
        long value = 0;
        byte ch = ' ';
        int i = start;
        final boolean leadingZero = buf[start] == 48;
        for (; i < end; i++) {
            ch = buf[i];
            if (ch == '.' || ch == 'e' || ch == 'E') {
                break;
            }
            final int ind = ch - 48;
            if (ind < 0 || ind > 9) {
                if (leadingZero && i > start + 1) {
                    numberException(reader, start, end, "Leading zero is not allowed");
                }
                if (i > start && reader.allWhitespace(i, end)) {
                    return BigDecimal.valueOf(value);
                }
                numberException(reader, start, end, "Unknown digit", (char) ch);
            }
            value = (value << 3) + (value << 1) + ind;
        }
        if (i == start) {
            numberException(reader, start, end, "Digit not found");
        } else if (leadingZero && ch != '.' && i > start + 1) {
            numberException(reader, start, end, "Leading zero is not allowed");
        } else if (i == end) {
            return BigDecimal.valueOf(value);
        } else if (ch == '.') {
            i++;
            if (i == end) {
                numberException(reader, start, end, "Number ends with a dot");
            }
            int dp = i;
            for (; i < end; i++) {
                ch = buf[i];
                if (ch == 'e' || ch == 'E') {
                    break;
                }
                final int ind = ch - 48;
                if (ind < 0 || ind > 9) {
                    if (reader.allWhitespace(i, end)) {
                        return BigDecimal.valueOf(value, i - dp);
                    }
                    numberException(reader, start, end, "Unknown digit", (char) ch);
                }
                value = (value << 3) + (value << 1) + ind;
            }
            if (i == end) {
                return BigDecimal.valueOf(value, end - dp);
            } else if (ch == 'e' || ch == 'E') {
                final int ep = i;
                i++;
                ch = buf[i];
                final int exp;
                if (ch == '-') {
                    exp = parseNegativeInt(buf, reader, i, end);
                } else if (ch == '+') {
                    exp = parsePositiveInt(buf, reader, i, end, 1);
                } else {
                    exp = parsePositiveInt(buf, reader, i, end, 0);
                }
                return BigDecimal.valueOf(value, ep - dp - exp);
            }
            return BigDecimal.valueOf(value, end - dp);
        } else if (ch == 'e' || ch == 'E') {
            i++;
            ch = buf[i];
            final int exp;
            if (ch == '-') {
                exp = parseNegativeInt(buf, reader, i, end);
            } else if (ch == '+') {
                exp = parsePositiveInt(buf, reader, i, end, 1);
            } else {
                exp = parsePositiveInt(buf, reader, i, end, 0);
            }
            return BigDecimal.valueOf(value, -exp);
        }
        return BigDecimal.valueOf(value);
    }

    private static BigDecimal parseNegativeDecimal(final byte[] buf, final JsonReader reader, final int start, final int end) throws IOException {
        long value = 0;
        byte ch = ' ';
        int i = start + 1;
        final boolean leadingZero = buf[start + 1] == 48;
        for (; i < end; i++) {
            ch = buf[i];
            if (ch == '.' || ch == 'e' || ch == 'E') {
                break;
            }
            final int ind = ch - 48;
            if (ind < 0 || ind > 9) {
                if (leadingZero && i > start + 2) {
                    numberException(reader, start, end, "Leading zero is not allowed");
                }
                if (i > start + 1 && reader.allWhitespace(i, end)) {
                    return BigDecimal.valueOf(value);
                }
                numberException(reader, start, end, "Unknown digit", (char) ch);
            }
            value = (value << 3) + (value << 1) - ind;
        }
        if (i == start + 1) {
            numberException(reader, start, end, "Digit not found");
        } else if (leadingZero && ch != '.' && i > start + 2) {
            numberException(reader, start, end, "Leading zero is not allowed");
        } else if (i == end) {
            return BigDecimal.valueOf(value);
        } else if (ch == '.') {
            i++;
            if (i == end) {
                numberException(reader, start, end, "Number ends with a dot");
            }
            int dp = i;
            for (; i < end; i++) {
                ch = buf[i];
                if (ch == 'e' || ch == 'E') {
                    break;
                }
                final int ind = ch - 48;
                if (ind < 0 || ind > 9) {
                    if (reader.allWhitespace(i, end)) {
                        return BigDecimal.valueOf(value, i - dp);
                    }
                    numberException(reader, start, end, "Unknown digit", (char) ch);
                }
                value = (value << 3) + (value << 1) - ind;
            }
            if (i == end) {
                return BigDecimal.valueOf(value, end - dp);
            } else if (ch == 'e' || ch == 'E') {
                final int ep = i;
                i++;
                ch = buf[i];
                final int exp;
                if (ch == '-') {
                    exp = parseNegativeInt(buf, reader, i, end);
                } else if (ch == '+') {
                    exp = parsePositiveInt(buf, reader, i, end, 1);
                } else {
                    exp = parsePositiveInt(buf, reader, i, end, 0);
                }
                return BigDecimal.valueOf(value, ep - dp - exp);
            }
            return BigDecimal.valueOf(value, end - dp);
        } else if (ch == 'e' || ch == 'E') {
            i++;
            ch = buf[i];
            final int exp;
            if (ch == '-') {
                exp = parseNegativeInt(buf, reader, i, end);
            } else if (ch == '+') {
                exp = parsePositiveInt(buf, reader, i, end, 1);
            } else {
                exp = parsePositiveInt(buf, reader, i, end, 0);
            }
            return BigDecimal.valueOf(value, -exp);
        }
        return BigDecimal.valueOf(value);
    }

    private static final BigDecimal BD_MAX_LONG = BigDecimal.valueOf(Long.MAX_VALUE);
    private static final BigDecimal BD_MIN_LONG = BigDecimal.valueOf(Long.MIN_VALUE);

    private static Number bigDecimalOrDouble(BigDecimal num, JsonReader.UnknownNumberParsing unknownNumbers) {
        return unknownNumbers == JsonReader.UnknownNumberParsing.LONG_AND_BIGDECIMAL
                ? num
                : num.doubleValue();
    }

    private static Number tryLongFromBigDecimal(final char[] buf, final int len, JsonReader reader) throws IOException {
        final BigDecimal num = parseNumberGeneric(buf, len, reader, false);
        if (num.scale() == 0 && num.precision() <= 19) {
            if (num.signum() == 1) {
                if (num.compareTo(BD_MAX_LONG) <= 0) {
                    return num.longValue();
                }
            } else if (num.compareTo(BD_MIN_LONG) >= 0) {
                return num.longValue();
            }
        }
        return bigDecimalOrDouble(num, reader.unknownNumbers);
    }

    public static Number deserializeNumber(final JsonReader reader) throws IOException {
        if (reader.unknownNumbers == JsonReader.UnknownNumberParsing.BIGDECIMAL) {
            return deserializeDecimal(reader);
        } else if (reader.unknownNumbers == JsonReader.UnknownNumberParsing.DOUBLE) {
            return deserializeDouble(reader);
        }
        final int start = reader.scanNumber();
        int end = reader.getCurrentIndex();
        if (end == reader.length()) {
            NumberInfo info = readLongNumber(reader, start);
            return tryLongFromBigDecimal(info.buffer, info.length, reader);
        }
        int len = end - start;
        if (len > 18) {
            return tryLongFromBigDecimal(reader.prepareBuffer(start, len), len, reader);
        }
        final byte[] buf = reader.buffer;
        final byte ch = buf[start];
        if (ch == '-') {
            return parseNegativeNumber(buf, reader, start, end);
        }
        return parsePositiveNumber(buf, reader, start, end);
    }

    private static Number parsePositiveNumber(final byte[] buf, final JsonReader reader, final int start, final int end) throws IOException {
        long value = 0;
        byte ch = ' ';
        int i = start;
        final boolean leadingZero = buf[start] == 48;
        for (; i < end; i++) {
            ch = buf[i];
            if (ch == '.' || ch == 'e' || ch == 'E') {
                break;
            }
            final int ind = ch - 48;
            if (ind < 0 || ind > 9) {
                if (leadingZero && i > start + 1) {
                    numberException(reader, start, end, "Leading zero is not allowed");
                }
                if (i > start && reader.allWhitespace(i, end)) {
                    return value;
                }
                return tryLongFromBigDecimal(reader.prepareBuffer(start, end - start), end - start, reader);
            }
            value = (value << 3) + (value << 1) + ind;
        }
        if (i == start) {
            numberException(reader, start, end, "Digit not found");
        } else if (leadingZero && ch != '.' && i > start + 1) {
            numberException(reader, start, end, "Leading zero is not allowed");
        } else if (i == end) {
            return value;
        } else if (ch == '.') {
            i++;
            if (i == end) {
                numberException(reader, start, end, "Number ends with a dot");
            }
            int dp = i;
            for (; i < end; i++) {
                ch = buf[i];
                if (ch == 'e' || ch == 'E') {
                    break;
                }
                final int ind = ch - 48;
                if (ind < 0 || ind > 9) {
                    if (reader.allWhitespace(i, end)) {
                        return BigDecimal.valueOf(value, i - dp);
                    }
                    return tryLongFromBigDecimal(reader.prepareBuffer(start, end - start), end - start, reader);
                }
                value = (value << 3) + (value << 1) + ind;
            }
            if (i == end) {
                return bigDecimalOrDouble(BigDecimal.valueOf(value, end - dp), reader.unknownNumbers);
            } else if (ch == 'e' || ch == 'E') {
                final int ep = i;
                i++;
                ch = buf[i];
                final int exp;
                if (ch == '-') {
                    exp = parseNegativeInt(buf, reader, i, end);
                } else if (ch == '+') {
                    exp = parsePositiveInt(buf, reader, i, end, 1);
                } else {
                    exp = parsePositiveInt(buf, reader, i, end, 0);
                }
                return bigDecimalOrDouble(BigDecimal.valueOf(value, ep - dp - exp), reader.unknownNumbers);
            }
            return BigDecimal.valueOf(value, end - dp);
        } else if (ch == 'e' || ch == 'E') {
            i++;
            ch = buf[i];
            final int exp;
            if (ch == '-') {
                exp = parseNegativeInt(buf, reader, i, end);
            } else if (ch == '+') {
                exp = parsePositiveInt(buf, reader, i, end, 1);
            } else {
                exp = parsePositiveInt(buf, reader, i, end, 0);
            }
            return bigDecimalOrDouble(BigDecimal.valueOf(value, -exp), reader.unknownNumbers);
        }
        return bigDecimalOrDouble(BigDecimal.valueOf(value), reader.unknownNumbers);
    }

    private static Number parseNegativeNumber(final byte[] buf, final JsonReader reader, final int start, final int end) throws IOException {
        long value = 0;
        byte ch = ' ';
        int i = start + 1;
        final boolean leadingZero = buf[start + 1] == 48;
        for (; i < end; i++) {
            ch = buf[i];
            if (ch == '.' || ch == 'e' || ch == 'E') {
                break;
            }
            final int ind = ch - 48;
            if (ind < 0 || ind > 9) {
                if (leadingZero && i > start + 2) {
                    numberException(reader, start, end, "Leading zero is not allowed");
                }
                if (i > start + 1 && reader.allWhitespace(i, end)) {
                    return value;
                }
                return tryLongFromBigDecimal(reader.prepareBuffer(start, end - start), end - start, reader);
            }
            value = (value << 3) + (value << 1) - ind;
        }
        if (i == start + 1) {
            numberException(reader, start, end, "Digit not found");
        } else if (leadingZero && ch != '.' && i > start + 2) {
            numberException(reader, start, end, "Leading zero is not allowed");
        } else if (i == end) {
            return value;
        } else if (ch == '.') {
            i++;
            if (i == end) {
                numberException(reader, start, end, "Number ends with a dot");
            }
            int dp = i;
            for (; i < end; i++) {
                ch = buf[i];
                if (ch == 'e' || ch == 'E') {
                    break;
                }
                final int ind = ch - 48;
                if (ind < 0 || ind > 9) {
                    if (reader.allWhitespace(i, end)) {
                        return BigDecimal.valueOf(value, i - dp);
                    }
                    return tryLongFromBigDecimal(reader.prepareBuffer(start, end - start), end - start, reader);
                }
                value = (value << 3) + (value << 1) - ind;
            }
            if (i == end) {
                return bigDecimalOrDouble(BigDecimal.valueOf(value, end - dp), reader.unknownNumbers);
            } else if (ch == 'e' || ch == 'E') {
                final int ep = i;
                i++;
                ch = buf[i];
                final int exp;
                if (ch == '-') {
                    exp = parseNegativeInt(buf, reader, i, end);
                } else if (ch == '+') {
                    exp = parsePositiveInt(buf, reader, i, end, 1);
                } else {
                    exp = parsePositiveInt(buf, reader, i, end, 0);
                }
                return bigDecimalOrDouble(BigDecimal.valueOf(value, ep - dp - exp), reader.unknownNumbers);
            }
            return bigDecimalOrDouble(BigDecimal.valueOf(value, end - dp), reader.unknownNumbers);
        } else if (ch == 'e' || ch == 'E') {
            i++;
            ch = buf[i];
            final int exp;
            if (ch == '-') {
                exp = parseNegativeInt(buf, reader, i, end);
            } else if (ch == '+') {
                exp = parsePositiveInt(buf, reader, i, end, 1);
            } else {
                exp = parsePositiveInt(buf, reader, i, end, 0);
            }
            return bigDecimalOrDouble(BigDecimal.valueOf(value, -exp), reader.unknownNumbers);
        }
        return bigDecimalOrDouble(BigDecimal.valueOf(value), reader.unknownNumbers);
    }

    @SuppressWarnings("unchecked")
    public static ArrayList<BigDecimal> deserializeDecimalCollection(final JsonReader reader) throws IOException {
        return reader.deserializeCollection(DecimalReader);
    }

    public static void deserializeDecimalCollection(final JsonReader reader, final Collection<BigDecimal> res) throws IOException {
        reader.deserializeCollection(DecimalReader, res);
    }

    @SuppressWarnings("unchecked")
    public static ArrayList<BigDecimal> deserializeDecimalNullableCollection(final JsonReader reader) throws IOException {
        return reader.deserializeNullableCollection(DecimalReader);
    }

    public static void deserializeDecimalNullableCollection(final JsonReader reader, final Collection<BigDecimal> res) throws IOException {
        reader.deserializeNullableCollection(DecimalReader, res);
    }
}