package org.testcontainers.containers.wait;

import lombok.extern.slf4j.Slf4j;
import org.rnorth.ducttape.TimeoutException;
import org.rnorth.ducttape.unreliables.Unreliables;
import org.testcontainers.DockerClientFactory;
import org.testcontainers.containers.ContainerLaunchException;
import org.testcontainers.containers.GenericContainer;
import org.testcontainers.dockerclient.ProxiedUnixSocketClientProviderStrategy;
import org.testcontainers.dockerclient.WindowsClientProviderStrategy;

import java.net.Socket;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.TimeUnit;

/**
 * Waits until a socket connection can be established on a port exposed or mapped by the container.
 *
 * @author richardnorth
 */
@Slf4j
public class HostPortWaitStrategy extends GenericContainer.AbstractWaitStrategy {

    private static final String SUCCESS_MARKER = "TESTCONTAINERS_SUCCESS";
    @Override
    protected void waitUntilReady() {
        final Integer port = getLivenessCheckPort();
        if (null == port) {
            log.debug("Liveness check port of {} is empty. Not waiting.", container.getContainerName());
            return;
        }

        Callable<Boolean> checkStrategy;

        if (isApplicable()) {
            List<Integer> exposedPorts = container.getExposedPorts();

            Integer exposedPort = exposedPorts.stream()
                    .filter(it -> port.equals(container.getMappedPort(it)))
                    .findFirst()
                    .orElse(null);

            if (null == exposedPort) {
                log.warn("Liveness check port of {} is set to {}, but it's not listed in exposed ports.",
                        container.getContainerName(), port);
                return;
            }

            String[][] commands = {
                     { "/bin/sh", "-c", "nc -vz -w 1 localhost " + exposedPort + " && echo " + SUCCESS_MARKER },
                     { "/bin/bash", "-c", "</dev/tcp/localhost/" + exposedPort + " && echo " + SUCCESS_MARKER }
            };

            checkStrategy = () -> {
                for (String[] command : commands) {
                    try {
                        if (container.execInContainer(command).getStdout().contains(SUCCESS_MARKER)) {
                            return true;
                        }
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    } catch (Exception e) {
                        continue;
                    }
                }

                return false;
            };
        } else {
            checkStrategy = () -> {
                new Socket(container.getContainerIpAddress(), port).close();
                return true;
            };
        }

        try {
            Unreliables.retryUntilTrue((int) startupTimeout.getSeconds(), TimeUnit.SECONDS, () -> {
                return getRateLimiter().getWhenReady(() -> {
                    try {
                        return checkStrategy.call();
                    } catch (Exception e) {
                        throw new RuntimeException(e);
                    }
                });
            });

        } catch (TimeoutException e) {
            throw new ContainerLaunchException("Timed out waiting for container port to open (" +
                    container.getContainerIpAddress() + ":" + port + " should be listening)");
        }
    }

    private boolean isApplicable() {
        // Special case for Docker for Mac, see #160
        if(DockerClientFactory.instance().isUsing(ProxiedUnixSocketClientProviderStrategy.class)
                && System.getProperty("os.name").toLowerCase().contains("mac")) {
            return true;
        }

        // Special case for Docker for Windows, see #160
        if (DockerClientFactory.instance().isUsing(WindowsClientProviderStrategy.class)) {
            return true;
        }

        return false;
    }
}

@@ -1232,28 +1232,38 @@
     if (
-isApplicable
+shouldCheckWithCommand
 ()) %7B%0A  
@@ -3482,20 +3482,30 @@
 ean 
-isApplicable
+shouldCheckWithCommand
 () %7B

rename isApplicable -> shouldCheckWithCommand