// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.jdisc.http.server.jetty;

import com.yahoo.container.logging.AccessLogEntry;
import com.yahoo.jdisc.Metric;
import com.yahoo.jdisc.handler.OverloadException;
import com.yahoo.jdisc.http.HttpRequest.Method;
import org.eclipse.jetty.server.Request;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Enumeration;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import static com.yahoo.jdisc.http.server.jetty.RequestUtils.getConnector;

/**
 * @author Simon Thoresen Hult
 * @author bjorncs
 */
@WebServlet(asyncSupported = true, description = "Bridge between Servlet and JDisc APIs")
class JDiscHttpServlet extends HttpServlet {

    public static final String ATTRIBUTE_NAME_ACCESS_LOG_ENTRY = JDiscHttpServlet.class.getName() + "_access-log-entry";

    private final static Logger log = Logger.getLogger(JDiscHttpServlet.class.getName());
    private final JDiscContext context;

    private static final Set<String> servletSupportedMethods =
            Stream.of(Method.OPTIONS, Method.GET, Method.HEAD, Method.POST, Method.PUT, Method.DELETE, Method.TRACE)
                  .map(Method::name)
                  .collect(Collectors.toSet());

    public JDiscHttpServlet(JDiscContext context) {
        this.context = context;
    }

    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException {
        dispatchHttpRequest(request, response);
    }

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException {
        dispatchHttpRequest(request, response);
    }

    @Override
    protected void doHead(HttpServletRequest request, HttpServletResponse response) throws IOException {
        dispatchHttpRequest(request, response);
    }

    @Override
    protected void doPut(HttpServletRequest request, HttpServletResponse response) throws IOException {
        dispatchHttpRequest(request, response);
    }

    @Override
    protected void doDelete(HttpServletRequest request, HttpServletResponse response) throws IOException {
        dispatchHttpRequest(request, response);
    }

    @Override
    protected void doOptions(HttpServletRequest request, HttpServletResponse response) throws IOException {
        dispatchHttpRequest(request, response);
    }

    @Override
    protected void doTrace(HttpServletRequest request, HttpServletResponse response) throws IOException {
        dispatchHttpRequest(request, response);
    }

    /**
     * Override to set connector attribute before the request becomes an upgrade request in the web socket case.
     * (After the upgrade, the HttpConnection is no longer available.)
     */
    @Override
    protected void service(HttpServletRequest request, HttpServletResponse response) 
            throws ServletException, IOException {
        request.setAttribute(JDiscServerConnector.REQUEST_ATTRIBUTE, getConnector((Request) request));

        Metric.Context metricContext = getMetricContext(request);
        context.metric.add(MetricDefinitions.NUM_REQUESTS, 1, metricContext);
        context.metric.add(MetricDefinitions.JDISC_HTTP_REQUESTS, 1, metricContext);

        String method = request.getMethod().toUpperCase();
        if (servletSupportedMethods.contains(method)) {
            super.service(request, response);
        } else if (method.equals(Method.PATCH.name())) {
            // PATCH method is not handled by the Servlet spec
            dispatchHttpRequest(request, response);
        } else {
            // Divergence from HTTP / Servlet spec: JDisc returns 405 for both unknown and known (but unsupported) methods.
            response.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
        }
    }

    private void dispatchHttpRequest(HttpServletRequest request, HttpServletResponse response) throws IOException {
        AccessLogEntry accessLogEntry = new AccessLogEntry();
        request.setAttribute(ATTRIBUTE_NAME_ACCESS_LOG_ENTRY, accessLogEntry);
        try {
            switch (request.getDispatcherType()) {
                case REQUEST:
                    new HttpRequestDispatch(context, accessLogEntry, getMetricContext(request), request, response).dispatchRequest();
                    break;
                default:
                    if (log.isLoggable(Level.INFO)) {
                        log.info("Unexpected " + request.getDispatcherType() + "; " + formatAttributes(request));
                    }
                    break;
            }
        } catch (OverloadException e) {
            // nop
        } catch (RuntimeException e) {
            throw new ExceptionWrapper(e);
        }
    }

    private static Metric.Context getMetricContext(HttpServletRequest request) {
        return JDiscServerConnector.fromRequest(request).createRequestMetricContext(request, Map.of());
    }

    private static String formatAttributes(final HttpServletRequest request) {
        StringBuilder out = new StringBuilder();
        out.append("attributes = {");
        for (Enumeration<String> names = request.getAttributeNames(); names.hasMoreElements(); ) {
            String name = names.nextElement();
            out.append(" '").append(name).append("' = '").append(request.getAttribute(name)).append("'");
            if (names.hasMoreElements()) {
                out.append(",");
            }
        }
        out.append(" }");
        return out.toString();
    }
}

@@ -3924,35 +3924,50 @@
 equest, response
+, metricContext
 );%0A
-
         %7D else %7B
@@ -4291,32 +4291,260 @@
 s IOException %7B%0A
+        dispatchHttpRequest(request, response, getMetricContext(request));%0A    %7D%0A%0A    private void dispatchHttpRequest(HttpServletRequest request, HttpServletResponse response, Metric.Context metricContext) throws IOException %7B%0A
         AccessLo
@@ -4832,20 +4832,17 @@
 gEntry, 
-getM
+m
 etricCon
@@ -4845,25 +4845,16 @@
 cContext
-(request)
 , reques

Use the same metric context that you have already aquired.