/*
 * Copyright 2013 Red Hat Inc. and/or its affiliates and other contributors.
 *
 * 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.
 */
 
package org.switchyard.component.http.endpoint;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.charset.Charset;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.http.NameValuePair;
import org.apache.http.client.utils.URLEncodedUtils;
import org.jboss.logging.Logger;
import org.jboss.com.sun.net.httpserver.BasicAuthenticator;
import org.jboss.com.sun.net.httpserver.HttpContext;
import org.jboss.com.sun.net.httpserver.HttpExchange;
import org.jboss.com.sun.net.httpserver.HttpHandler;
import org.jboss.com.sun.net.httpserver.HttpServer;
import org.switchyard.ServiceDomain;
import org.switchyard.component.common.Endpoint;
import org.switchyard.component.http.ContentType;
import org.switchyard.component.http.HttpLogger;
import org.switchyard.component.http.InboundHandler;
import org.switchyard.component.http.composer.HttpRequestBindingData;
import org.switchyard.component.http.composer.HttpRequestInfo;
import org.switchyard.component.http.composer.HttpResponseBindingData;
import org.switchyard.security.jboss.credential.extractor.HttpExchangeCredentialExtractor;

/**
 * Publishes standalone HTTP endpoint.
 * <p>
 *     By default it will be published in port {@value #DEFAULT_PORT}. This can be configured making use of
 *     <i>{@value #DEFAULT_PORT_PROPERTY}</i> system property.
 * </p>
 *
 * @author Magesh Kumar B <mageshbk@jboss.com> (C) 2012 Red Hat Inc.
 */
public class StandaloneEndpointPublisher implements EndpointPublisher {

    /**
     * Default port in which the standalone publisher is started.
     */
    public static final int DEFAULT_PORT = 8080;

    /**
     * System property to adjust the port in which the standalone publisher is started.
     */
    public static final String DEFAULT_PORT_PROPERTY = "org.switchyard.component.http.standalone.port";

    private static final Logger LOGGER = Logger.getLogger(StandaloneEndpointPublisher.class);

    private static final String CONTENT_TYPE = "Content-Type";

    // The global standalone HttpServer
    private static HttpServer _httpServer;

    static {
        try {
            _httpServer = HttpServer.create(new InetSocketAddress(getPort()), 10);
            _httpServer.setExecutor(null); // creates a default executor
            _httpServer.start();
        } catch (IOException ioe) {
            HttpLogger.ROOT_LOGGER.unableToLaunchStandaloneHttpServer(ioe);
        }
    }

    /**
     * {@inheritDoc}
     */
    public Endpoint publish(ServiceDomain domain, String context, InboundHandler handler) throws Exception {
        HttpContext httpContext = null;
        if (!context.startsWith("/")) {
            context = "/" + context;
        }
        if (_httpServer != null) {
            httpContext = _httpServer.createContext(context, new StandaloneHandler(handler));
        }
        return new StandaloneEndpoint(httpContext);
    }

    private static class StandaloneHandler implements HttpHandler {

        private InboundHandler _handler;

        public StandaloneHandler(InboundHandler handler) {
            _handler = handler;
        }

        public void handle(HttpExchange exchange) {
            try {
                HttpRequestBindingData httpRequest = new HttpRequestBindingData();
                byte[] responseBody = null;
                try {
                    String contentTypeStr = exchange.getRequestHeaders().getFirst(CONTENT_TYPE);
                    ContentType contentType = new ContentType(contentTypeStr);
                    httpRequest.setContentType(contentType);
                    httpRequest.setBodyFromStream(exchange.getRequestBody());
                    httpRequest.setHeaders(exchange.getRequestHeaders());
                    httpRequest.setRequestInfo(getRequestInfo(exchange, contentType));
                } catch (IOException e) {
                    HttpLogger.ROOT_LOGGER.unexpectedExceptionWhileReadingRequest(e);
                }
                HttpResponseBindingData httpResponse = _handler.invoke(httpRequest);
                try {
                    if (httpResponse != null) {
                        exchange.getResponseHeaders().putAll(httpResponse.getHeaders());
                        if (httpResponse.getBodyBytes() != null) {
                            exchange.sendResponseHeaders(httpResponse.getStatus(), httpResponse.getBodyBytes().available());
                            httpResponse.writeBodyToStream(exchange.getResponseBody());
                        } else {
                            if (httpResponse.getStatus() != null) {
                                exchange.sendResponseHeaders(httpResponse.getStatus(), 0);
                            } else {
                                exchange.sendResponseHeaders(HttpServletResponse.SC_ACCEPTED, 0);
                            }
                        }
                    } else {
                        exchange.sendResponseHeaders(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, 0);
                    }
                } catch (IOException e) {
                    HttpLogger.ROOT_LOGGER.unexpectedExceptionWhileWritingResponse(e);
                }
            } catch (Exception e) {
                HttpLogger.ROOT_LOGGER.unexpectedExceptionWhileHandlingHttpRequest(e);
            }
        }
    }

