package your.microservice.core.util;

/*
 * Copyright 2002-2014 the original author or authors.
 *
 * 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.
 */

import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.util.UriComponentsBuilder;
import org.springframework.web.util.UrlPathHelper;
import org.springframework.web.util.WebUtils;

import javax.servlet.http.HttpServletRequest;

/**
 * A UriComponentsBuilder that extracts information from an HttpServletRequest.
 *
 * @author Rossen Stoyanchev
 * @since 3.1
 */
public class YourServletUriComponentsBuilder extends UriComponentsBuilder {

    private String servletRequestURI;


    /**
     * Default constructor. Protected to prevent direct instantiation.
     *
     * @see #fromContextPath(HttpServletRequest)
     * @see #fromServletMapping(HttpServletRequest)
     * @see #fromRequest(HttpServletRequest)
     * @see #fromCurrentContextPath()
     * @see #fromCurrentServletMapping()
     * @see #fromCurrentRequest()
     */
    protected YourServletUriComponentsBuilder() {
    }

    /**
     * Prepare a builder from the host, port, scheme, and context path of
     * an HttpServletRequest.
     * @param request request to build path from
     * @return the component builder created from the path
     */
    public static YourServletUriComponentsBuilder fromContextPath(HttpServletRequest request) {
        YourServletUriComponentsBuilder builder = fromRequest(request);
        builder.replacePath(request.getContextPath());
        builder.replaceQuery(null);
        return builder;
    }

    /**
     * Prepare a builder from the host, port, scheme, context path, and
     * servlet mapping of an HttpServletRequest. The results may vary depending
     * on the type of servlet mapping used.
     *
     * <p>If the servlet is mapped by name, e.g. {@code "/main/*"}, the path
     * will end with "/main". If the servlet is mapped otherwise, e.g.
     * {@code "/"} or {@code "*.do"}, the result will be the same as
     * if calling {@link #fromContextPath(HttpServletRequest)}.
     * @param request request to build path from
     * @return a URI component builder
     */
    public static YourServletUriComponentsBuilder fromServletMapping(HttpServletRequest request) {
        YourServletUriComponentsBuilder builder = fromContextPath(request);
        if (StringUtils.hasText(new UrlPathHelper().getPathWithinServletMapping(request))) {
            builder.path(request.getServletPath());
        }
        return builder;
    }

    /**
     * Prepare a builder from the host, port, scheme, and path of
     * an HttpServletRequest.
     * @param request to build path from
     * @return a URI component builder
     */
    public static YourServletUriComponentsBuilder fromRequestUri(HttpServletRequest request) {
        YourServletUriComponentsBuilder builder = fromRequest(request);
        builder.pathFromRequest(request);
        builder.replaceQuery(null);
        return builder;
    }

    /**
     * Prepare a builder by copying the scheme, host, port, path, and
     * query string of an HttpServletRequest.
     * @param request to build path from
     * @return a URI component builder
     */
    public static YourServletUriComponentsBuilder fromRequest(HttpServletRequest request) {
        String scheme = request.getScheme();
        String host = request.getServerName();
        int port = request.getServerPort();

        String hostHeader = request.getHeader("X-Forwarded-Host");
        if (StringUtils.hasText(hostHeader)) {
            String[] hosts = StringUtils.commaDelimitedListToStringArray(hostHeader);
            String hostToUse = hosts[0];
            if (hostToUse.contains(":")) {
                String[] hostAndPort = StringUtils.split(hostToUse, ":");
                host  = hostAndPort[0];
                port = Integer.parseInt(hostAndPort[1]);
            }
            else {
                host = hostToUse;
                port = -1;
            }
        }

        String portHeader = request.getHeader("X-Forwarded-Port");
        if (StringUtils.hasText(portHeader)) {
            port = Integer.parseInt(portHeader);
        }

        String protocolHeader = request.getHeader("X-Forwarded-Proto");
        if (StringUtils.hasText(protocolHeader)) {
            scheme = protocolHeader;
        }

        YourServletUriComponentsBuilder builder = new YourServletUriComponentsBuilder();
        builder.scheme(scheme);
        builder.host(host);
        if (scheme.equals("http") && port != 80 || scheme.equals("https") && port != 443) {
            builder.port(port);
        }
        builder.pathFromRequest(request);
        builder.query(request.getQueryString());
        return builder;
    }

    /**
     * Same as {@link #fromContextPath(HttpServletRequest)} except the
     * request is obtained through {@link RequestContextHolder}.
     * @return a URI component builder
     */
    public static YourServletUriComponentsBuilder fromCurrentContextPath() {
        return fromContextPath(getCurrentRequest());
    }

    /**
     * Same as {@link #fromServletMapping(HttpServletRequest)} except the
     * request is obtained through {@link RequestContextHolder}.
     * @return a URI component builder
     */
    public static YourServletUriComponentsBuilder fromCurrentServletMapping() {
        return fromServletMapping(getCurrentRequest());
    }

    /**
     * Same as {@link #fromRequestUri(HttpServletRequest)} except the
     * request is obtained through {@link RequestContextHolder}.
     * @return a URI component builder
     */
    public static YourServletUriComponentsBuilder fromCurrentRequestUri() {
        return fromRequestUri(getCurrentRequest());
    }

    /**
     * Same as {@link #fromRequest(HttpServletRequest)} except the
     * request is obtained through {@link RequestContextHolder}.
     * @return a URI component builder
     */
    public static YourServletUriComponentsBuilder fromCurrentRequest() {
        return fromRequest(getCurrentRequest());
    }

    /**
     * Obtain the request through {@link RequestContextHolder}.
     * @return the active servlet request
     */
    protected static HttpServletRequest getCurrentRequest() {
        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
        Assert.state(requestAttributes != null, "Could not find current request via RequestContextHolder");
        Assert.isInstanceOf(ServletRequestAttributes.class, requestAttributes);
        HttpServletRequest servletRequest = ((ServletRequestAttributes) requestAttributes).getRequest();
        Assert.state(servletRequest != null, "Could not find current HttpServletRequest");
        return servletRequest;
    }

    private void pathFromRequest(HttpServletRequest request) {
        this.servletRequestURI = request.getRequestURI();
        replacePath(request.getRequestURI());
    }

    /**
     * Removes any path extension from the {@link HttpServletRequest#getRequestURI()
     * requestURI}. This method must be invoked before any calls to {@link #path(String)}
     * or {@link #pathSegment(String...)}.
     * <pre>
     *  // GET http://foo.com/rest/books/6.json
     *
     *  YourServletUriComponentsBuilder builder = YourServletUriComponentsBuilder.fromRequestUri(this.request);
     *  String ext = builder.removePathExtension();
     *  String uri = builder.path("/pages/1.{ext}").buildAndExpand(ext).toUriString();
     *
     *  assertEquals("http://foo.com/rest/books/6/pages/1.json", result);
     * </pre>
     * @return the removed path extension for possible re-use, or {@code null}
     * @since 4.0
     */
    public String removePathExtension() {
        String extension = null;
        if (this.servletRequestURI != null) {
            String filename = WebUtils.extractFullFilenameFromUrlPath(this.servletRequestURI);
            extension = StringUtils.getFilenameExtension(filename);
            if (!StringUtils.isEmpty(extension)) {
                int end = this.servletRequestURI.length() - extension.length() + 1;
                replacePath(this.servletRequestURI.substring(0, end));
            }
            this.servletRequestURI = null;
        }
        return extension;
    }

}