/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.apache.cassandra.cql3;

import java.math.BigDecimal;
import java.math.BigInteger;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.nio.ByteBuffer;
import java.util.*;
import java.util.concurrent.ThreadLocalRandom;
import java.util.regex.Pattern;

import org.junit.Test;

import org.apache.cassandra.db.marshal.*;
import org.apache.cassandra.serializers.*;
import org.apache.cassandra.transport.ProtocolVersion;
import org.apache.cassandra.utils.ByteBufferUtil;
import org.apache.cassandra.utils.UUIDGen;

import static org.junit.Assert.assertEquals;

/**
 * Test functionality to re-create a CQL literal from its serialized representation.
 * This test uses some randomness to generate the values and nested structures (collections,tuples,UDTs).
 */
public class CQL3TypeLiteralTest
{
    private static final Pattern QUOTE = Pattern.compile("'");

    /**
     * Container holding the expected CQL literal for a type and serialized value.
     * The CQL literal is generated independently from the code in {@link CQL3Type}.
     */
    static class Value
    {
        final String expected;
        final CQL3Type cql3Type;
        final ByteBuffer value;

        Value(String expected, CQL3Type cql3Type, ByteBuffer value)
        {
            this.expected = expected;
            this.cql3Type = cql3Type;
            this.value = value;
        }
    }

    static final Map<CQL3Type.Native, List<Value>> nativeTypeValues = new EnumMap<>(CQL3Type.Native.class);

    static void addNativeValue(String expected, CQL3Type.Native cql3Type, ByteBuffer value)
    {
        List<Value> l = nativeTypeValues.get(cql3Type);
        if (l == null)
            nativeTypeValues.put(cql3Type, l = new ArrayList<>());
        l.add(new Value(expected, cql3Type, value));
    }

