/**
 * Copyright (c) André Bargull
 * Alle Rechte vorbehalten / All Rights Reserved.  Use is subject to license terms.
 *
 * <https://github.com/anba/es6draft>
 */
package com.github.anba.es6draft.runtime.objects.simd;

import java.nio.ByteBuffer;

/**
 * <h1>SIMD</h1>
 * <h2>SIMD objects</h2>
 * <ul>
 * <li>SIMD type descriptors
 * </ul>
 */
public enum SIMDType {
    /**
     * Float64x2
     */
    Float64x2(2, 8),

    /**
     * Float32x4
     */
    Float32x4(4, 4),

    /**
     * Int32x4
     */
    Int32x4(4, 4),

    /**
     * Int16x8
     */
    Int16x8(8, 2),

    /**
     * Int8x16
     */
    Int8x16(16, 1),

    /**
     * Uint32x4
     */
    Uint32x4(4, 4),

    /**
     * Uint16x8
     */
    Uint16x8(8, 2),

    /**
     * Uint8x16
     */
    Uint8x16(16, 1),

    /**
     * Bool64x2
     */
    Bool64x2(2, 0),

    /**
     * Bool32x4
     */
    Bool32x4(4, 0),

    /**
     * Bool16x8
     */
    Bool16x8(8, 0),

    /**
     * Bool8x16
     */
    Bool8x16(16, 0);

    private final int vectorLength;
    private final int elementSize;

    private SIMDType(int vectorLength, int elementSize) {
        this.vectorLength = vectorLength;
        this.elementSize = elementSize;
    }

    /**
     * [[VectorLength]]
     * 
     * @return the vector length
     */
    public int getVectorLength() {
        return vectorLength;
    }

    /**
     * [[ElementSize]]
     * 
     * @return the element size
     */
    public int getElementSize() {
        return elementSize;
    }

    /**
     * Returns the {@code typeof} description for this SIMD type.
     * 
     * @return the {@code typeof} description
     */
    public String typeof() {
        switch (this) {
        case Float64x2:
            return "float64x2";
        case Float32x4:
            return "float32x4";
        case Int32x4:
            return "int32x4";
        case Int16x8:
            return "int16x8";
        case Int8x16:
            return "int8x16";
        case Uint32x4:
            return "uint32x4";
        case Uint16x8:
            return "uint16x8";
        case Uint8x16:
            return "uint8x16";
        case Bool64x2:
            return "bool64x2";
        case Bool32x4:
            return "bool32x4";
        case Bool16x8:
            return "bool16x8";
        case Bool8x16:
            return "bool8x16";
        default:
            throw new AssertionError();
        }
    }

    /**
     * Returns the SIMD type for a {@code typeof} name.
     * 
     * @param typeName
     *            the SIMD typeof name
     * @return the SIMD type
     */
    public static SIMDType from(String typeName) {
        switch (typeName) {
        case "float64x2":
            return Float64x2;
        case "float32x4":
            return Float32x4;
        case "int32x4":
            return Int32x4;
        case "int16x8":
            return Int16x8;
        case "int8x16":
            return Int8x16;
        case "uint32x4":
            return Uint32x4;
        case "uint16x8":
            return Uint16x8;
        case "uint8x16":
            return Uint8x16;
        case "bool64x2":
            return Bool64x2;
        case "bool32x4":
            return Bool32x4;
        case "bool16x8":
            return Bool16x8;
        case "bool8x16":
            return Bool8x16;
        default:
            throw new IllegalArgumentException(typeName);
        }
    }

    /**
     * Returns {@code true} for floating point SIMD types.
     * 
     * @return {@code true} if the {@code this} object describes a floating point SIMD type.
     */
    public boolean isFloatingPoint() {
        switch (this) {
        case Float64x2:
        case Float32x4:
            return true;
        case Bool64x2:
        case Bool32x4:
        case Bool16x8:
        case Bool8x16:
        case Int32x4:
        case Int16x8:
        case Int8x16:
        case Uint32x4:
        case Uint16x8:
        case Uint8x16:
            return false;
        default:
            throw new AssertionError();
        }
    }

    /**
     * Returns {@code true} for integer SIMD types.
     * 
     * @return {@code true} if the {@code this} object describes an integer SIMD type.
     */
    public boolean isInteger() {
        switch (this) {
        case Int32x4:
        case Int16x8:
        case Int8x16:
        case Uint32x4:
        case Uint16x8:
        case Uint8x16:
            return true;
        case Float64x2:
        case Float32x4:
        case Bool64x2:
        case Bool32x4:
        case Bool16x8:
        case Bool8x16:
            return false;
        default:
            throw new AssertionError();
        }
    }

