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();
            }
        }
    }
}