// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.model.container.http.xml;

import com.yahoo.component.ComponentSpecification;
import com.yahoo.config.model.builder.xml.XmlHelper;
import com.yahoo.config.model.deploy.DeployState;
import com.yahoo.config.model.producer.AbstractConfigProducer;
import com.yahoo.log.LogLevel;
import com.yahoo.text.XML;
import com.yahoo.vespa.model.builder.xml.dom.VespaDomBuilder;
import com.yahoo.vespa.model.container.Container;
import com.yahoo.vespa.model.container.ContainerCluster;
import com.yahoo.vespa.model.container.component.chain.Chain;
import com.yahoo.vespa.model.container.http.AccessControl;
import com.yahoo.vespa.model.container.http.FilterChains;
import com.yahoo.vespa.model.container.http.Http;
import com.yahoo.vespa.model.container.http.Http.Binding;
import org.w3c.dom.Element;

import java.util.ArrayList;
import java.util.List;
import java.util.Optional;

import static com.yahoo.vespa.model.container.http.AccessControl.ACCESS_CONTROL_CHAIN_ID;
import static com.yahoo.vespa.model.container.http.AccessControl.accessControlBinding;

/**
 * @author tonytv
 * @author gjoranv
 */
public class HttpBuilder extends VespaDomBuilder.DomConfigProducerBuilder<Http> {

    @Override
    protected Http doBuild(AbstractConfigProducer ancestor, Element spec) {
        FilterChains filterChains;
        List<Binding> bindings = new ArrayList<>();

        Element filteringElem = XML.getChild(spec, "filtering");
        if (filteringElem != null) {
            filterChains = new FilterChainsBuilder().build(ancestor, filteringElem);
            bindings = readFilterBindings(filteringElem);

            Element accessControlElem = XML.getChild(filteringElem, "access-control");
            if (accessControlElem != null) {
                bindings.addAll(getAccessControlBindings(ancestor));
                filterChains.add(new Chain<>(FilterChains.emptyChainSpec(ACCESS_CONTROL_CHAIN_ID)));
            }
        } else {
            filterChains = new FilterChainsBuilder().newChainsInstance(ancestor);
        }

        Http http = new Http(bindings);
        http.setFilterChains(filterChains);

        buildHttpServers(ancestor, http, spec);

        return http;
    }

    private static List<Binding> getAccessControlBindings(AbstractConfigProducer ancestor) {
        ArrayList<Binding> bindings = new ArrayList<>();
        getContainerCluster(ancestor).ifPresent(cluster -> cluster.getHandlers().stream()
                .filter(AccessControl::shouldHandlerBeProtected)
                .forEach(handler -> handler.getServerBindings()
                        .forEach(binding -> bindings.add(accessControlBinding(binding)))));
        return bindings;
    }

    private static Optional<ContainerCluster> getContainerCluster(AbstractConfigProducer configProducer) {
        AbstractConfigProducer currentProducer = configProducer;
        while (currentProducer.getClass() != ContainerCluster.class) {
            currentProducer = currentProducer.getParent();
            if (currentProducer == null)
                return Optional.empty();
        }
        return Optional.of((ContainerCluster) currentProducer);
    }

    private List<Binding> readFilterBindings(Element filteringSpec) {
        List<Binding> result = new ArrayList<>();

        for (Element child: XML.getChildren(filteringSpec)) {
            String tagName = child.getTagName();
            if ((tagName.equals("request-chain") || tagName.equals("response-chain"))) {
                ComponentSpecification chainId = XmlHelper.getIdRef(child);

                for (Element bindingSpec: XML.getChildren(child, "binding")) {
                    String binding = XML.getValue(bindingSpec);
                    result.add(new Binding(chainId, binding));
                }
            }
        }
        return result;
    }

    private void buildHttpServers(AbstractConfigProducer ancestor, Http http, Element spec) {
        http.setHttpServer(new JettyHttpServerBuilder().build(ancestor, spec));
    }

    static int readPort(Element spec, DeployState deployState) {
        String portString = spec.getAttribute("port");

        int port = Integer.parseInt(portString);
        if (port < 0)
            throw new IllegalArgumentException(String.format("Invalid port %d.", port));

        int legalPortInHostedVespa = Container.BASEPORT;
        if (deployState.isHosted() && port != legalPortInHostedVespa) {
            deployState.getDeployLogger().log(LogLevel.WARNING,
                    String.format("Trying to set port to %d for http server with id %s. You cannot set port to anything else than %s",
                            port, spec.getAttribute("id"), legalPortInHostedVespa));
        }

        return port;
    }
}

@@ -995,16 +995,52 @@
 ptional;
+%0Aimport java.util.stream.Collectors;
 %0A%0Aimport
@@ -2456,64 +2456,14 @@
     
-ArrayList%3CBinding%3E bindings = new ArrayList%3C%3E();%0A       
+return
  get
@@ -2492,18 +2492,29 @@
 tor)
-.ifPresent
+%0A                .map
 (clu
@@ -2552,16 +2552,24 @@
 tream()%0A
+        
         
@@ -2641,24 +2641,32 @@
         
+      
-.forEach
+  .flatMap
 (handler
@@ -2696,16 +2696,26 @@
 ndings()
+.stream())
 %0A       
@@ -2736,98 +2736,146 @@
    .
-forEach(binding -%3E bindings.add(accessControlBinding(binding)))));%0A        return bindings
+map(AccessControl::accessControlBinding)%0A                        .collect(Collectors.toList()))%0A                .orElse(new ArrayList%3C%3E())
 ;%0A  

Use flatMap() and collect().