    /**
     * Returns {@code true} for boolean SIMD types.
     * 
     * @return {@code true} if the {@code this} object describes a boolean SIMD type.
     */
    public boolean isBoolean() {
        switch (this) {
        case Bool64x2:
        case Bool32x4:
        case Bool16x8:
        case Bool8x16:
            return true;
        case Float64x2:
        case Float32x4:
        case Int32x4:
        case Int16x8:
        case Int8x16:
        case Uint32x4:
        case Uint16x8:
        case Uint8x16:
            return false;
        default:
            throw new AssertionError();
        }
    }

    /**
     * SerializeFloat64( block, offset, value, isLittleEndian )
     * 
     * @param block
     *            the data block
     * @param offset
     *            the data block offset
     * @param value
     *            the number value
     */
    public static void SerializeFloat64(ByteBuffer block, int offset, double value) {
        /* steps 1-3 (not applicable) */
        /* step 4 */
        assert 0 <= offset && offset + 8 <= block.capacity();
        /* steps 5-6 */
        block.putDouble(offset, value);
    }

    /**
     * DeserializeFloat64( block, offset, isLittleEndian )
     * 
     * @param block
     *            the data block
     * @param offset
     *            the data block offset
     * @return the number value
     */
    public static double DeserializeFloat64(ByteBuffer block, int offset) {
        // FIXME: spec bug - isLittleEndian never used as argument to [[DeserializeElement]]
        /* steps 1-2 (not applicable) */
        /* step 3 */
        assert 0 <= offset && offset + 8 <= block.capacity();
        /* steps 4-6 */
        double value = block.getDouble(offset);
        /* steps 7-8 */
        // FIXME: spec issue? - canonicalization breaks moz-tests
        // return Double.isNaN(value) ? Double.NaN : value;
        return value;
    }

    /**
     * SerializeFloat32( block, offset, value, isLittleEndian )
     * 
     * @param block
     *            the data block
     * @param offset
     *            the data block offset
     * @param value
     *            the number value
     */
    public static void SerializeFloat32(ByteBuffer block, int offset, double value) {
        /* steps 1-3 (not applicable) */
        /* step 4 */
        assert 0 <= offset && offset + 4 <= block.capacity();
        /* steps 5-6 */
        block.putFloat(offset, (float) value);
    }

    /**
     * DeserializeFloat32( block, offset, isLittleEndian )
     * 
     * @param block
     *            the data block
     * @param offset
     *            the data block offset
     * @return the number value
     */
    public static double DeserializeFloat32(ByteBuffer block, int offset) {
        // FIXME: spec bug - isLittleEndian never used as argument to [[DeserializeElement]]
        /* steps 1-2 (not applicable) */
        /* step 3 */
        assert 0 <= offset && offset + 4 <= block.capacity();
        /* steps 4-6 */
        float value = block.getFloat(offset);
        /* steps 7-8 */
        // FIXME: spec issue? - canonicalization breaks moz-tests
        // return Float.isNaN(value) ? Float.NaN : value;
        return value;
    }

    /**
     * SerializeInt( descriptor )( block, offset, n, isLittleEndian ), descriptor = Int32x4
     * 
     * @param block
     *            the data block
     * @param offset
     *            the data block offset
     * @param value
     *            the number value
     */
    public static void SerializeInt32(ByteBuffer block, int offset, int value) {
        /* steps 1-4 (not applicable) */
        /* step 5 */
        assert 0 <= offset && offset + 4 <= block.capacity();
        /* steps 6-7 */
        block.putInt(offset, value);
    }

    /**
     * DeserializeInt( descriptor )( block, offset, isLittleEndian ), descriptor = Int32x4
     * 
     * @param block
     *            the data block
     * @param offset
     *            the data block offset
     * @return the number value
     */
    public static int DeserializeInt32(ByteBuffer block, int offset) {
        /* steps 1-2 (not applicable) */
        /* step 3 */
        assert 0 <= offset && offset + 4 <= block.capacity();
        /* steps 4-7 */
        return block.getInt(offset);
    }

    /**
     * SerializeInt( descriptor )( block, offset, n, isLittleEndian ), descriptor = Int16x8
     * 
     * @param block
     *            the data block
     * @param offset
     *            the data block offset
     * @param value
     *            the number value
     */
    public static void SerializeInt16(ByteBuffer block, int offset, int value) {
        /* steps 1-3 (not applicable) */
        /* step 4 */
        assert value == (short) value;
        /* step 5 */
        assert 0 <= offset && offset + 2 <= block.capacity();
        /* steps 6-7 */
        block.putShort(offset, (short) value);
    }

