package com.ebay.epd.dockerrunner;

import com.google.common.base.Function;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.spotify.docker.client.DockerClient;
import com.spotify.docker.client.DockerException;
import com.spotify.docker.client.messages.ContainerConfig;
import com.spotify.docker.client.messages.ContainerCreation;
import com.spotify.docker.client.messages.HostConfig;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

import static com.google.common.collect.Lists.newArrayList;

public class Container {
    private final DockerClient client;
    private final String imageName;
    private final Iterable<Link> links;
    private final String host;
    private Option<String> cpuset;
    private Option<Memory> memory;
    private List<Env> envs;
    private StartedContainer startedContainer;

    private final BlockUntil noBlock = new BlockUntil() {
        @Override
        public boolean conditionMet(String host, StartedContainer container) {
            return true;
        }
    };

    public Container(DockerClient client, String imageName, Iterable<Link> links, String host, Option<String> cpuset, Option<Memory> memory, List<Env> envs) {
        this.client = client;
        this.imageName = imageName;
        this.links = links;
        this.host = host;
        this.cpuset = cpuset;
        this.memory = memory;
        this.envs = envs;
    }

    public StartedContainer start() {
        return start(noBlock, 0);
    }

    public StartedContainer start(BlockUntil blockUntil, int secondsTimeout) {
        if (startedContainer == null) {
            List<String> concatLinks = newArrayList(Iterables.transform(links, new Function<Link, String>() {
                @Override
                public String apply(Link link) {
                    return String.format("%s:%s", link.container.name(), link.alias);
                }
            }));
            List<String> envStrs = Lists.transform(envs, new Function<Env, String>() {
                @Override
                public String apply(Env input) {
                    return String.format("%s=%s", input.key, input.value);
                }
            });
            HostConfig hostConfig = HostConfig.builder().publishAllPorts(true).links(concatLinks).build();
            final ContainerConfig.Builder containerConfig = ContainerConfig.builder().image(imageName).env(envStrs);
            cpuset.doIfPresent(new Option.OptionalCommand<String>() {
                @Override
                public void apply(String cs) {
                    containerConfig.cpuset(cs);
                }
            });
            memory.doIfPresent(new Option.OptionalCommand<Memory>() {
                @Override
                public void apply(Memory mem) {
                    containerConfig.memory(mem.toBytes());
                }
            });

            ContainerCreation container;
            try {
                container = client.createContainer(containerConfig.build());
                client.startContainer(container.id(), hostConfig);
            } catch (DockerException | InterruptedException e) {
                throw new ContainerException(e);
            }
            Map<String, StartedContainer> linkedContainers = new HashMap<>();
            for (Link link : links) {
                linkedContainers.put(link.alias, link.container);
            }
            startedContainer = new StartedContainer(client, container.id(), linkedContainers);
        }
        waitFor(blockUntil, System.currentTimeMillis() + secondsTimeout * 1000);
        return startedContainer;
    }

    private void waitFor(BlockUntil blockUntil, long timeToStop) {
        try {
            if (blockUntil.conditionMet(host, startedContainer)) {
                return;
            }
        } catch (Exception e) {
            //Exception is equivalent to false... carry on.
        }
        if (System.currentTimeMillis() > timeToStop) {
            throw new ContainerStartupTimeoutException();
        }
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            //Do we need to worry about this?
        }
        waitFor(blockUntil, timeToStop);
    }

    void stopIfStarted() {
        if (startedContainer != null) {
            startedContainer.stop();
        }
    }

    public static class Env {
        private final String key;
        private final String value;

        public Env(String key, String value) {
            this.key = key;
            this.value = value;
        }
    }

    public static class ContainerException extends RuntimeException {
        public ContainerException(Exception e) {
            super(e);
        }
    }

    public static class ContainerStartupTimeoutException extends RuntimeException {
    }
}

@@ -1071,16 +1071,39 @@
 %7D%0A    %7D;
+%0A    private String id;
 %0A%0A    pu
@@ -3062,24 +3062,61 @@
 g.build());%0A
+                id = container.id();%0A
             
@@ -3141,30 +3141,18 @@
 ntainer(
-container.id()
+id
 , hostCo
@@ -4043,32 +4043,387 @@
 %3E timeToStop) %7B%0A
+            String logMessage;%0A            try %7B%0A                logMessage = client.logs(id, DockerClient.LogsParameter.STDOUT, DockerClient.LogsParameter.STDERR).readFully();%0A            %7D catch (DockerException%7CInterruptedException e) %7B%0A                logMessage = %22An error occurred trying to pull the logs from the docker container.%22;%0A            %7D%0A
             thro
@@ -4461,16 +4461,26 @@
 ception(
+logMessage
 );%0A     
@@ -5264,16 +5264,262 @@
 ption %7B%0A
-    %7D
+%0A        private final String logMessage;%0A%0A        public ContainerStartupTimeoutException(String logMessage) %7B%0A            this.logMessage = logMessage;%0A        %7D%0A%0A        public String containerLog() %7B%0A            return logMessage;%0A        %7D%0A    %7D%0A
 %0A%7D%0A

Return container logs when container start times out.