package jetbrains.mps.baseLanguage.unitTest.execution.server;

/*Generated by MPS */

import org.junit.platform.launcher.TestExecutionListener;
import org.junit.platform.launcher.TestIdentifier;
import jetbrains.mps.baseLanguage.unitTest.execution.TestEventMessage;
import java.util.Optional;
import org.junit.platform.engine.TestExecutionResult;
import org.jetbrains.annotations.NotNull;
import org.junit.platform.engine.support.descriptor.ClassSource;
import org.junit.platform.engine.support.descriptor.MethodSource;
import org.junit.platform.engine.UniqueId;
import java.util.List;

/**
 * Adaptation of {@code DefaultRunListener} class to JUnit5 API. 
 */
public class DefaultTestExecutionListener implements TestExecutionListener {
  private final CommandOutputStream myOutput;
  private int myFailuresCount = 0;

  public DefaultTestExecutionListener(CommandOutputStream out) {
    this.myOutput = out;
  }

  public int getFailuresCount() {
    return myFailuresCount;
  }

  @Override
  public void executionStarted(TestIdentifier testIdentifier) {
    if (testIdentifier.isTest()) {
      flush();
      printSyncToken(TestEventMessage.START_TEST, Optional.of(testIdentifier));

    } else if (!(shouldSkipContainer(testIdentifier))) {
      flush();
      printSyncToken(TestEventMessage.START_TESTRUN, Optional.of(testIdentifier));
    }
  }

  @Override
  public void executionSkipped(TestIdentifier testIdentifier, String reason) {
    if (testIdentifier.isTest()) {
      printSyncToken(TestEventMessage.IGNORE_FAILURE_TEST_PREFIX, Optional.of(testIdentifier));
    }
  }

  @Override
  public void executionFinished(TestIdentifier testIdentifier, TestExecutionResult testExecutionResult) {
    if (testIdentifier.isTest()) {
      switch (testExecutionResult.getStatus()) {
        case ABORTED:
          // fall through

        case FAILED:
          printSyncToken(TestEventMessage.FAILURE_TEST_BEGIN, Optional.of(testIdentifier));
          testExecutionResult.getThrowable().ifPresent((ex) -> ex.printStackTrace(System.out));
          myFailuresCount++;
          // fall through

        case SUCCESSFUL:
          flush();
          printSyncToken(TestEventMessage.FINISH_TEST, Optional.of(testIdentifier));
          flush();
          break;
      }

    } else if (!(shouldSkipContainer(testIdentifier))) {
      flush();
      printSyncToken(TestEventMessage.FINISH_TESTRUN, Optional.of(testIdentifier));
      flush();

    }
  }

  private void flush() {
    System.out.flush();
    System.err.flush();
  }

  private void printSyncToken(@NotNull String tokenPrefix, @NotNull final Optional<TestIdentifier> identifier) {
    // simply mimic the standard structure of test class/test methods
    final StringBuilder builder = new StringBuilder();
    builder.append(tokenPrefix);
    identifier.flatMap(TestIdentifier::getSource).ifPresentOrElse((src) -> {
      if (src instanceof ClassSource) {
        builder.append(((ClassSource) src).getClassName());

      } else if (src instanceof MethodSource) {
        builder.append(((MethodSource) src).getClassName()).append(":").append(((MethodSource) src).getMethodName());

      } else {
        builder.append(identifier.map(TestIdentifier::getDisplayName));
      }

    }, () -> {

      builder.append("FAKEDESCRIPTION");

    });

    Runtime runtime = Runtime.getRuntime();
    builder.append(":memory=").append(runtime.totalMemory() - runtime.freeMemory());
    builder.append(":time=").append(System.currentTimeMillis());

    synchronized (myOutput) {
      myOutput.writeCommand(builder.toString());
      myOutput.flushSafe();
    }
  }

  private boolean shouldSkipContainer(TestIdentifier testIdentifier) {
    UniqueId id = UniqueId.parse(testIdentifier.getUniqueId());
    List<UniqueId.Segment> segments = id.getSegments();
    if (segments.isEmpty()) {
      return false;
    }
    UniqueId.Segment lastSegment = segments.get(segments.size() - 1);
    return lastSegment.getType().equals("engine");
  }

}
