/**
   * Handles beginning execution of {@link MethodInvocation}.
   *
   * @param event {@link VMObservableEvent} event for invocation
   */
  private void startMethodInvocation(VMObservableEvent event) {
    MethodInvocation invocation = (MethodInvocation) event.getObject();
    ComponentThread thread = event.getThread();
    Object instance = event.getProperties()[1];

    UserField field = getUserFieldForInstance(instance);

    AbstractEventNode<?> parentNode = peekEventNode(getThreadForNode(invocation, thread));

    // Add instance and referencing field to parent node
    if (parentNode instanceof ExpressionStatementEventNode) {
      ExpressionStatementEventNode statementNode = (ExpressionStatementEventNode) parentNode;
      if ((instance != null) & (field != null)) {
        statementNode.setCallerInstance(instance);
        statementNode.setCallerField(field);
      }

      if ((instance != null) & (field != null)) {
        notifyListenersOfStart(statementNode); // Now that caller has been set, notify listeners
      }

      if (invocation.method.getValue() instanceof UserMethod) {
        UserMethod userMethod = (UserMethod) invocation.method.getValue();
        if (!userMethod.isFunction()) {
          statementNode.setUserMethod(userMethod);
        }
      }
    } else if (parentNode instanceof ExpressionEvaluationEventNode) {
      if (field != null) {
        ((ExpressionEvaluationEventNode) parentNode).setValueField(field);
      }
    }
  }
  private void startLambdaInvocation(VMObservableEvent event) {
    org.lgna.project.ast.UserLambda lambda = (UserLambda) event.getObject();
    ComponentThread thread = event.getThread();
    double startTime = event.getTime();
    org.lgna.project.ast.AbstractMethod method = (AbstractMethod) event.getProperties()[0];

    // We ignore scene activation
    if (!method.getName().contentEquals("sceneActivated")) {
      AbstractEventNode<?> eventNode =
          EventNodeFactory.createEventNode(lambda, thread, startTime, null);
      ((LambdaEventNode) eventNode).setInvokingEventMethod(method);

      pushEventNode(eventNode, false);
    }
  }
  private void endLambdaInvocation(VMObservableEvent event) {
    org.lgna.project.ast.UserLambda lambda = (UserLambda) event.getObject();
    org.lgna.project.ast.AbstractMethod method = (AbstractMethod) event.getProperties()[0];
    ComponentThread thread = event.getThread();
    double endTime = event.getTime();

    if (!method.getName().contentEquals("sceneActivated")) {
      AbstractEventNode<?> eventNode = popEventNode(thread, false);

      lambdaEventNodes.add((LambdaEventNode) eventNode);

      if ((eventNode != null)
          && (eventNode.getAstNode() == lambda)
          && (eventNode.getThread() == thread)) {
        eventNode.setEndTime(endTime);
      } else {
        throw new ExecutionObserverException("EventNode invalid: " + eventNode.toString(), event);
      }
    }
  }
  /**
   * Handles the ending of evaluation of an {@link Expression}.
   *
   * @param event VMObservableEvent event for expression
   */
  private void endExpressionEvaluation(VMObservableEvent event) {
    Expression expression = (Expression) event.getObject();
    ComponentThread thread = event.getThread();
    double endTime = event.getTime();
    Object value = event.getProperties()[0];

    AbstractEventNode<?> eventNode = popEventNode(thread, false);

    if ((eventNode != null)
        && (eventNode.getAstNode() == expression)
        && (eventNode.getThread() == thread)) {
      eventNode.setEndTime(endTime);
      ((ExpressionEvaluationEventNode) eventNode).setValue(value);

      UserField field = this.getUserFieldForInstance(value);
      if (field != null) {
        ((ExpressionEvaluationEventNode) eventNode).setValueField(field);
      }
    } else {
      throw new ExecutionObserverException("EventNode invalid: " + eventNode.toString(), event);
    }
  }
  /**
   * Called when a new {@link VMObservableEvent} is passed. Executes the correct method based on the
   * {@link VMMessage} for the event.
   *
   * @param event {@link VMObservableEvent} passed from {@link VMObserverManager}
   */
  @Override
  public synchronized void update(VMObservableEvent event) {
    VMMessage eventMsg = event.getVMMessage();

    if (eventMsg == VMMessage.START_RECORDING) {
      this.isRecording = true;
    } else if (eventMsg == VMMessage.STOP_RECORDING) {
      this.isRecording = false;
    } else if (eventMsg == VMMessage.CREATE_INSTANCE) {
      if ((event.getProperties().length == 1) && (event.getProperties()[0] instanceof UserField)) {
        UserField field = (UserField) event.getProperties()[0];
        Object instance = event.getObject();

        if (instance instanceof UserInstance) {
          if (((UserInstance) instance).getType().isAssignableTo(SScene.class)) {
            this.sceneInstance = (UserInstance) instance;
          }

          instance = ((UserInstance) instance).getJavaInstance();
        }

        this.instanceMap.put(instance, field);
      }
    }

    // Lambda invocations come from eventListeners and are
    // recorded outside the default record loop
    if (eventMsg == VMMessage.START_LAMBDA_INVOKE) {
      if (event.getObject() instanceof UserLambda) {
        startLambdaInvocation(event);
      } else {
        throw new ExecutionObserverException(event);
      }
    } else if (eventMsg == VMMessage.END_LAMBDA_INVOKE) {
      if (event.getObject() instanceof UserLambda) {
        endLambdaInvocation(event);
      } else {
        throw new ExecutionObserverException(event);
      }
    }

    // Should we record this message?
    boolean shouldRecord = isRecording();
    if (!shouldRecord) {
      if (event.getThread() != null) {
        shouldRecord = event.getThread().getDescription().contentEquals("eventThread");
      }
    }

    synchronized (this) {
      if (shouldRecord) {
        switch (eventMsg) {
          case RESET:
            reset();
            break;
          case START_INVOKE_ENTRY_POINT:
            this.hasInvokedEntryPoint = true;
            break;
          case START_EXP_EVAL:
            if (this.hasInvokedEntryPoint) {
              if (event.getObject() instanceof Expression) {

                // Non-function method invocations are handled separately
                if (event.getObject() instanceof MethodInvocation) {
                  MethodInvocation methodInvocation = (MethodInvocation) event.getObject();
                  if (methodInvocation.method.getValue().isFunction()) {
                    startExpressionEvaluation(event);
                  }
                } else {
                  startExpressionEvaluation(event);
                }
              } else {
                throw new ExecutionObserverException(event);
              }
            }
            break;
          case END_EXP_EVAL:
            if (this.hasInvokedEntryPoint) {
              if (event.getObject() instanceof Expression) {

                // Non-function method invocations are handled separately
                if (event.getObject() instanceof MethodInvocation) {
                  MethodInvocation methodInvocation = (MethodInvocation) event.getObject();
                  if (methodInvocation.method.getValue().isFunction()) {
                    endExpressionEvaluation(event);
                  }
                } else {
                  endExpressionEvaluation(event);
                }
              } else {
                throw new ExecutionObserverException(event);
              }
            }
            break;
          case START_METHOD_INVOKE:
            if (this.hasInvokedEntryPoint) {
              if (event.getObject() instanceof MethodInvocation) {
                startMethodInvocation(event);
              } else {
                throw new ExecutionObserverException(event);
              }
            }
            break;
          case START_STMT_EXEC:
            if (this.hasInvokedEntryPoint) {
              if (event.getObject() instanceof BlockStatement) {
                // pass
                //						} else if( event.getObject() instanceof Comment ) {
                //							//pass
                //						} else {
              } else {
                startStatement(event);
              }
            }
            break;
          case END_STMT_EXEC:
            if (this.hasInvokedEntryPoint) {
              if (event.getObject() instanceof BlockStatement) {
                //							//pass
                //						} else if( event.getObject() instanceof Comment ) {
                //							//pass
                //						} else {
              } else {
                endStatement(event);
              }
            }
            break;
          case START_CONTAINER:
            if (this.hasInvokedEntryPoint) {
              if ((event.getObject() instanceof AbstractStatementWithBody)
                  || (event.getObject() instanceof BlockStatement)) {
                startContainer(event);
              } else {
                throw new ExecutionObserverException(event);
              }
            }
            break;
          case END_CONTAINER:
            if (this.hasInvokedEntryPoint) {
              if ((event.getObject() instanceof AbstractStatementWithBody)
                  || (event.getObject() instanceof BlockStatement)) {
                endContainer(event);
              } else {
                throw new ExecutionObserverException(event);
              }
            }
            break;
        }
      }
    }
  }