    static
    {
        // Add some (random) values for each native type.
        // Also adds null values and empty values, if the type allows this.

        for (int i = 0; i < 20; i++)
        {
            String v = randString(true);
            addNativeValue(quote(v), CQL3Type.Native.ASCII, AsciiSerializer.instance.serialize(v));
        }
        addNativeValue("''", CQL3Type.Native.ASCII, AsciiSerializer.instance.serialize(""));
        addNativeValue("''", CQL3Type.Native.ASCII, ByteBufferUtil.EMPTY_BYTE_BUFFER);
        addNativeValue("null", CQL3Type.Native.ASCII, null);

        for (int i = 0; i < 20; i++)
        {
            String v = randString(false);
            addNativeValue(quote(v), CQL3Type.Native.TEXT, UTF8Serializer.instance.serialize(v));
        }
        addNativeValue("''", CQL3Type.Native.TEXT, UTF8Serializer.instance.serialize(""));
        addNativeValue("''", CQL3Type.Native.TEXT, ByteBufferUtil.EMPTY_BYTE_BUFFER);
        addNativeValue("null", CQL3Type.Native.TEXT, null);

        for (int i = 0; i < 20; i++)
        {
            String v = randString(false);
            addNativeValue(quote(v), CQL3Type.Native.VARCHAR, UTF8Serializer.instance.serialize(v));
        }
        addNativeValue("''", CQL3Type.Native.VARCHAR, UTF8Serializer.instance.serialize(""));
        addNativeValue("''", CQL3Type.Native.VARCHAR, ByteBufferUtil.EMPTY_BYTE_BUFFER);
        addNativeValue("null", CQL3Type.Native.VARCHAR, null);

        addNativeValue("0", CQL3Type.Native.BIGINT, LongType.instance.decompose(0L));
        for (int i = 0; i < 20; i++)
        {
            long v = randLong();
            addNativeValue(Long.toString(v), CQL3Type.Native.BIGINT, LongType.instance.decompose(v));
        }
        addNativeValue("null", CQL3Type.Native.BIGINT, ByteBufferUtil.EMPTY_BYTE_BUFFER);
        addNativeValue("null", CQL3Type.Native.BIGINT, null);

        addNativeValue("0", CQL3Type.Native.COUNTER, LongType.instance.decompose(0L));
        for (int i = 0; i < 20; i++)
        {
            long v = randLong();
            addNativeValue(Long.toString(v), CQL3Type.Native.COUNTER, LongType.instance.decompose(v));
        }
        addNativeValue("null", CQL3Type.Native.COUNTER, ByteBufferUtil.EMPTY_BYTE_BUFFER);
        addNativeValue("null", CQL3Type.Native.COUNTER, null);

        addNativeValue("0", CQL3Type.Native.INT, Int32Type.instance.decompose(0));
        for (int i = 0; i < 20; i++)
        {
            int v = randInt();
            addNativeValue(Integer.toString(v), CQL3Type.Native.INT, Int32Type.instance.decompose(v));
        }
        addNativeValue("null", CQL3Type.Native.INT, ByteBufferUtil.EMPTY_BYTE_BUFFER);
        addNativeValue("null", CQL3Type.Native.INT, null);

        addNativeValue("0", CQL3Type.Native.SMALLINT, ShortType.instance.decompose((short) 0));
        for (int i = 0; i < 20; i++)
        {
            short v = randShort();
            addNativeValue(Short.toString(v), CQL3Type.Native.SMALLINT, ShortType.instance.decompose(v));
        }
        addNativeValue("null", CQL3Type.Native.SMALLINT, null);

        addNativeValue("0", CQL3Type.Native.TINYINT, ByteType.instance.decompose((byte) 0));
        for (int i = 0; i < 20; i++)
        {
            byte v = randByte();
            addNativeValue(Short.toString(v), CQL3Type.Native.TINYINT, ByteType.instance.decompose(v));
        }
        addNativeValue("null", CQL3Type.Native.TINYINT, null);

        addNativeValue("0.0", CQL3Type.Native.FLOAT, FloatType.instance.decompose((float) 0));
        for (int i = 0; i < 20; i++)
        {
            float v = randFloat();
            addNativeValue(Float.toString(v), CQL3Type.Native.FLOAT, FloatType.instance.decompose(v));
        }
        addNativeValue("NaN", CQL3Type.Native.FLOAT, FloatType.instance.decompose(Float.NaN));
        addNativeValue("null", CQL3Type.Native.FLOAT, ByteBufferUtil.EMPTY_BYTE_BUFFER);
        addNativeValue("null", CQL3Type.Native.FLOAT, null);

        addNativeValue("0.0", CQL3Type.Native.DOUBLE, DoubleType.instance.decompose((double) 0));
        for (int i = 0; i < 20; i++)
        {
            double v = randDouble();
            addNativeValue(Double.toString(v), CQL3Type.Native.DOUBLE, DoubleType.instance.decompose(v));
        }
        addNativeValue("NaN", CQL3Type.Native.DOUBLE, DoubleType.instance.decompose(Double.NaN));
        addNativeValue("null", CQL3Type.Native.DOUBLE, ByteBufferUtil.EMPTY_BYTE_BUFFER);
        addNativeValue("null", CQL3Type.Native.DOUBLE, null);

        addNativeValue("0", CQL3Type.Native.DECIMAL, DecimalType.instance.decompose(BigDecimal.ZERO));
        for (int i = 0; i < 20; i++)
        {
            BigDecimal v = BigDecimal.valueOf(randDouble());
            addNativeValue(v.toString(), CQL3Type.Native.DECIMAL, DecimalType.instance.decompose(v));
        }
        addNativeValue("null", CQL3Type.Native.DECIMAL, ByteBufferUtil.EMPTY_BYTE_BUFFER);
        addNativeValue("null", CQL3Type.Native.DECIMAL, null);

        addNativeValue("0", CQL3Type.Native.VARINT, IntegerType.instance.decompose(BigInteger.ZERO));
        for (int i = 0; i < 20; i++)
        {
            BigInteger v = BigInteger.valueOf(randLong());
            addNativeValue(v.toString(), CQL3Type.Native.VARINT, IntegerType.instance.decompose(v));
        }
        addNativeValue("null", CQL3Type.Native.VARINT, ByteBufferUtil.EMPTY_BYTE_BUFFER);
        addNativeValue("null", CQL3Type.Native.VARINT, null);

        // boolean doesn't have that many possible values...
        addNativeValue("false", CQL3Type.Native.BOOLEAN, BooleanType.instance.decompose(false));
        addNativeValue("true", CQL3Type.Native.BOOLEAN, BooleanType.instance.decompose(true));
        addNativeValue("null", CQL3Type.Native.BOOLEAN, ByteBufferUtil.EMPTY_BYTE_BUFFER);
        addNativeValue("null", CQL3Type.Native.BOOLEAN, null);

        // (mostly generates date values with surreal values like in year 14273)
        for (int i = 0; i < 20; i++)
        {
            int v = randInt();
            addNativeValue(SimpleDateSerializer.instance.toString(v), CQL3Type.Native.DATE, SimpleDateSerializer.instance.serialize(v));
        }
        addNativeValue("null", CQL3Type.Native.DATE, null);

        for (int i = 0; i < 100; i++)
        {
            long v = randLong(24L * 60 * 60 * 1000 * 1000 * 1000);
            addNativeValue(TimeSerializer.instance.toString(v), CQL3Type.Native.TIME, TimeSerializer.instance.serialize(v));
        }
        addNativeValue("null", CQL3Type.Native.TIME, null);

        for (int i = 0; i < 100; i++)
        {
            Duration duration = Duration.newInstance(Math.abs(randInt()), Math.abs(randInt()), Math.abs(randLong()));
            addNativeValue(DurationSerializer.instance.toString(duration), CQL3Type.Native.DURATION, DurationSerializer.instance.serialize(duration));
        }
        addNativeValue("null", CQL3Type.Native.DURATION, null);

        // (mostly generates timestamp values with surreal values like in year 14273)
        for (int i = 0; i < 20; i++)
        {
            long v = randLong();
            addNativeValue(TimestampSerializer.instance.toStringUTC(new Date(v)), CQL3Type.Native.TIMESTAMP, TimestampType.instance.fromString(Long.toString(v)));
        }
        addNativeValue("null", CQL3Type.Native.TIMESTAMP, ByteBufferUtil.EMPTY_BYTE_BUFFER);
        addNativeValue("null", CQL3Type.Native.TIMESTAMP, null);

        for (int i = 0; i < 20; i++)
        {
            UUID v = UUIDGen.getTimeUUID(randLong(System.currentTimeMillis()));
            addNativeValue(v.toString(), CQL3Type.Native.TIMEUUID, TimeUUIDType.instance.decompose(v));
        }
        addNativeValue("null", CQL3Type.Native.TIMEUUID, ByteBufferUtil.EMPTY_BYTE_BUFFER);
        addNativeValue("null", CQL3Type.Native.TIMEUUID, null);

        for (int i = 0; i < 20; i++)
        {
            UUID v = UUID.randomUUID();
            addNativeValue(v.toString(), CQL3Type.Native.UUID, UUIDType.instance.decompose(v));
        }
        addNativeValue("null", CQL3Type.Native.UUID, ByteBufferUtil.EMPTY_BYTE_BUFFER);
        addNativeValue("null", CQL3Type.Native.UUID, null);

        for (int i = 0; i < 20; i++)
        {
            ByteBuffer v = randBytes();
            addNativeValue("0x" + BytesSerializer.instance.toString(v), CQL3Type.Native.BLOB, BytesType.instance.decompose(v));
        }
        addNativeValue("0x", CQL3Type.Native.BLOB, ByteBufferUtil.EMPTY_BYTE_BUFFER);
        addNativeValue("null", CQL3Type.Native.BLOB, null);

        for (int i = 0; i < 20; i++)
        {
            InetAddress v;
            try
            {
                v = InetAddress.getByAddress(new byte[]{ randByte(), randByte(), randByte(), randByte() });
            }
            catch (UnknownHostException e)
            {
                throw new RuntimeException(e);
            }
            addNativeValue(v.getHostAddress(), CQL3Type.Native.INET, InetAddressSerializer.instance.serialize(v));
        }
        addNativeValue("null", CQL3Type.Native.INET, ByteBufferUtil.EMPTY_BYTE_BUFFER);
        addNativeValue("null", CQL3Type.Native.INET, null);
    }

