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

import com.oracle.truffle.api.CompilerDirectives;
import com.oracle.truffle.api.frame.VirtualFrame;

import de.hpi.swa.trufflesqueak.model.CompiledCodeObject;
import de.hpi.swa.trufflesqueak.nodes.AbstractNode;
import de.hpi.swa.trufflesqueak.nodes.context.frame.FrameSlotReadNode;
import de.hpi.swa.trufflesqueak.util.FrameAccess;
import de.hpi.swa.trufflesqueak.util.NotProvided;

public final class ArgumentNodes {
    public abstract static class AbstractArgumentNode extends AbstractNode {
        public abstract Object execute(VirtualFrame frame);

        public static final AbstractArgumentNode create(final int argumentIndex, final int numArguments, final boolean useStack) {
            if (argumentIndex <= numArguments) {
                if (useStack) {
                    return new ArgumentOnStackNode(argumentIndex);
                } else {
                    return new ArgumentNode(argumentIndex);
                }
            } else {
                return new ArgumentNotProvidedNode();
            }
        }
    }

    public static final class ArgumentOnStackNode extends AbstractArgumentNode {
        private final int argumentIndex;

        @Child private FrameSlotReadNode readNode;

        public ArgumentOnStackNode(final int argumentIndex) {
            this.argumentIndex = argumentIndex; // argumentIndex == 0 returns receiver
        }

        @Override
        public Object execute(final VirtualFrame frame) {
            if (readNode == null) {
                CompilerDirectives.transferToInterpreterAndInvalidate();
                final CompiledCodeObject blockOrMethod = FrameAccess.getBlockOrMethod(frame);
                final int stackPointer = FrameAccess.getStackPointer(frame, blockOrMethod);
                readNode = insert(FrameSlotReadNode.create(blockOrMethod.getStackSlot(stackPointer + argumentIndex)));
            }
            return readNode.executeRead(frame);
        }
    }

    public static final class ArgumentNode extends AbstractArgumentNode {
        private final int argumentIndex;

        public ArgumentNode(final int argumentIndex) {
            this.argumentIndex = argumentIndex; // argumentIndex == 0 returns receiver
        }

        @Override
        public Object execute(final VirtualFrame frame) {
            return FrameAccess.getArgument(frame, argumentIndex);
        }
    }

    private static final class ArgumentNotProvidedNode extends AbstractArgumentNode {
        @Override
        public NotProvided execute(final VirtualFrame frame) {
            return NotProvided.SINGLETON;
        }
    }
}