/*
 * Copyright (c) 2017-2020 Software Architecture Group, Hasso Plattner Institute
 *
 * Licensed under the MIT License.
 */
package de.hpi.swa.trufflesqueak.nodes.accessing;

import com.oracle.truffle.api.TruffleLanguage.ContextReference;
import com.oracle.truffle.api.dsl.Cached;
import com.oracle.truffle.api.dsl.CachedContext;
import com.oracle.truffle.api.dsl.Fallback;
import com.oracle.truffle.api.dsl.GenerateUncached;
import com.oracle.truffle.api.dsl.ImportStatic;
import com.oracle.truffle.api.dsl.Specialization;
import com.oracle.truffle.api.profiles.ConditionProfile;

import de.hpi.swa.trufflesqueak.SqueakLanguage;
import de.hpi.swa.trufflesqueak.exceptions.PrimitiveExceptions.PrimitiveFailed;
import de.hpi.swa.trufflesqueak.image.SqueakImageContext;
import de.hpi.swa.trufflesqueak.model.CharacterObject;
import de.hpi.swa.trufflesqueak.model.LargeIntegerObject;
import de.hpi.swa.trufflesqueak.model.NativeObject;
import de.hpi.swa.trufflesqueak.nodes.AbstractNode;
import de.hpi.swa.trufflesqueak.nodes.accessing.NativeObjectNodesFactory.NativeObjectSizeNodeGen;
import de.hpi.swa.trufflesqueak.util.UnsafeUtils;

public final class NativeObjectNodes {
    @GenerateUncached
    public abstract static class NativeObjectReadNode extends AbstractNode {

        public abstract Object execute(NativeObject obj, long index);

        @Specialization(guards = "obj.isByteType()")
        protected static final long doNativeBytes(final NativeObject obj, final long index) {
            return Byte.toUnsignedLong(obj.getByte(index));
        }

        @Specialization(guards = "obj.isShortType()")
        protected static final long doNativeShorts(final NativeObject obj, final long index) {
            return Short.toUnsignedLong(obj.getShort(index));
        }

        @Specialization(guards = "obj.isIntType()")
        protected static final long doNativeInts(final NativeObject obj, final long index) {
            return Integer.toUnsignedLong(obj.getInt(index));
        }

        @Specialization(guards = "obj.isLongType()")
        protected static final Object doNativeLongs(final NativeObject obj, final long index,
                        @Cached("createBinaryProfile()") final ConditionProfile positiveValueProfile,
                        @CachedContext(SqueakLanguage.class) final ContextReference<SqueakImageContext> ref) {
            final long value = obj.getLong(index);
            if (positiveValueProfile.profile(value >= 0)) {
                return value;
            } else {
                return LargeIntegerObject.toUnsigned(ref.get(), value);
            }
        }
    }

    @GenerateUncached
    @ImportStatic(NativeObject.class)
    public abstract static class NativeObjectWriteNode extends AbstractNode {

        public abstract void execute(NativeObject obj, long index, Object value);

        @Specialization(guards = {"obj.isByteType()", "value >= 0", "value <= BYTE_MAX"})
        protected static final void doNativeBytes(final NativeObject obj, final long index, final long value) {
            obj.setByte(index, (byte) value);
        }

        @Specialization(guards = {"obj.isShortType()", "value >= 0", "value <= SHORT_MAX"})
        protected static final void doNativeShorts(final NativeObject obj, final long index, final long value) {
            obj.setShort(index, (short) value);
        }

        @Specialization(guards = {"obj.isIntType()", "value >= 0", "value <= INTEGER_MAX"})
        protected static final void doNativeInts(final NativeObject obj, final long index, final long value) {
            obj.setInt(index, (int) value);
        }

        @Specialization(guards = {"obj.isLongType()", "value >= 0"})
        protected static final void doNativeLongs(final NativeObject obj, final long index, final long value) {
            obj.setLong(index, value);
        }

        protected static final boolean inByteRange(final char value) {
            return value <= NativeObject.BYTE_MAX;
        }

        @Specialization(guards = {"obj.isByteType()", "inByteRange(value)"})
        protected static final void doNativeBytesChar(final NativeObject obj, final long index, final char value) {
            doNativeBytes(obj, index, value);
        }

        @Specialization(guards = "obj.isShortType()") // char values fit into short
        protected static final void doNativeShortsChar(final NativeObject obj, final long index, final char value) {
            doNativeShorts(obj, index, value);
        }