    @Test
    public void testNative()
    {
        // test each native type against each supported protocol version (although it doesn't make sense to
        // iterate through all protocol versions as of C* 3.0).

        for (ProtocolVersion version : ProtocolVersion.SUPPORTED)
        {
            for (Map.Entry<CQL3Type.Native, List<Value>> entry : nativeTypeValues.entrySet())
            {
                for (Value value : entry.getValue())
                {
                    compareCqlLiteral(version, value);
                }
            }
        }
    }

    @Test
    public void testCollectionWithNatives()
    {
        // test 100 collections with varying element/key/value types against each supported protocol version,
        // type of collection is randomly chosen

        for (ProtocolVersion version : ProtocolVersion.SUPPORTED)
        {
            for (int n = 0; n < 100; n++)
            {
                Value value = generateCollectionValue(version, randomCollectionType(0), true);
                compareCqlLiteral(version, value);
            }
        }
    }

    @Test
    public void testCollectionNullAndEmpty()
    {
        // An empty collection is one with a size of 0 (note that rely on the fact that protocol version < 3 are not
        // supported anymore and so the size of a collection is always on 4 bytes).
        ByteBuffer emptyCollection = ByteBufferUtil.bytes(0);

        for (ProtocolVersion version : ProtocolVersion.SUPPORTED)
        {
            for (boolean frozen : Arrays.asList(true, false))
            {
                // empty
                Value value = new Value("[]", ListType.getInstance(UTF8Type.instance, frozen).asCQL3Type(), emptyCollection);
                compareCqlLiteral(version, value);
                value = new Value("{}", SetType.getInstance(UTF8Type.instance, frozen).asCQL3Type(), emptyCollection);
                compareCqlLiteral(version, value);
                value = new Value("{}", MapType.getInstance(UTF8Type.instance, UTF8Type.instance, frozen).asCQL3Type(), emptyCollection);
                compareCqlLiteral(version, value);

                // null
                value = new Value("null", ListType.getInstance(UTF8Type.instance, frozen).asCQL3Type(), null);
                compareCqlLiteral(version, value);
                value = new Value("null", SetType.getInstance(UTF8Type.instance, frozen).asCQL3Type(), null);
                compareCqlLiteral(version, value);
                value = new Value("null", MapType.getInstance(UTF8Type.instance, UTF8Type.instance, frozen).asCQL3Type(), null);
                compareCqlLiteral(version, value);
            }
        }
    }

