/*
 * Copyright (C) 2018 The Android Open Source Project
 *
 * Licensed 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.
 */

import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.lang.invoke.VarHandle;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;

public class VarHandleUnitTestHelpers {
    public static boolean isRunningOnAndroid() {
        return System.getProperty("java.vm.vendor").contains("Android");
    }

    public static boolean is64Bit() {
        // The behaviour of certain accessors depends on the ISA word size.
        if (isRunningOnAndroid()) {
            try {
                Class<?> runtimeClass = Class.forName("dalvik.system.VMRuntime");
                MethodHandle getRuntimeMH =
                        MethodHandles.lookup()
                                .findStatic(
                                        runtimeClass,
                                        "getRuntime",
                                        MethodType.methodType(runtimeClass));
                Object runtime = getRuntimeMH.invoke();
                MethodHandle is64BitMH =
                        MethodHandles.lookup()
                                .findVirtual(
                                        runtimeClass,
                                        "is64Bit",
                                        MethodType.methodType(boolean.class));
                return (boolean) is64BitMH.invoke(runtime);
            } catch (Throwable t) {
                throw new RuntimeException(t);
            }
        } else {
            return System.getProperty("sun.arch.data.model").equals("64");
        }
    }

    public static boolean getBytesAs_boolean(byte[] array, int index, ByteOrder order) {
        return getBytesAs_boolean(ByteBuffer.wrap(array), index, order);
    }

    public static byte getBytesAs_byte(byte[] array, int index, ByteOrder order) {
        return getBytesAs_byte(ByteBuffer.wrap(array), index, order);
    }

    public static char getBytesAs_char(byte[] array, int index, ByteOrder order) {
        return getBytesAs_char(ByteBuffer.wrap(array), index, order);
    }

    public static short getBytesAs_short(byte[] array, int index, ByteOrder order) {
        return getBytesAs_short(ByteBuffer.wrap(array), index, order);
    }

    public static int getBytesAs_int(byte[] array, int index, ByteOrder order) {
        return getBytesAs_int(ByteBuffer.wrap(array), index, order);
    }

    public static long getBytesAs_long(byte[] array, int index, ByteOrder order) {
        return getBytesAs_long(ByteBuffer.wrap(array), index, order);
    }

    public static float getBytesAs_float(byte[] array, int index, ByteOrder order) {
        return getBytesAs_float(ByteBuffer.wrap(array), index, order);
    }

    public static double getBytesAs_double(byte[] array, int index, ByteOrder order) {
        return getBytesAs_double(ByteBuffer.wrap(array), index, order);
    }

    public static boolean getBytesAs_boolean(ByteBuffer buffer, int index, ByteOrder order) {
        return buffer.order(order).get(index) != 0;
    }

    public static byte getBytesAs_byte(ByteBuffer buffer, int index, ByteOrder order) {
        return buffer.order(order).get(index);
    }

    public static char getBytesAs_char(ByteBuffer buffer, int index, ByteOrder order) {
        return buffer.order(order).getChar(index);
    }

    public static short getBytesAs_short(ByteBuffer buffer, int index, ByteOrder order) {
        return buffer.order(order).getShort(index);
    }

    public static int getBytesAs_int(ByteBuffer buffer, int index, ByteOrder order) {
        return buffer.order(order).getInt(index);
    }

    public static long getBytesAs_long(ByteBuffer buffer, int index, ByteOrder order) {
        return buffer.order(order).getLong(index);
    }

    public static float getBytesAs_float(ByteBuffer buffer, int index, ByteOrder order) {
        return buffer.order(order).getFloat(index);
    }

    public static double getBytesAs_double(ByteBuffer buffer, int index, ByteOrder order) {
        return buffer.order(order).getDouble(index);
    }

    public static void setBytesAs_boolean(byte[] array, int index, boolean value, ByteOrder order) {
        setBytesAs_boolean(ByteBuffer.wrap(array), index, value, order);
    }

    public static void setBytesAs_byte(byte[] array, int index, byte value, ByteOrder order) {
        setBytesAs_byte(ByteBuffer.wrap(array), index, value, order);
    }

    public static void setBytesAs_char(byte[] array, int index, char value, ByteOrder order) {
        setBytesAs_char(ByteBuffer.wrap(array), index, value, order);
    }

    public static void setBytesAs_short(byte[] array, int index, short value, ByteOrder order) {
        setBytesAs_short(ByteBuffer.wrap(array), index, value, order);
    }

    public static void setBytesAs_int(byte[] array, int index, int value, ByteOrder order) {
        setBytesAs_int(ByteBuffer.wrap(array), index, value, order);
    }

    public static void setBytesAs_long(byte[] array, int index, long value, ByteOrder order) {
        setBytesAs_long(ByteBuffer.wrap(array), index, value, order);
    }

