package com.bmuschko.gradle.docker.internal.services;

import com.bmuschko.gradle.docker.tasks.DockerClientConfiguration;
import com.github.dockerjava.api.DockerClient;
import com.github.dockerjava.core.DefaultDockerClientConfig;
import com.github.dockerjava.core.DockerClientImpl;
import com.github.dockerjava.httpclient5.ApacheDockerHttpClient;
import org.gradle.api.file.Directory;
import org.gradle.api.file.DirectoryProperty;
import org.gradle.api.model.ObjectFactory;
import org.gradle.api.provider.Property;
import org.gradle.api.provider.Provider;
import org.gradle.api.services.BuildService;
import org.gradle.api.services.BuildServiceParameters;

import javax.inject.Inject;
import java.io.File;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

public abstract class DockerClientService implements BuildService<DockerClientService.Params>, AutoCloseable {
    private final Map<DefaultDockerClientConfig, DockerClient> dockerClients;

    private final ObjectFactory objects;

    public interface Params extends BuildServiceParameters {
        Property<String> getUrl();
        DirectoryProperty getCertPath();
        Property<String> getApiVersion();
    }

    @Inject
    public DockerClientService(ObjectFactory objects) {
        this.objects = objects;
        dockerClients = new ConcurrentHashMap<>();
    }

    public DockerClient getDockerClient(DockerClientConfiguration dockerClientConfiguration) {
        String dockerUrl = getDockerHostUrl(dockerClientConfiguration);
        File dockerCertPath = thingOrProperty(objects.directoryProperty(), dockerClientConfiguration.getCertPath(), getParameters().getCertPath()).map(Directory::getAsFile).getOrNull();
        String apiVersion = thingOrProperty(objects.property(String.class), dockerClientConfiguration.getApiVersion(), getParameters().getApiVersion()).getOrNull();

        // Create configuration
        DefaultDockerClientConfig.Builder dockerClientConfigBuilder = DefaultDockerClientConfig.createDefaultConfigBuilder();
        dockerClientConfigBuilder.withDockerHost(dockerUrl);

        if (dockerCertPath != null) {
            String caninicalCertPath;
            try {
                caninicalCertPath = dockerCertPath.getCanonicalPath();
            } catch (IOException e) {
                throw new UncheckedIOException(e);
            }
            dockerClientConfigBuilder.withDockerTlsVerify(true);
            dockerClientConfigBuilder.withDockerCertPath(caninicalCertPath);
        } else {
            dockerClientConfigBuilder.withDockerTlsVerify(false);
        }

        if (apiVersion != null) {
            dockerClientConfigBuilder.withApiVersion(apiVersion);
        }

        DefaultDockerClientConfig dockerClientConfig = dockerClientConfigBuilder.build();
        return createDefaultDockerClient(dockerClientConfig);
    }

    private DockerClient createDefaultDockerClient(DefaultDockerClientConfig config) {
        return dockerClients.computeIfAbsent(config, i -> {
            ApacheDockerHttpClient dockerClient = new ApacheDockerHttpClient.Builder()
                    .dockerHost(config.getDockerHost())
                    .sslConfig(config.getSSLConfig())
                    .build();
            return DockerClientImpl.getInstance(
                    config,
                    dockerClient
            );
        });
    }

    /**
     * Checks if Docker host URL starts with http(s) and if so, converts it to tcp
     * which is accepted by docker-java library.
     *
     * @param dockerClientConfiguration docker client configuration
     * @return Docker host URL as string
     */
    private String getDockerHostUrl(DockerClientConfiguration dockerClientConfiguration) {
        String url = thingOrProperty(objects.property(String.class), dockerClientConfiguration.getUrl(), getParameters().getUrl()).map(String::toLowerCase).get();
        return url.startsWith("http") ? "tcp" + url.substring(url.indexOf(':')) : url;
    }

    private <T> Provider<T> thingOrProperty(Property<T> prop, T t, Property<T> tp) {
        prop.set(t);
        return prop.orElse(tp);
    }

    @Override
    public void close() throws Exception {
        IOException throwable = null;
        for (DockerClient dockerClient : dockerClients.values()) {
            try {
                dockerClient.close();
            } catch (IOException e) {
                if (throwable == null) {
                    throwable = e;
                } else {
                    throwable.addSuppressed(e);
                }
            }
        }
        if (throwable != null) {
            throw throwable;
        }
    }
}

@@ -834,16 +834,60 @@
 shMap;%0A%0A
+/**%0A * Build service for Docker client.%0A */%0A
 public a
@@ -1106,24 +1106,77 @@
 y objects;%0A%0A
+    /**%0A     * Parameters for build service.%0A     */%0A
     public i
@@ -1236,282 +1236,934 @@
     
-Property%3CString%3E getUrl();%0A        DirectoryProperty getCertPath();%0A        Property%3CString%3E getApiVersion();%0A    %7D%0A%0A    @Inject%0A    public DockerClientService(ObjectFactory objects) %7B%0A        this.objects = objects;%0A        dockerClients = new ConcurrentHashMap%3C%3E();%0A    %7D%0A
+/**%0A         * The server URL to connect to via Docker%E2%80%99s remote API.%0A         *%0A         * @return The server URL%0A         */%0A        Property%3CString%3E getUrl();%0A%0A        /**%0A         * The path to certificates for communicating with Docker over SSL.%0A         *%0A         * @return The cert path%0A         */%0A        DirectoryProperty getCertPath();%0A%0A        /**%0A         * The remote API version.%0A         *%0A         * @return The remote API%0A         */%0A        Property%3CString%3E getApiVersion();%0A    %7D%0A%0A    /**%0A     * Constructor for Docker client service.%0A     *%0A     * @param objects The object factory%0A     */%0A    @Inject%0A    public DockerClientService(ObjectFactory objects) %7B%0A        this.objects = objects;%0A        dockerClients = new ConcurrentHashMap%3C%3E();%0A    %7D%0A%0A    /**%0A     * Returns the Docker client.%0A     *%0A     * @param dockerClientConfiguration The Docker client configuration%0A     * @return Docker client%0A     */
 %0A   
@@ -2954,25 +2954,25 @@
   String can
-i
+o
 nicalCertPat
@@ -3011,17 +3011,17 @@
      can
-i
+o
 nicalCer
@@ -3291,17 +3291,17 @@
 Path(can
-i
+o
 nicalCer

Fix Javadoc warnings