package org.graalphp.nodes.localvar;

import com.oracle.truffle.api.dsl.NodeChild;
import com.oracle.truffle.api.dsl.NodeField;
import com.oracle.truffle.api.dsl.Specialization;
import com.oracle.truffle.api.frame.FrameDescriptor;
import com.oracle.truffle.api.frame.FrameSlot;
import com.oracle.truffle.api.frame.FrameSlotKind;
import com.oracle.truffle.api.frame.VirtualFrame;
import org.graalphp.nodes.PhpExprNode;
import org.graalphp.util.Logger;
import org.graalphp.util.PhpLogger;

/**
 * Truffle idiomatic way to write a variable onto the stack (Truffle frame)
 * TODO: we have to use a materialized frame when supporting global variables
 *
 * @author abertschi
 */
@NodeChild(value = "srcNode", type = PhpExprNode.class)
@NodeField(name = "slot", type = FrameSlot.class)
public abstract class WriteLocalVarNode extends PhpExprNode {

    private static final Logger LOG = PhpLogger.getLogger(WriteLocalVarNode.class.getSimpleName());

    protected abstract PhpExprNode getSrcNode();

    // storage slot in frame
    protected abstract FrameSlot getSlot();

    @Specialization(guards = "isSlotBoolOrIllegal(frame)")
    protected boolean writeBool(VirtualFrame frame, boolean val) {
        frame.getFrameDescriptor().setFrameSlotKind(getSlot(), FrameSlotKind.Boolean);
        frame.setBoolean(getSlot(), val);
        return val;
    }

    @Specialization(guards = "isSlotLongOrIllegal(frame)")
    protected long writeLong(VirtualFrame frame, long value) {
        frame.getFrameDescriptor().setFrameSlotKind(getSlot(), FrameSlotKind.Long);
        frame.setLong(getSlot(), value);
        return value;
    }

    @Specialization(guards = "isSlotDoubleOrIllegal(frame)")
    protected double writeDouble(VirtualFrame frame, double val) {
        frame.getFrameDescriptor().setFrameSlotKind(getSlot(), FrameSlotKind.Double);
        frame.setDouble(getSlot(), val);
        return val;
    }

    // XXX: evaluate if we should keep the specializations, or use replace
    @Specialization(replaces = {"writeLong", "writeBool", "writeDouble"})
    protected Object write(VirtualFrame frame, Object value) {
        frame.getFrameDescriptor().setFrameSlotKind(getSlot(), FrameSlotKind.Object);
        frame.setObject(getSlot(), value);
        return value;
    }

    protected final boolean isSlotLongOrIllegal(VirtualFrame frame) {
        final FrameDescriptor desc = frame.getFrameDescriptor();
        final FrameSlotKind kind = desc.getFrameSlotKind(getSlot());
        return kind == FrameSlotKind.Long || kind == FrameSlotKind.Illegal;
    }

    protected final boolean isSlotBoolOrIllegal(VirtualFrame frame) {
        final FrameDescriptor desc = frame.getFrameDescriptor();
        final FrameSlotKind kind = desc.getFrameSlotKind(getSlot());
        return kind == FrameSlotKind.Boolean || kind == FrameSlotKind.Illegal;
    }

    protected final boolean isSlotDoubleOrIllegal(VirtualFrame frame) {
        final FrameDescriptor desc = frame.getFrameDescriptor();
        final FrameSlotKind kind = desc.getFrameSlotKind(getSlot());
        return kind == FrameSlotKind.Double || kind == FrameSlotKind.Illegal;
    }

    @Override
    public String toString() {
        return "WriteLocalVarNode{slot: " + getSlot() + "; value: " + getSrcNode() + "}";
    }
}