    @Test
    public void testTupleWithNatives()
    {
        // test 100 tuples with varying element/key/value types against each supported protocol version

        for (ProtocolVersion version : ProtocolVersion.SUPPORTED)
        {
            for (int n = 0; n < 100; n++)
            {
                Value value = generateTupleValue(version, randomTupleType(0), true);
                compareCqlLiteral(version, value);
            }
        }
    }

    @Test
    public void testUserDefinedWithNatives()
    {
        // test 100 UDTs with varying element/key/value types against each supported protocol version

        for (ProtocolVersion version : ProtocolVersion.SUPPORTED)
        {
            for (int n = 0; n < 100; n++)
            {
                Value value = generateUserDefinedValue(version, randomUserType(0), true);
                compareCqlLiteral(version, value);
            }
        }
    }

    @Test
    public void testNested()
    {
        // This is the "nice" part of this unit test - it tests (probably) nested type structures
        // like 'tuple<map, list<user>, tuple, user>' or 'map<tuple<int, text>, set<inet>>' with
        // random types  against each supported protocol version.

        for (ProtocolVersion version : ProtocolVersion.SUPPORTED)
        {
            for (int n = 0; n < 100; n++)
            {
                Value value = randomNested(version);
                compareCqlLiteral(version, value);
            }
        }
    }

    static void compareCqlLiteral(ProtocolVersion version, Value value)
    {
        ByteBuffer buffer = value.value != null ? value.value.duplicate() : null;
        String msg = "Failed to get expected value for type " + value.cql3Type + " / " + value.cql3Type.getType() + " with protocol-version " + version + " expected:\"" + value.expected + '"';
        try
        {
            assertEquals(msg,
                         value.expected,
                         value.cql3Type.toCQLLiteral(buffer, version));
        }
        catch (RuntimeException e)
        {
            throw new RuntimeException(msg, e);
        }
    }

    static Value randomNested(ProtocolVersion version)
    {
        AbstractType type = randomNestedType(2);

        return generateAnyValue(version, type.asCQL3Type());
    }

    /**
     * Generates type of randomly nested type structures.
     */
    static AbstractType randomNestedType(int level)
    {
        if (level == 0)
            return randomNativeType();
        switch (randInt(level == 2 ? 3 : 4))
        {
            case 0:
                return randomCollectionType(level - 1);
            case 1:
                return randomTupleType(level - 1);
            case 2:
                return randomUserType(level - 1);
            case 3:
                return randomNativeType();
        }
        throw new AssertionError();
    }

