/*
 * Copyright 2016 Palantir Technologies, Inc. All rights reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.palantir.docker.compose.connection;

import com.google.common.base.Supplier;
import com.google.common.base.Suppliers;
import com.google.common.base.Throwables;
import com.jayway.awaitility.Awaitility;
import com.palantir.docker.compose.execution.DockerCompose;
import org.joda.time.Duration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;

import static org.hamcrest.core.Is.is;
import static org.junit.Assert.assertThat;

public class Container {

    private static final Logger log = LoggerFactory.getLogger(Container.class);

    private final String containerName;
    private final DockerCompose dockerComposeProcess;

    private final Supplier<Ports> portMappings = Suppliers.memoize(this::getDockerPorts);

    public Container(String containerName, DockerCompose dockerComposeProcess) {
        this.containerName = containerName;
        this.dockerComposeProcess = dockerComposeProcess;
    }

    public String getContainerName() {
        return containerName;
    }

    public boolean waitForPorts(Duration timeout) {
        try {
            Ports exposedPorts = portMappings.get();
            exposedPorts.waitToBeListeningWithin(timeout);
            return true;
        } catch (Exception e) {
            log.warn("Container '" + containerName + "' failed to come up: " + e.getMessage(), e);
            return false;
        }
    }


    public boolean waitForHttpPort(int internalPort, Function<DockerPort, String> urlFunction, Duration timeout) {
        try {
            DockerPort port = portMappedInternallyTo(internalPort);
            Awaitility.await()
                .pollInterval(50, TimeUnit.MILLISECONDS)
                .atMost(timeout.getMillis(), TimeUnit.MILLISECONDS)
                .until(() -> assertThat(port.isListeningNow() && port.isHttpResponding(urlFunction), is(true)));
            return true;
        } catch (Exception e) {
            log.warn("Container '" + containerName + "' failed to come up: " + e.getMessage(), e);
            return false;
        }
    }

    public DockerPort portMappedExternallyTo(int externalPort) throws IOException, InterruptedException {
        return portMappings.get()
                           .stream()
                           .filter(port -> port.getExternalPort() == externalPort)
                           .findFirst()
                           .orElseThrow(() -> new IllegalArgumentException("No port mapped externally to '" + externalPort + "' for container '" + containerName + "'"));
    }

    public DockerPort portMappedInternallyTo(int internalPort) throws IOException, InterruptedException {
        return portMappings.get()
                           .stream()
                           .filter(port -> port.getInternalPort() == internalPort)
                           .findFirst()
                           .orElseThrow(() -> new IllegalArgumentException("No internal port '" + internalPort + "' for container '" + containerName + "'"));
    }

    private Ports getDockerPorts() {
        try {
            return dockerComposeProcess.ports(containerName);
        } catch (IOException | InterruptedException e) {
            throw Throwables.propagate(e);
        }
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) {
            return true;
        }
        if (o == null || getClass() != o.getClass()) {
            return false;
        }
        Container container = (Container) o;
        return Objects.equals(containerName, container.containerName);
    }

    @Override
    public int hashCode() {
        return Objects.hash(containerName);
    }

    @Override
    public String toString() {
        return "Container{" +
                "containerName='" + containerName + '\'' +
                '}';
    }

    public boolean areAllPortsOpen() {
        long numberOfUnavailablePorts = portMappings.get().stream()
                .filter(port -> !port.isListeningNow())
                .count();

        return numberOfUnavailablePorts == 0;
    }
}

@@ -1158,51 +1158,8 @@
 .is;
-%0Aimport static org.junit.Assert.assertThat;
 %0A%0Apu
@@ -2092,16 +2092,454 @@
 %0A    %7D%0A%0A
+    public boolean portIsListeningOnHttp(int internalPort, Function%3CDockerPort, String%3E urlFunction) %7B%0A        try %7B%0A            DockerPort port = portMappedInternallyTo(internalPort);%0A            return port.isListeningNow() && port.isHttpResponding(urlFunction);%0A        %7D catch (Exception e) %7B%0A            log.warn(%22Container '%22 + containerName + %22' failed to come up: %22 + e.getMessage(), e);%0A            return false;%0A        %7D%0A    %7D%0A
 %0A    pub
@@ -2664,76 +2664,8 @@
 y %7B%0A
-            DockerPort port = portMappedInternallyTo(internalPort);%0A
     
@@ -2849,25 +2849,13 @@
  -%3E 
-assertThat(
 port
-.i
+I
 sLis
@@ -2864,39 +2864,29 @@
 ning
-Now() && port.isHttpResponding(
+OnHttp(internalPort, 
 urlF
@@ -2904,17 +2904,16 @@
 s(true))
-)
 ;%0A      

Add HTTP wait method