    /**
     * DeserializeInt( descriptor )( block, offset, isLittleEndian ), descriptor = Int16x8
     * 
     * @param block
     *            the data block
     * @param offset
     *            the data block offset
     * @return the number value
     */
    public static int DeserializeInt16(ByteBuffer block, int offset) {
        /* steps 1-2 (not applicable) */
        /* step 3 */
        assert 0 <= offset && offset + 2 <= block.capacity();
        /* steps 4-7 */
        return block.getShort(offset);
    }

    /**
     * SerializeInt( descriptor )( block, offset, n, isLittleEndian ), descriptor = Int8x16
     * 
     * @param block
     *            the data block
     * @param offset
     *            the data block offset
     * @param value
     *            the number value
     */
    public static void SerializeInt8(ByteBuffer block, int offset, int value) {
        /* steps 1-3 (not applicable) */
        /* step 4 */
        assert value == (byte) value;
        /* step 5 */
        assert 0 <= offset && offset + 1 <= block.capacity();
        /* steps 6-7 */
        block.put(offset, (byte) value);
    }

    /**
     * DeserializeInt( descriptor )( block, offset, isLittleEndian ), descriptor = Int8x16
     * 
     * @param block
     *            the data block
     * @param offset
     *            the data block offset
     * @return the number value
     */
    public static int DeserializeInt8(ByteBuffer block, int offset) {
        /* steps 1-2 (not applicable) */
        /* step 3 */
        assert 0 <= offset && offset + 1 <= block.capacity();
        /* steps 4-7 */
        return block.get(offset);
    }

    /**
     * SerializeInt( descriptor )( block, offset, n, isLittleEndian ), descriptor = Uint32x4
     * 
     * @param block
     *            the data block
     * @param offset
     *            the data block offset
     * @param value
     *            the number value
     */
    public static void SerializeUint32(ByteBuffer block, int offset, int value) {
        /* steps 1-4 (not applicable) */
        /* step 5 */
        assert 0 <= offset && offset + 4 <= block.capacity();
        /* steps 6-7 */
        block.putInt(offset, value);
    }

    /**
     * DeserializeInt( descriptor )( block, offset, isLittleEndian ), descriptor = Uint32x4
     * 
     * @param block
     *            the data block
     * @param offset
     *            the data block offset
     * @return the number value
     */
    public static int DeserializeUint32(ByteBuffer block, int offset) {
        /* steps 1-2 (not applicable) */
        /* step 3 */
        assert 0 <= offset && offset + 4 <= block.capacity();
        /* steps 4-7 */
        return block.getInt(offset); // Value returned as signed int32.
    }

    /**
     * SerializeInt( descriptor )( block, offset, n, isLittleEndian ), descriptor = Uint16x8
     * 
     * @param block
     *            the data block
     * @param offset
     *            the data block offset
     * @param value
     *            the number value
     */
    public static void SerializeUint16(ByteBuffer block, int offset, int value) {
        /* steps 1-3 (not applicable) */
        /* step 4 */
        assert value == (value & 0xffff);
        /* step 5 */
        assert 0 <= offset && offset + 2 <= block.capacity();
        /* steps 6-7 */
        block.putShort(offset, (short) value);
    }

    /**
     * DeserializeInt( descriptor )( block, offset, isLittleEndian ), descriptor = Uint16x8
     * 
     * @param block
     *            the data block
     * @param offset
     *            the data block offset
     * @return the number value
     */
    public static int DeserializeUint16(ByteBuffer block, int offset) {
        /* steps 1-2 (not applicable) */
        /* step 3 */
        assert 0 <= offset && offset + 2 <= block.capacity();
        /* steps 4-7 */
        return block.getShort(offset) & 0xffff;
    }

    /**
     * SerializeInt( descriptor )( block, offset, n, isLittleEndian ), descriptor = Uint8x16
     * 
     * @param block
     *            the data block
     * @param offset
     *            the data block offset
     * @param value
     *            the number value
     */
    public static void SerializeUint8(ByteBuffer block, int offset, int value) {
        /* steps 1-3 (not applicable) */
        /* step 4 */
        assert value == (value & 0xff);
        /* step 5 */
        assert 0 <= offset && offset + 1 <= block.capacity();
        /* steps 6-7 */
        block.put(offset, (byte) value);
    }

    /**
     * DeserializeInt( descriptor )( block, offset, isLittleEndian ), descriptor = Uint8x16
     * 
     * @param block
     *            the data block
     * @param offset
     *            the data block offset
     * @return the number value
     */
    public static int DeserializeUint8(ByteBuffer block, int offset) {
        /* steps 1-2 (not applicable) */
        /* step 3 */
        assert 0 <= offset && offset + 1 <= block.capacity();
        /* steps 4-7 */
        return block.get(offset) & 0xff;
    }
}