    static Value generateCollectionValue(ProtocolVersion version, CollectionType collectionType, boolean allowNull)
    {
        StringBuilder expected = new StringBuilder();
        ByteBuffer buffer;

        if (allowNull && randBool(0.05d))
        {
            expected.append("null");
            buffer = null;
        }
        else
        {
            int size = randInt(20);

            CQL3Type elements;
            CQL3Type values = null;
            char bracketOpen;
            char bracketClose;
            switch (collectionType.kind)
            {
                case LIST:
                    elements = ((ListType) collectionType).getElementsType().asCQL3Type();
                    bracketOpen = '[';
                    bracketClose = ']';
                    break;
                case SET:
                    elements = ((SetType) collectionType).getElementsType().asCQL3Type();
                    bracketOpen = '{';
                    bracketClose = '}';
                    break;
                case MAP:
                    elements = ((MapType) collectionType).getKeysType().asCQL3Type();
                    values = ((MapType) collectionType).getValuesType().asCQL3Type();
                    bracketOpen = '{';
                    bracketClose = '}';
                    break;
                default:
                    throw new AssertionError();
            }

            expected.append(bracketOpen);
            Collection<ByteBuffer> buffers = new ArrayList<>();
            Set<ByteBuffer> added = new HashSet<>();
            for (int i = 0; i < size; i++)
            {
                Value el = generateAnyValue(version, elements);
                if (!added.add(el.value))
                    continue;

                buffers.add(el.value.duplicate());
                if (expected.length() > 1)
                    expected.append(", ");
                expected.append(el.cql3Type.toCQLLiteral(el.value, version));

                if (collectionType.kind == CollectionType.Kind.MAP)
                {
                    // add map value
                    el = generateAnyValue(version, values);
                    buffers.add(el.value.duplicate());
                    expected.append(": ");
                    expected.append(el.cql3Type.toCQLLiteral(el.value, version));
                }
            }
            expected.append(bracketClose);
            buffer = CollectionSerializer.pack(buffers, added.size(), version);
        }

        return new Value(expected.toString(), collectionType.asCQL3Type(), buffer);
    }

    /**
     * Generates a value for any type or type structure.
     */
    static Value generateAnyValue(ProtocolVersion version, CQL3Type type)
    {
        if (type instanceof CQL3Type.Native)
            return generateNativeValue(type, false);
        if (type instanceof CQL3Type.Tuple)
            return generateTupleValue(version, (TupleType) type.getType(), false);
        if (type instanceof CQL3Type.UserDefined)
            return generateUserDefinedValue(version, (UserType) type.getType(), false);
        if (type instanceof CQL3Type.Collection)
            return generateCollectionValue(version, (CollectionType) type.getType(), false);
        throw new AssertionError();
    }

    static Value generateTupleValue(ProtocolVersion version, TupleType tupleType, boolean allowNull)
    {
        StringBuilder expected = new StringBuilder();
        ByteBuffer buffer;

        if (allowNull && randBool(0.05d))
        {
            // generate 'null' collection
            expected.append("null");
            buffer = null;
        }
        else
        {
            expected.append('(');

            // # of fields in this value
            int fields = tupleType.size();
            if (randBool(0.2d))
                fields = randInt(fields);

            ByteBuffer[] buffers = new ByteBuffer[fields];
            for (int i = 0; i < fields; i++)
            {
                AbstractType<?> fieldType = tupleType.type(i);

                if (i > 0)
                    expected.append(", ");

                if (allowNull && randBool(.1))
                {
                    expected.append("null");
                    continue;
                }

                Value value = generateAnyValue(version, fieldType.asCQL3Type());
                expected.append(value.expected);
                buffers[i] = value.value.duplicate();
            }
            expected.append(')');
            buffer = TupleType.buildValue(buffers);
        }

        return new Value(expected.toString(), tupleType.asCQL3Type(), buffer);
    }

    static Value generateUserDefinedValue(ProtocolVersion version, UserType userType, boolean allowNull)
    {
        StringBuilder expected = new StringBuilder();
        ByteBuffer buffer;

        if (allowNull && randBool(0.05d))
        {
            // generate 'null' collection
            expected.append("null");
            buffer = null;
        }
        else
        {
            expected.append('{');

            // # of fields in this value
            int fields = userType.size();
            if (randBool(0.2d))
                fields = randInt(fields);

            ByteBuffer[] buffers = new ByteBuffer[fields];
            for (int i = 0; i < fields; i++)
            {
                AbstractType<?> fieldType = userType.type(i);

                if (i > 0)
                    expected.append(", ");

                expected.append(ColumnIdentifier.maybeQuote(userType.fieldNameAsString(i)));
                expected.append(": ");

                if (randBool(.1))
                {
                    expected.append("null");
                    continue;
                }

                Value value = generateAnyValue(version, fieldType.asCQL3Type());
                expected.append(value.expected);
                buffers[i] = value.value.duplicate();
            }
            expected.append('}');
            buffer = TupleType.buildValue(buffers);
        }

        return new Value(expected.toString(), userType.asCQL3Type(), buffer);
    }