        @Specialization(guards = "obj.isIntType()")
        protected static final void doNativeIntsChar(final NativeObject obj, final long index, final char value) {
            doNativeInts(obj, index, value);
        }

        @Specialization(guards = "obj.isLongType()")
        protected static final void doNativeLongsChar(final NativeObject obj, final long index, final char value) {
            doNativeLongs(obj, index, value);
        }

        /*
         * CharacterObject hold values > Character.MAX_VALUE, which cannot fit into byte/short type.
         */

        @Specialization(guards = "obj.isIntType()")
        protected static final void doNativeIntsChar(final NativeObject obj, final long index, final CharacterObject value) {
            doNativeInts(obj, index, value.getValue());
        }

        @Specialization(guards = "obj.isLongType()")
        protected static final void doNativeLongsChar(final NativeObject obj, final long index, final CharacterObject value) {
            doNativeLongs(obj, index, value.getValue());
        }

        @Specialization(guards = {"obj.isByteType()", "value.inRange(0, BYTE_MAX)"})
        protected static final void doNativeBytesLargeInteger(final NativeObject obj, final long index, final LargeIntegerObject value) {
            doNativeBytes(obj, index, value.longValue());
        }

        @Specialization(guards = {"obj.isShortType()", "value.inRange(0, SHORT_MAX)"})
        protected static final void doNativeShortsLargeInteger(final NativeObject obj, final long index, final LargeIntegerObject value) {
            doNativeShorts(obj, index, value.longValue());
        }

        @Specialization(guards = {"obj.isIntType()", "value.inRange(0, INTEGER_MAX)"})
        protected static final void doNativeIntsLargeInteger(final NativeObject obj, final long index, final LargeIntegerObject value) {
            doNativeInts(obj, index, value.longValue());
        }

        @Specialization(guards = {"obj.isLongType()", "value.isZeroOrPositive()", "value.fitsIntoLong()"})
        protected static final void doNativeLongsLargeInteger(final NativeObject obj, final long index, final LargeIntegerObject value) {
            doNativeLongs(obj, index, value.longValue());
        }

        @Specialization(guards = {"obj.isLongType()", "value.isZeroOrPositive()", "!value.fitsIntoLong()", "value.lessThanOneShiftedBy64()"})
        protected static final void doNativeLongsLargeIntegerSigned(final NativeObject obj, final long index, final LargeIntegerObject value) {
            doNativeLongs(obj, index, value.toSignedLong());
        }

        @SuppressWarnings("unused")
        @Fallback
        protected static final void doFail(final NativeObject obj, final long index, final Object value) {
            /*
             * Throw primitive failed (instead of UnsupportedSpecializationException) here for
             * PrimBasicAtPutNode.
             */
            throw PrimitiveFailed.GENERIC_ERROR;
        }
    }

    @GenerateUncached
    public abstract static class NativeObjectSizeNode extends AbstractNode {

        public static NativeObjectSizeNode getUncached() {
            return NativeObjectSizeNodeGen.getUncached();
        }

        public abstract int execute(NativeObject obj);

        @Specialization(guards = "obj.isByteType()")
        protected static final int doNativeBytes(final NativeObject obj) {
            return obj.getByteLength();
        }

        @Specialization(guards = "obj.isShortType()")
        protected static final int doNativeShorts(final NativeObject obj) {
            return obj.getShortLength();
        }

        @Specialization(guards = "obj.isIntType()")
        protected static final int doNativeInts(final NativeObject obj) {
            return obj.getIntLength();
        }

        @Specialization(guards = "obj.isLongType()")
        protected static final int doNativeLongs(final NativeObject obj) {
            return obj.getLongLength();
        }
    }

    public abstract static class NativeObjectByteSizeNode extends AbstractNode {

        public abstract int execute(NativeObject obj);

        @Specialization(guards = "obj.isByteType()")
        protected static final int doNativeBytes(final NativeObject obj) {
            return obj.getByteLength();
        }

        @Specialization(guards = "obj.isShortType()")
        protected static final int doNativeShorts(final NativeObject obj) {
            return obj.getShortLength() * Short.BYTES;
        }

        @Specialization(guards = "obj.isIntType()")
        protected static final int doNativeInts(final NativeObject obj) {
            return obj.getIntLength() * Integer.BYTES;
        }

        @Specialization(guards = "obj.isLongType()")
        protected static final int doNativeLongs(final NativeObject obj) {
            return obj.getLongLength() * Long.BYTES;
        }
    }

