package som.interpreter.nodes;

import som.compiler.ClassDefinition;
import som.interpreter.Invokable;
import som.interpreter.SArguments;
import som.interpreter.objectstorage.FieldAccessorNode.AbstractReadFieldNode;
import som.interpreter.objectstorage.FieldAccessorNode.AbstractWriteFieldNode;
import som.vm.constants.Nil;
import som.vmobjects.SClass;
import som.vmobjects.SObject;

import com.oracle.truffle.api.CompilerAsserts;
import com.oracle.truffle.api.CompilerDirectives;
import com.oracle.truffle.api.Truffle;
import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.nodes.DirectCallNode;
import com.oracle.truffle.api.utilities.ValueProfile;


public abstract class SlotAccessNode extends ExpressionNode {

  public SlotAccessNode() { super(null); }

  public abstract Object doRead(VirtualFrame frame, SObject rcvr);

  public static final class SlotReadNode extends SlotAccessNode {
    // TODO: may be, we can get rid of this completely?? could directly use AbstractReadFieldNode
    // TODO: we only got read support at the moment
    @Child protected AbstractReadFieldNode read;
    private final ValueProfile rcvrClass = ValueProfile.createClassProfile();

    public SlotReadNode(final AbstractReadFieldNode read) {
      this.read = read;
    }

    @Override
    public Object doRead(final VirtualFrame frame, final SObject rcvr) {
      return read.read(rcvr);
    }

    @Override
    public Object executeGeneric(final VirtualFrame frame) {
      return read.read((SObject) rcvrClass.profile(SArguments.rcvr(frame)));
    }
  }

  // TODO: try to remove, should only be used in getCallTarget version of mutator slots
  public static final class SlotWriteNode extends ExpressionNode {
    private final ValueProfile rcvrClass = ValueProfile.createClassProfile();
    @Child protected AbstractWriteFieldNode write;

    public SlotWriteNode(final AbstractWriteFieldNode write) {
      super(null);
      this.write = write;
    }

    @Override
    public Object executeGeneric(final VirtualFrame frame) {
      return write.write((SObject) rcvrClass.profile(SArguments.rcvr(frame)), SArguments.arg(frame, 1));
    }
  }

  public static final class ClassSlotAccessNode extends SlotAccessNode {
    private final ClassDefinition classDefinition;
    @Child protected DirectCallNode classObjectInstantiation;

    @Child protected AbstractReadFieldNode  read;
    @Child protected AbstractWriteFieldNode write;

    public ClassSlotAccessNode(final ClassDefinition classDefinition,
        final AbstractReadFieldNode read, final AbstractWriteFieldNode write) {
      this.read = read;
      this.write = write;
      this.classDefinition = classDefinition;
    }

    @Override
    public SClass doRead(final VirtualFrame frame, final SObject rcvr) {
      Object cacheValue = read.read(rcvr);

      // check whether cache is initialized with class object
      if (cacheValue == Nil.nilObject) {
        SClass classObject = instantiateClassObject(frame, rcvr);
        write.write(rcvr, classObject);
        return classObject;
      } else {
        assert cacheValue instanceof SClass;
        return (SClass) cacheValue;
      }
    }

    private void createClassInstantiationCallTarget() {
      CompilerAsserts.neverPartOfCompilation();
      Invokable invokable = classDefinition.getSuperclassResolutionInvokable();
      classObjectInstantiation = insert(Truffle.getRuntime().createDirectCallNode(
          invokable.createCallTarget()));
    }

    private SClass instantiateClassObject(final VirtualFrame frame,
        final SObject rcvr) {
      if (classObjectInstantiation == null) {
        CompilerDirectives.transferToInterpreterAndInvalidate();
        createClassInstantiationCallTarget();
      }

      SClass superClass = (SClass) classObjectInstantiation.call(frame,
          new Object[] {rcvr});
      SClass classObject = classDefinition.instantiateClass(rcvr, superClass);
      return classObject;
    }

    @Override
    public Object executeGeneric(final VirtualFrame frame) {
      return doRead(frame, (SObject) SArguments.rcvr(frame));
    }
  }
}

@@ -2784,24 +2784,200 @@
 ect rcvr) %7B%0A
+      // here we need to synchronize, because this is actually something that%0A      // can happen concurrently, and we only want a single instance of the%0A      // class object%0A
       Object
@@ -2978,24 +2978,25 @@
 Object cache
+d
 Value = read
@@ -3008,17 +3008,174 @@
 (rcvr);%0A
-%0A
+      if (cachedValue != Nil.nilObject) %7B%0A        return (SClass) cachedValue;%0A      %7D%0A%0A      synchronized (rcvr) %7B%0A        cachedValue = read.read(rcvr);%0A%0A  
       //
@@ -3230,24 +3230,26 @@
 bject%0A      
+  
 if (cacheVal
@@ -3241,24 +3241,25 @@
    if (cache
+d
 Value == Nil
@@ -3264,32 +3264,34 @@
 il.nilObject) %7B%0A
+  
         SClass c
@@ -3348,16 +3348,18 @@
         
+  
 write.wr
@@ -3386,24 +3386,26 @@
 t);%0A        
+  
 return class
@@ -3408,24 +3408,26 @@
 lassObject;%0A
+  
       %7D else
@@ -3437,16 +3437,18 @@
         
+  
 assert c
@@ -3447,24 +3447,25 @@
 assert cache
+d
 Value instan
@@ -3477,16 +3477,18 @@
 SClass;%0A
+  
         
@@ -3508,23 +3508,34 @@
 s) cache
+d
 Value;%0A
+        %7D%0A
       %7D%0A

Synchronize access to the cached class slot, if no value is yet available.