/**
   * 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);
    }
  }