content: package featurecat.lizzie.analysis;
  
  import com.google.common.collect.ImmutableMap;
  import com.google.common.util.concurrent.ListenableFuture;
  import org.apache.commons.lang3.StringUtils;
  import featurecat.lizzie.util.GenericLizzieException;
  import org.apache.commons.lang3.builder.Builder;
  
  import java.util.List;
  import java.util.concurrent.CountDownLatch;
  import java.util.concurrent.ExecutionException;
  import java.util.concurrent.TimeUnit;
  import java.util.concurrent.TimeoutException;
  import java.util.function.Consumer;
  
  public class GtpBasedAnalyzerBuilder implements Builder<AbstractGtpBasedAnalyzer> {
      public static final String REASON = "REASON";
      public static final String ENGINE_NOT_FUNCTION = "ENGINE_NOT_FUNCTION";
      public static final String ENGINE_NOT_SUPPORTED = "ENGINE_NOT_SUPPORTED";
  
      private GtpClient gtpClient;
  
      public GtpBasedAnalyzerBuilder setGtpClient(GtpClient gtpClient) {
          this.gtpClient = gtpClient;
  
          return this;
      }
  
      @Override
      public AbstractGtpBasedAnalyzer build() {
          if (!gtpClient.isRunning()) {
              gtpClient.start();
          }
  
          // Check for engine ready
          ListenableFuture<List<String>> future = gtpClient.postCommand("name");
          List<String> nameResponse = null;
          try {
              nameResponse = future.get(60, TimeUnit.SECONDS);
          } catch (ExecutionException | TimeoutException | InterruptedException e) {
              // Do nothing
          }
  
          if (!GtpCommand.isSuccessfulResponse(nameResponse)) {
              throw new GenericLizzieException(ImmutableMap.of(REASON, ENGINE_NOT_FUNCTION));
          }
          String name = GtpCommand.getLineWithoutResponseHeader(nameResponse, 0).trim();
          if (name.equals("Leela Zero")) {
              int leelazEngineVersion = getLeelazEngineVersion();
              if (leelazEngineVersion == 2) {
                  return new OfficialLeelazAnalyzerV2(gtpClient);
              } else if (leelazEngineVersion == 1) {
                  return new OfficialLeelazAnalyzerV1(gtpClient);
              } else {
                  detectCorrectModifiedLeelazEngine();
                  return new ClassicModifiedLeelazAnalyzer(gtpClient);
              }
          } else if (name.equals("Leela Zero Phoenix")) {
              return new PhoenixGoAnalyzer(gtpClient);
          } else {
              throw new GenericLizzieException(ImmutableMap.of(REASON, ENGINE_NOT_SUPPORTED));
          }
      }
  
      private int getLeelazEngineVersion() {
          List<String> listCommandsResponse = null;
          ListenableFuture<List<String>> future = gtpClient.postCommand("list_commands");
          try {
              listCommandsResponse = future.get(5, TimeUnit.SECONDS);
          } catch (ExecutionException | TimeoutException | InterruptedException e) {
              // Do nothing
          }
  
          if (!GtpCommand.isSuccessfulResponse(listCommandsResponse)) {
              throw new GenericLizzieException(ImmutableMap.of(REASON, ENGINE_NOT_FUNCTION));
          }
  
          GtpCommand.removeResponseHeaderInPlace(listCommandsResponse);
          assert listCommandsResponse != null;
          boolean isOldVersion = listCommandsResponse.stream().noneMatch(s -> StringUtils.equalsIgnoreCase(s.trim(), "lz-analyze"));
          if (isOldVersion) {
              return 0;
          }
  
          return detectOfficialLeelazAnalyzingProtocolVersion();
      }
  
      private int detectOfficialLeelazAnalyzingProtocolVersion() {
          OfficialV2LeelazEngineDetector detector = new OfficialV2LeelazEngineDetector();
          gtpClient.registerStdoutLineConsumer(detector);
  
          try {
              gtpClient.postCommand("lz-analyze 20", true, null);
              if (!detector.latch.await(8, TimeUnit.SECONDS)) {
                  throw new GenericLizzieException(ImmutableMap.of(REASON, ENGINE_NOT_SUPPORTED));
              }
              gtpClient.sendCommand("name"); // Stop
          } catch (InterruptedException e) {
              // Do nothing
          } finally {
              gtpClient.unregisterStdoutLineConsumer(detector);
          }
  
          return detector.version;
      }
  
      private static class OfficialV2LeelazEngineDetector implements Consumer<String> {
          private CountDownLatch latch = new CountDownLatch(1);
          private int version = 1;
  
          @Override
          public void accept(String line) {
              if (StringUtils.isEmpty(line)) {
                  return;
              }
  
              if (StringUtils.startsWithIgnoreCase(line, "info")) {
                  if (StringUtils.contains(line, "order")) {
                      version = 2;
                  } else {
                      version = 1;
                  }
  
                  latch.countDown();
              }
          }
      }
  
      private void detectCorrectModifiedLeelazEngine() {
          CorrectModifiedLeelazEngineDetector detector = new CorrectModifiedLeelazEngineDetector();
          gtpClient.registerStderrLineConsumer(detector);
          try {
              gtpClient.postCommand("time_left b 0 0");
              if (!detector.latch.await(8, TimeUnit.SECONDS)) {
                  throw new GenericLizzieException(ImmutableMap.of(REASON, ENGINE_NOT_SUPPORTED));
              }
              gtpClient.sendCommand("name"); // Stop
          } catch (InterruptedException e) {
              // Do nothing
          } finally {
              gtpClient.unregisterStderrLineConsumer(detector);
          }
      }
  
      private static class CorrectModifiedLeelazEngineDetector implements Consumer<String> {
          private CountDownLatch latch = new CountDownLatch(1);
  
          @Override
          public void accept(String line) {
              if (line.startsWith("~begin")) {
                  latch.countDown();
              }
          }
      }
  }
  