package be.iminds.ilabt.jfed.connectivity_tester;

import be.iminds.ilabt.jfed.fedmon.webapi.service.json.*;
import be.iminds.ilabt.jfed.lowlevel.TestbedInfoSource;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.Multimap;

import javax.inject.Inject;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.*;
import java.util.stream.Collectors;

/**
 * User: twalcari
 * Date: 1/7/14
 * Time: 2:28 PM
 */
public class AMConnectionTestGenerator implements TestsGenerator {


    private final TestbedInfoSource testbedInfoSource;

    @Inject
    public AMConnectionTestGenerator(TestbedInfoSource testbedInfoSource) {
        this.testbedInfoSource = testbedInfoSource;
    }

    @Override
    public Collection<ConnectivityTest> generateTests() {
        Multimap<HostnameAndPort, ApiInfo> testsPerHost = HashMultimap.create();

        List<ConnectivityTest> tests = new ArrayList<>();

        //aggregate tests per HostnameAndPort
        testbedInfoSource.getServers().stream()
                .filter(auth -> !auth.hasFlag(Server.Flag.excludeInConnectivityTest))
                .forEach(server -> {
                    for (Service service : server.getServices()) {
                        try {
                            URL url = new URL(service.getUrl());
                            testsPerHost.put(new HostnameAndPort(url.getHost(),
                                            url.getPort() != -1 ? url.getPort()
                                                    : url.getDefaultPort()),
                                    new ApiInfo(server.getName(), service.getApi(), service.getApiVersion()));
                        } catch (MalformedURLException e) {
                            e.printStackTrace();
                        }
                    }
                });

        //create one test per hostname and port
        tests.addAll(testsPerHost.keySet().stream().map(hostnameAndPort -> new HostAndPortTest(hostnameAndPort.getHostname(),
                hostnameAndPort.getPort(),
                ApiInfo.mergeApiInfos(testsPerHost.get(hostnameAndPort)),
                "AM")).collect(Collectors.toList()));

        return tests;

    }

    private static class HostnameAndPort {
        private final String hostname;
        private final int port;

        private HostnameAndPort(String hostname, int port) {
            this.hostname = hostname;
            this.port = port;
        }

        public String getHostname() {
            return hostname;
        }

        public int getPort() {
            return port;
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;

            HostnameAndPort that = (HostnameAndPort) o;

            if (port != that.port) return false;
            if (hostname != null ? !Objects.equals(hostname, that.hostname) : that.hostname != null) return false;

            return true;
        }

        @Override
        public int hashCode() {
            int result = hostname != null ? hostname.hashCode() : 0;
            result = 31 * result + port;
            return result;
        }
    }

    private static class ApiInfo implements Comparable<ApiInfo> {
        private final String name;
        private final String role;
        private final String version;

        private ApiInfo(String name, String role, String version) {
            this.name = name;
            this.role = role;
            this.version = version;
        }


        @Override
        public String toString() {
            return String.format("%s %sv%s", name, role, version);
        }

        public static String mergeApiInfos(Collection<ApiInfo> apiInfos) {
            List<ApiInfo> infos = new ArrayList<>(apiInfos);
            Collections.sort(infos);

            String currentName = null;
            StringBuilder result = new StringBuilder();
            boolean newName = true;

            for (ApiInfo apiInfo : infos) {

                if (!Objects.equals(apiInfo.name, currentName)) {
                    if (currentName != null)
                        result.append("; ");
                    result.append(apiInfo.name).append(" ");

                    currentName = apiInfo.name;
                    newName = true;
                }
                if (!newName) {
                    result.append(", ");
                }

                result.append(apiInfo.role).append("v").append(apiInfo.version);
                newName = false;
            }

            return result.toString();
        }

        @Override
        public int compareTo(ApiInfo o) {
            int result = name.compareTo(o.name);
            if (result == 0)
                result = role.compareTo(o.role);
            if (result == 0)
                result = version.compareTo(o.version);

            return result;
        }
    }

}