    static Value generateNativeValue(CQL3Type type, boolean allowNull)
    {
        List<Value> values = nativeTypeValues.get(type);
        assert values != null : type.toString() + " needs to be defined";
        while (true)
        {
            Value v = values.get(randInt(values.size()));
            if (allowNull || v.value != null)
                return v;
        }
    }

    static CollectionType randomCollectionType(int level)
    {
        CollectionType.Kind kind = CollectionType.Kind.values()[randInt(CollectionType.Kind.values().length)];
        switch (kind)
        {
            case LIST:
            case SET:
                return ListType.getInstance(randomNestedType(level), randBool());
            case MAP:
                return MapType.getInstance(randomNestedType(level), randomNestedType(level), randBool());
        }
        throw new AssertionError();
    }

    static TupleType randomTupleType(int level)
    {
        int typeCount = 2 + randInt(5);
        List<AbstractType<?>> types = new ArrayList<>();
        for (int i = 0; i < typeCount; i++)
            types.add(randomNestedType(level));
        return new TupleType(types);
    }

    static UserType randomUserType(int level)
    {
        int typeCount = 2 + randInt(5);
        List<FieldIdentifier> names = new ArrayList<>();
        List<AbstractType<?>> types = new ArrayList<>();
        for (int i = 0; i < typeCount; i++)
        {
            names.add(FieldIdentifier.forQuoted('f' + randLetters(i)));
            types.add(randomNestedType(level));
        }
        return new UserType("ks", UTF8Type.instance.fromString("u" + randInt(1000000)), names, types, true);
    }

    //
    // Following methods are just helper methods. Mostly to generate many kinds of random values.
    //

    private static String randLetters(int len)
    {
        StringBuilder sb = new StringBuilder(len);
        while (len-- > 0)
        {
            int i = randInt(52);
            if (i < 26)
                sb.append((char) ('A' + i));
            else
                sb.append((char) ('a' + i - 26));
        }
        return sb.toString();
    }

    static AbstractType randomNativeType()
    {
        while (true)
        {
            CQL3Type.Native t = CQL3Type.Native.values()[randInt(CQL3Type.Native.values().length)];
            if (t != CQL3Type.Native.EMPTY)
                return t.getType();
        }
    }

    static boolean randBool()
    {
        return randBool(0.5d);
    }

    static boolean randBool(double probability)
    {
        return ThreadLocalRandom.current().nextDouble() < probability;
    }

    static long randLong()
    {
        return ThreadLocalRandom.current().nextLong();
    }

    static long randLong(long max)
    {
        return ThreadLocalRandom.current().nextLong(max);
    }

    static int randInt()
    {
        return ThreadLocalRandom.current().nextInt();
    }

    static int randInt(int max)
    {
        return ThreadLocalRandom.current().nextInt(max);
    }

    static short randShort()
    {
        return (short) ThreadLocalRandom.current().nextInt();
    }

    static byte randByte()
    {
        return (byte) ThreadLocalRandom.current().nextInt();
    }

    static double randDouble()
    {
        return ThreadLocalRandom.current().nextDouble();
    }

    static float randFloat()
    {
        return ThreadLocalRandom.current().nextFloat();
    }

    static String randString(boolean ascii)
    {
        int l = randInt(20);
        StringBuilder sb = new StringBuilder(l);
        for (int i = 0; i < l; i++)
        {
            if (randBool(.05))
                sb.append('\'');
            else
            {
                char c = (char) (ascii ? randInt(128) : randShort());
                sb.append(c);
            }
        }
        return UTF8Serializer.instance.deserialize(UTF8Serializer.instance.serialize(sb.toString()));
    }

    static ByteBuffer randBytes()
    {
        int l = randInt(20);
        byte[] v = new byte[l];
        for (int i = 0; i < l; i++)
        {
            v[i] = randByte();
        }
        return ByteBuffer.wrap(v);
    }

    private static String quote(String v)
    {
        return '\'' + QUOTE.matcher(v).replaceAll("''") + '\'';
    }
}
