/**
   * 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);
      }
    }
  }
  /**
   * Handles the beginning of evaluation of an {@link Expression}.
   *
   * @param event VMObservableEvent event for expression
   */
  private void startExpressionEvaluation(VMObservableEvent event) {
    Expression expression = (Expression) event.getObject();
    ComponentThread thread = event.getThread();
    double startTime = event.getTime();

    AbstractEventNode<?> parentNode = peekEventNode(thread);

    ExpressionEvaluationEventNode eventNode =
        (ExpressionEvaluationEventNode)
            EventNodeFactory.createEventNode(expression, thread, startTime, parentNode);

    // Add expression evaluation to ExpressionStatementEventNode
    if (parentNode instanceof ExpressionStatementEventNode) {
      ((ExpressionStatementEventNode) parentNode).addExpressionEvaluationNode(eventNode);
    }
    // Handle expression evaluation for ConditionalEventNode
    else if (parentNode instanceof ConditionalStatementEventNode) {
      ((ConditionalStatementEventNode) parentNode).addConditionalEvaluation(eventNode);
    }
    // Handle expression evaluation for CountLoopEventNode
    else if (parentNode instanceof CountLoopEventNode) {
      ((CountLoopEventNode) parentNode).setCountExpressionNode(eventNode);
    }
    // Handle expression evaluation for EachInArrayTogetherEventNode
    else if (parentNode instanceof EachInArrayTogetherEventNode) {
      ((EachInArrayTogetherEventNode) parentNode).setArrayExpressionNode(eventNode);
    }
    // Handle expression evaluation for ForEachInArrayLoopEventNode
    else if (parentNode instanceof ForEachInArrayLoopEventNode) {
      ((ForEachInArrayLoopEventNode) parentNode).setArrayExpressionNode(eventNode);
    }
    // Handle expression evaluation for WhileLoopEventNode
    else if (parentNode instanceof WhileLoopEventNode) {
      ((WhileLoopEventNode) parentNode).addConditionalEvaluation(eventNode);
    }
    // Handle expression evaluation for LocalDeclarationStatementEventNode
    else if (parentNode instanceof LocalDeclarationStatementEventNode) {
      ((LocalDeclarationStatementEventNode) parentNode).setInitializerExpression(eventNode);
    }
    // Handle expression evaluation for ReturnStatementEventNode
    else if (parentNode instanceof ReturnStatementEventNode) {
      ((ReturnStatementEventNode) parentNode).setExpressionNode(eventNode);
    }
    // Handle expression evaluation for ExpressionEvaluationEventNode
    else if (parentNode instanceof ExpressionEvaluationEventNode) {
      // pass - handled on endExpressionEvaluation
    } else {
      throw new ExecutionObserverException("Parent node invalid:" + parentNode.toString(), event);
    }

    pushEventNode(eventNode, false);
  }
  /**
   * Handles ending execution of Container statement ( {@link AbstractStatementWithBody} or {@link
   * BlockStatement}).
   *
   * @param event {@link VMObservableEvent} event for container type statement
   */
  private void endContainer(VMObservableEvent event) {
    Statement statement = (Statement) event.getObject();
    ComponentThread thread = event.getThread();
    double endTime = event.getTime();

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

    if ((eventNode != null)
        && (eventNode.getAstNode() == statement)
        && (eventNode.getThread() == thread)) {
      eventNode.setEndTime(endTime);
    } else {
      throw new ExecutionObserverException("EventNode invalid: " + eventNode.toString(), event);
    }
  }
  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);
    }
  }
  /**
   * Handles beginning execution of a {@link Statement}.
   *
   * @param event {@link VMObservableEvent} event for statement
   */
  private void startStatement(VMObservableEvent event) {
    Statement statement = (Statement) event.getObject();
    ComponentThread thread = event.getThread();
    double startTime = event.getTime();

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

    // A Statement should always have a parent node (which can only be a ContainerEventNode)
    if (parentNode instanceof ContainerEventNode) {
      ContainerEventNode<?> containerNode = (ContainerEventNode<?>) parentNode;
      AbstractEventNode<?> eventNode =
          EventNodeFactory.createEventNode(statement, thread, startTime, containerNode);

      pushEventNode(eventNode, false);
    } else {
      throw new ExecutionObserverException("Parent node invalid: " + parentNode.toString(), event);
    }
  }
  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 beginning execution of container statement ( {@link AbstractStatementWithBody} or
   * {@link BlockStatement}).
   *
   * @param event {@link VMObservableEvent} event for container type statement
   */
  private void startContainer(VMObservableEvent event) {
    Statement statement = (Statement) event.getObject();
    ComponentThread thread = event.getThread();
    double startTime = event.getTime();

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

    // Root case
    if (parentNode == null) {
      AbstractEventNode<?> eventNode =
          EventNodeFactory.createEventNode(statement, thread, startTime, null);
      this.rootEventNode = eventNode;
      pushEventNode(eventNode, true);
    } else if (parentNode instanceof ConditionalStatementEventNode) {
      AbstractEventNode<?> eventNode =
          EventNodeFactory.createEventNode(statement, thread, startTime, parentNode);
      pushEventNode(eventNode, true);
    } else if (parentNode instanceof ContainerEventNode) {
      AbstractEventNode<?> eventNode =
          EventNodeFactory.createEventNode(statement, thread, startTime, parentNode);
      pushEventNode(eventNode, true);
    } else if (parentNode instanceof ExpressionStatementEventNode) {
      AbstractEventNode<?> eventNode =
          EventNodeFactory.createEventNode(statement, thread, startTime, parentNode);
      ((ExpressionStatementEventNode) parentNode)
          .addUserMethodEventNode((ContainerEventNode<?>) eventNode);
      pushEventNode(eventNode, true);
    } else if (parentNode instanceof ExpressionEvaluationEventNode) {
      AbstractEventNode<?> eventNode =
          EventNodeFactory.createEventNode(statement, thread, startTime, parentNode);
      pushEventNode(eventNode, true);
    } else if (parentNode instanceof LambdaEventNode) {
      AbstractEventNode<?> eventNode =
          EventNodeFactory.createEventNode(statement, thread, startTime, parentNode);
      ((LambdaEventNode) parentNode).addBodyEventNode((ContainerEventNode<?>) eventNode);
      pushEventNode(eventNode, true);
    } else {
      throw new ExecutionObserverException("Parent node invalid: " + parentNode.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;
        }
      }
    }
  }