    public static void setBytesAs_float(byte[] array, int index, float value, ByteOrder order) {
        setBytesAs_float(ByteBuffer.wrap(array), index, value, order);
    }

    public static void setBytesAs_double(byte[] array, int index, double value, ByteOrder order) {
        setBytesAs_double(ByteBuffer.wrap(array), index, value, order);
    }

    public static void setBytesAs_boolean(
            ByteBuffer buffer, int index, boolean value, ByteOrder order) {
        buffer.order(order).put(index, value ? (byte) 1 : (byte) 0);
    }

    public static void setBytesAs_byte(ByteBuffer buffer, int index, byte value, ByteOrder order) {
        buffer.order(order).put(index, value);
    }

    public static void setBytesAs_char(ByteBuffer buffer, int index, char value, ByteOrder order) {
        buffer.order(order).putChar(index, value);
    }

    public static void setBytesAs_short(
            ByteBuffer buffer, int index, short value, ByteOrder order) {
        buffer.order(order).putShort(index, value);
    }

    public static void setBytesAs_int(ByteBuffer buffer, int index, int value, ByteOrder order) {
        buffer.order(order).putInt(index, value);
    }

    public static void setBytesAs_long(ByteBuffer buffer, int index, long value, ByteOrder order) {
        buffer.order(order).putLong(index, value);
    }

    public static void setBytesAs_float(
            ByteBuffer buffer, int index, float value, ByteOrder order) {
        buffer.order(order).putFloat(index, value);
    }

    public static void setBytesAs_double(
            ByteBuffer buffer, int index, double value, ByteOrder order) {
        buffer.order(order).putDouble(index, value);
    }

    // Until ART is running on an OpenJDK9 based runtime, there are no
    // calls to help with alignment. OpenJDK9 introduces
    // ByteBuffer.alignedSlice() and ByteBuffer.alignmentOffset(). RI
    // and ART have different data structure alignments which may make
    // porting code interesting.

    public static int alignedOffset_char(ByteBuffer buffer, int start) {
        return alignedOffset_short(buffer, start);
    }

    public static int alignedOffset_short(ByteBuffer buffer, int start) {
        for (int i = 0; i < Short.SIZE; ++i) {
            try {
                vh_probe_short.getVolatile(buffer, start + i);
                return start + i;
            } catch (IllegalStateException e) {
                // Unaligned access.
            }
        }
        return start;
    }

    public static int alignedOffset_int(ByteBuffer buffer, int start) {
        for (int i = 0; i < Integer.SIZE; ++i) {
            try {
                vh_probe_int.getVolatile(buffer, start + i);
                return start + i;
            } catch (IllegalStateException e) {
                // Unaligned access.
            } catch (Exception e) {
                break;
            }
        }
        return start;
    }

    public static int alignedOffset_long(ByteBuffer buffer, int start) {
        for (int i = 0; i < Long.SIZE; ++i) {
            try {
                vh_probe_long.getVolatile(buffer, start + i);
                return start + i;
            } catch (IllegalStateException e) {
                // Unaligned access.
            } catch (UnsupportedOperationException e) {
                // 64-bit operation is not supported irrespective of alignment.
                break;
            }
        }
        return start;
    }

    public static int alignedOffset_float(ByteBuffer buffer, int start) {
        return alignedOffset_int(buffer, start);
    }

    public static int alignedOffset_double(ByteBuffer buffer, int start) {
        return alignedOffset_long(buffer, start);
    }

    public static int alignedOffset_char(byte[] array, int start) {
        return alignedOffset_char(ByteBuffer.wrap(array), start);
    }

    public static int alignedOffset_short(byte[] array, int start) {
        return alignedOffset_short(ByteBuffer.wrap(array), start);
    }

    public static int alignedOffset_int(byte[] array, int start) {
        return alignedOffset_int(ByteBuffer.wrap(array), start);
    }

    public static int alignedOffset_long(byte[] array, int start) {
        return alignedOffset_long(ByteBuffer.wrap(array), start);
    }

    public static int alignedOffset_float(byte[] array, int start) {
        return alignedOffset_float(ByteBuffer.wrap(array), start);
    }

    public static int alignedOffset_double(byte[] array, int start) {
        return alignedOffset_double(ByteBuffer.wrap(array), start);
    }

    static {
        ByteOrder order = ByteOrder.LITTLE_ENDIAN;
        vh_probe_short = MethodHandles.byteBufferViewVarHandle(short[].class, order);
        vh_probe_int = MethodHandles.byteBufferViewVarHandle(int[].class, order);
        vh_probe_long = MethodHandles.byteBufferViewVarHandle(long[].class, order);
    }

    private static final VarHandle vh_probe_short;
    private static final VarHandle vh_probe_int;
    private static final VarHandle vh_probe_long;
}