    /**
     * Method for get request information from a http exchange.
     *
     * @param request HttpExchange
     * @param type ContentType
     * @return Request information from a http exchange
     * @throws IOException when the request information could not be read
     */
    public static HttpRequestInfo getRequestInfo(HttpExchange request, ContentType type) throws IOException {
        HttpRequestInfo requestInfo = new HttpRequestInfo();

        if (request.getHttpContext().getAuthenticator() instanceof BasicAuthenticator) {
            requestInfo.setAuthType(HttpServletRequest.BASIC_AUTH);
        }
        URI u = request.getRequestURI();
        URI requestURI = null;
        try {
            requestURI = new URI(u.getScheme(), u.getUserInfo(), u.getHost(), u.getPort(), u.getPath(), null, null);
        } catch (URISyntaxException e) {
            // Strange that this could happen when copying from another URI.
            LOGGER.debug(e);
        }
        requestInfo.setCharacterEncoding(type.getCharset());
        requestInfo.setContentType(type.toString());
        requestInfo.setContextPath(request.getHttpContext().getPath());
        requestInfo.setLocalAddr(request.getLocalAddress().getAddress().getHostAddress());
        requestInfo.setLocalName(request.getLocalAddress().getAddress().getHostName());
        requestInfo.setMethod(request.getRequestMethod());
        requestInfo.setProtocol(request.getProtocol());
        requestInfo.setQueryString(u.getQuery());
        requestInfo.setRemoteAddr(request.getRemoteAddress().getAddress().getHostAddress());
        requestInfo.setRemoteHost(request.getRemoteAddress().getAddress().getHostName());
        if (request.getHttpContext().getAuthenticator() instanceof BasicAuthenticator) {
            requestInfo.setRemoteUser(request.getPrincipal().getUsername());
        }
        requestInfo.setContentLength(request.getRequestBody().available());
        // requestInfo.setRequestSessionId(request.getRequestedSessionId());
        if (requestURI != null) {
            requestInfo.setRequestURI(requestURI.toString());
        }
        requestInfo.setScheme(u.getScheme());
        requestInfo.setServerName(u.getHost());
        requestInfo.setRequestPath(u.getPath());

        // Http Query params...
        if (requestInfo.getQueryString() != null) {
            Charset charset = null;
            if (type.getCharset() != null) {
                try {
                    charset = Charset.forName(type.getCharset());
                } catch (Exception exception) {
                    LOGGER.debug(exception);
                }
            }
            for (NameValuePair nameValuePair : URLEncodedUtils.parse(requestInfo.getQueryString(), charset)) {
                requestInfo.addQueryParam(nameValuePair.getName(), nameValuePair.getValue());
            }
        }

        // Credentials...
        requestInfo.getCredentials().addAll(new HttpExchangeCredentialExtractor().extract(request));

        if (LOGGER.isTraceEnabled()) {
            LOGGER.trace(requestInfo);
        }

        return requestInfo;
    }

    /**
     * Returns the port where the standalone publisher will be started
     * @return the port
     */
    static int getPort() {
        int port = DEFAULT_PORT;
        final String portAsStr = System.getProperty(DEFAULT_PORT_PROPERTY);
        if (portAsStr != null) {
            port = Integer.parseInt(portAsStr);
        }
        return port;
    }
}