    public abstract static class NativeGetBytesNode extends AbstractNode {

        public abstract byte[] execute(NativeObject obj);

        @Specialization(guards = "obj.isByteType()")
        protected static final byte[] doNativeBytes(final NativeObject obj) {
            return obj.getByteStorage();
        }

        @Specialization(guards = "obj.isShortType()")
        protected static final byte[] doNativeShorts(final NativeObject obj) {
            return UnsafeUtils.toBytes(obj.getShortStorage());
        }

        @Specialization(guards = "obj.isIntType()")
        protected static final byte[] doNativeInts(final NativeObject obj) {
            return UnsafeUtils.toBytes(obj.getIntStorage());
        }

        @Specialization(guards = "obj.isLongType()")
        protected static final byte[] doNativeLongs(final NativeObject obj) {
            return UnsafeUtils.toBytes(obj.getLongStorage());
        }
    }

    public abstract static class NativeGetShortsNode extends AbstractNode {

        public abstract short[] execute(NativeObject obj);

        @Specialization(guards = "obj.isByteType()")
        protected static final short[] doNativeBytes(final NativeObject obj) {
            return UnsafeUtils.toShorts(obj.getByteStorage());
        }

        @Specialization(guards = "obj.isShortType()")
        protected static final short[] doNativeShorts(final NativeObject obj) {
            return obj.getShortStorage();
        }

        @Specialization(guards = "obj.isIntType()")
        protected static final short[] doNativeInts(final NativeObject obj) {
            return UnsafeUtils.toShorts(obj.getIntStorage());
        }

        @Specialization(guards = "obj.isLongType()")
        protected static final short[] doNativeLongs(final NativeObject obj) {
            return UnsafeUtils.toShorts(obj.getLongStorage());
        }
    }

    public abstract static class NativeGetIntsNode extends AbstractNode {

        public abstract int[] execute(NativeObject obj);

        @Specialization(guards = "obj.isByteType()")
        protected static final int[] doNativeBytes(final NativeObject obj) {
            return UnsafeUtils.toInts(obj.getByteStorage());
        }

        @Specialization(guards = "obj.isShortType()")
        protected static final int[] doNativeShorts(final NativeObject obj) {
            return UnsafeUtils.toInts(obj.getShortStorage());
        }

        @Specialization(guards = "obj.isIntType()")
        protected static final int[] doNativeInts(final NativeObject obj) {
            return obj.getIntStorage();
        }

        @Specialization(guards = "obj.isLongType()")
        protected static final int[] doNativeLongs(final NativeObject obj) {
            return UnsafeUtils.toInts(obj.getLongStorage());
        }
    }

    public abstract static class NativeGetLongsNode extends AbstractNode {

        public abstract long[] execute(NativeObject obj);

        @Specialization(guards = "obj.isByteType()")
        protected static final long[] doNativeBytes(final NativeObject obj) {
            return UnsafeUtils.toLongs(obj.getByteStorage());
        }

        @Specialization(guards = "obj.isShortType()")
        protected static final long[] doNativeShorts(final NativeObject obj) {
            return UnsafeUtils.toLongs(obj.getShortStorage());
        }

        @Specialization(guards = "obj.isIntType()")
        protected static final long[] doNativeInts(final NativeObject obj) {
            return UnsafeUtils.toLongs(obj.getIntStorage());
        }

        @Specialization(guards = "obj.isLongType()")
        protected static final long[] doNativeLongs(final NativeObject obj) {
            return obj.getLongStorage();
        }
    }

    public abstract static class NativeObjectShallowCopyNode extends AbstractNode {

        public abstract NativeObject execute(NativeObject obj);

        @Specialization(guards = "obj.isByteType()")
        protected static final NativeObject doNativeBytes(final NativeObject obj) {
            return obj.shallowCopy(obj.getByteStorage().clone());
        }

        @Specialization(guards = "obj.isShortType()")
        protected static final NativeObject doNativeShorts(final NativeObject obj) {
            return obj.shallowCopy(obj.getShortStorage().clone());
        }

        @Specialization(guards = "obj.isIntType()")
        protected static final NativeObject doNativeInts(final NativeObject obj) {
            return obj.shallowCopy(obj.getIntStorage().clone());
        }

        @Specialization(guards = "obj.isLongType()")
        protected static final NativeObject doNativeLongs(final NativeObject obj) {
            return obj.shallowCopy(obj.getLongStorage().clone());
        }
    }
}