package org.pac4j.vertx;

import io.vertx.core.http.HttpServerRequest;
import io.vertx.core.json.JsonArray;
import io.vertx.core.json.JsonObject;
import io.vertx.ext.web.RoutingContext;
import io.vertx.ext.web.Session;
import org.pac4j.core.context.Cookie;
import org.pac4j.core.context.WebContext;
import org.pac4j.core.context.session.SessionStore;
import org.pac4j.vertx.auth.Pac4jUser;

import java.net.URI;
import java.net.URISyntaxException;
import java.security.InvalidParameterException;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * WebContext implementation for Vert.x 3.
 *
 * @author Jeremy Prime
 * @since 2.0.0
 */
public class VertxWebContext implements WebContext {

    private final RoutingContext routingContext;
    private final String method;
    private final String serverName;
    private final int serverPort;
    private final String fullUrl;
    private final String scheme;
    private final String remoteAddress;
    private final JsonObject headers;
    private final JsonObject parameters;
    private final Map<String, String[]> mapParameters;
    private final SessionStore<VertxWebContext> sessionStore;

    private boolean contentHasBeenWritten = false; // Need to set chunked before first write of any content

    public VertxWebContext(final RoutingContext routingContext, final SessionStore<VertxWebContext> sessionStore) {
        final HttpServerRequest request = routingContext.request();
        this.routingContext = routingContext;
        this.method = request.method().toString();
        this.sessionStore = sessionStore;

        this.fullUrl = request.absoluteURI();
        URI uri;
        try {
            uri = new URI(fullUrl);
        } catch (URISyntaxException e) {
            e.printStackTrace();
            throw new InvalidParameterException("Request to invalid URL " + fullUrl + " while constructing VertxWebContext");
        }
        this.scheme = uri.getScheme();
        this.serverName = uri.getHost();
        this.serverPort = (uri.getPort() != -1) ? uri.getPort() : scheme.equals("http") ? 80 : 443;
        this.remoteAddress = request.remoteAddress().toString();

        headers = new JsonObject();
        for (String name : request.headers().names()) {
            headers.put(name, request.headers().get(name));
        }

        parameters = new JsonObject();
        for (String name : request.params().names()) {
            parameters.put(name, new JsonArray(Arrays.asList(request.params().getAll(name).toArray())));
        }

        mapParameters = new HashMap<>();
        for (String name : parameters.fieldNames()) {
            JsonArray params = parameters.getJsonArray(name);
            String[] values = new String[params.size()];
            int i = 0;
            for (Object o : params) {
                values[i++] = (String) o;
            }
            mapParameters.put(name, values);
        }

    }

    public void failResponse(final int status) {
        routingContext.fail(status);
    }

    public void completeResponse() {
        routingContext.response().end();
    }

    @Override
    public String getRequestParameter(String name) {
        JsonArray values = parameters.getJsonArray(name);
        if (values != null && values.size() > 0) {
            return values.getString(0);
        }
        return null;
    }

    @Override
    public Map<String, String[]> getRequestParameters() {
        return mapParameters;
    }

    @Override
    public Object getRequestAttribute(String s) {
        return routingContext.get(s);
    }

    @Override
    public void setRequestAttribute(String s, Object o) {
        routingContext.put(s, o);
    }

    @Override
    public String getRequestHeader(String name) {
        return headers.getString(name);
    }

    @Override
    public String getRequestMethod() {
        return method;
    }

    @Override
    public String getRemoteAddr() {
        return remoteAddress;
    }

    @Override
    public void writeResponseContent(String content) {
        if (content != null && !content.isEmpty()) {
            if (!contentHasBeenWritten) {
                routingContext.response().setChunked(true);
                contentHasBeenWritten = true;
            }
            routingContext.response().write(content);
        }
    }

    @Override
    public void setResponseStatus(int code) {
        routingContext.response().setStatusCode(code);
    }

    @Override
    public void setResponseHeader(String name, String value) {
        routingContext.response().putHeader(name, value);
    }

    public Map<String, String> getResponseHeaders() {
        return routingContext.response().headers().entries().stream()
            .collect(Collectors.toMap(Map.Entry::getKey,
                    Map.Entry::getValue));
    }

    @Override
    public void setResponseContentType(String s) {
        routingContext.response().headers().add("Content-Type", s);
    }

    @Override
    public String getServerName() {
        return serverName;
    }

    @Override
    public int getServerPort() {
        return serverPort;
    }

    @Override
    public String getScheme() {
        return scheme;
    }

    @Override
    public boolean isSecure() {
        return getScheme().equals("https");
    }

    @Override
    public String getFullRequestURL() {
        return fullUrl;
    }

    @Override
    public Collection<Cookie> getRequestCookies() {
        return routingContext.cookies().stream().map(cookie -> {
            final Cookie p4jCookie = new Cookie(cookie.getName(), cookie.getValue());
            p4jCookie.setDomain(cookie.getDomain());
            p4jCookie.setPath(cookie.getPath());
            return p4jCookie;
        }).collect(Collectors.toList());
    }

    @Override
    public void addResponseCookie(Cookie cookie) {
        routingContext.addCookie(io.vertx.ext.web.Cookie.cookie(cookie.getName(), cookie.getValue()));
    }

    @Override
    public String getPath() {
        return routingContext.request().path();
    }

    @Override
    public SessionStore getSessionStore() {
        return this.sessionStore;
    }

    public Pac4jUser getVertxUser() {
        return (Pac4jUser) routingContext.user();
    }

    public void removeVertxUser() {
        routingContext.clearUser();
    }

    public void setVertxUser(final Pac4jUser pac4jUser) {
        routingContext.setUser(pac4jUser);
    }

    public Session getVertxSession() {
        return routingContext.session();
    }

}

@@ -5933,100 +5933,713 @@
     
-routingContext.addCookie(io.vertx.ext.web.Cookie.cookie(cookie.getName(), cookie.getValue())
+io.vertx.ext.web.Cookie vertxCookie =  io.vertx.ext.web.Cookie.cookie(cookie.getName(), cookie.getValue())%0A                               .setHttpOnly(cookie.isHttpOnly())%0A                               .setSecure(cookie.isSecure())%0A                               .setDomain(cookie.getDomain())%0A                               .setPath(cookie.getPath());%0A        /*%0A        *  dont use the default of one of org.pac4j.core.context.Cookie which is -1 but allow the default one of%0A        *  io.vertx.ext.web.Cookie which uses netty Cookie internally%0A        * */%0A        if(cookie.getMaxAge()%3E0)%7B%0A            vertxCookie.setMaxAge(cookie.getMaxAge());%0A        %7D%0A%0A        routingContext.addCookie(vertxCookie
 );%0A 

set httpOnly to true in vertx-pac4j csrf token cookie (#125)