/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You 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.apache.openejb.server.httpd;

import org.apache.openejb.AppContext;
import org.apache.openejb.assembler.classic.OpenEjbConfiguration;
import org.apache.openejb.assembler.classic.ServiceInfo;
import org.apache.openejb.core.WebContext;
import org.apache.openejb.loader.SystemInstance;
import org.apache.openejb.spi.ContainerSystem;
import org.apache.openejb.util.LogCategory;
import org.apache.openejb.util.Logger;
import org.apache.openejb.util.PropertyPlaceHolderHelper;
import org.apache.webbeans.config.WebBeansContext;

import java.net.InetAddress;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.List;

public class OpenEJBHttpRegistry {
    public static final Logger log = Logger.getInstance(LogCategory.HTTPSERVER, OpenEJBHttpRegistry.class);

    protected final HttpListenerRegistry registry;
    protected final List<URI> baseUris = new ArrayList<>();

    public OpenEJBHttpRegistry() {
        try {
            OpenEjbConfiguration configuration = SystemInstance.get().getComponent(OpenEjbConfiguration.class);
            for (ServiceInfo service : configuration.facilities.services) {
                if (service.className.equals(HttpServerFactory.class.getName())) {
                    int port = Integer.parseInt(PropertyPlaceHolderHelper.simpleValue(service.properties.getProperty("port")));
                    String ip = service.properties.getProperty("bind");
                    if ("0.0.0.0".equals(ip)) {
                        InetAddress[] addresses = InetAddress.getAllByName(ip);
                        for (InetAddress address : addresses) {
                            baseUris.add(new URI("http", null, address.getHostAddress(), port, null, null, null));
                        }
                    } else {
                        baseUris.add(new URI("http", null, ip, port, null, null, null));
                    }
                    break;
                }
            }
        } catch (Exception e) {
            log.error("Unable to build base URIs for " + getClass().getSimpleName() + " registry", e);
        }
        registry = SystemInstance.get().getComponent(HttpListenerRegistry.class);
    }

    public HttpListener addWrappedHttpListener(HttpListener httpListener, ClassLoader classLoader, String regex) {
        HttpListener listener = new ClassLoaderHttpListener(httpListener, classLoader);
        registry.addHttpListener(listener, regex);
        return listener;
    }

    public List<String> getResolvedAddresses(String path) {
        String suffix = path;
        if (!path.startsWith("/")) {
            suffix = '/' + suffix;
        }

        List<String> addresses = new ArrayList<>();
        for (final URI baseUri : baseUris) {
            URI uri = baseUri;
            if (baseUri.getPort() == 0) { // if port was set to 0 we need to get httpejbd service port which was updated in SystemInstance
                final int port = Integer.parseInt(SystemInstance.get().getProperty("httpejbd.port", "0"));
                if (port != 0) {
                    try {
                        uri = new URI(baseUri.getScheme(), baseUri.getUserInfo(), baseUri.getHost(), port, baseUri.getPath(), baseUri.getQuery(), baseUri.getFragment());
                    } catch (final URISyntaxException e) {
                        // no-op
                    }
                }
            }
            final URI address = uri.resolve(suffix);
            addresses.add(address.toString());
        }
        return addresses;
    }

    protected static class ClassLoaderHttpListener implements HttpListener {
        private final HttpListener delegate;
        private final ClassLoader classLoader;

        protected ClassLoaderHttpListener(HttpListener delegate, ClassLoader classLoader) {
            this.delegate = delegate;
            this.classLoader = classLoader;
        }

        public void onMessage(HttpRequest request, HttpResponse response) throws Exception {
            final Thread thread = Thread.currentThread();
            final ClassLoader oldCl = thread.getContextClassLoader();

            WebBeansContext wbc = null;
            try {
                if (request instanceof HttpRequestImpl) {
                    final HttpRequestImpl httpRequest = HttpRequestImpl.class.cast(request);
                    final WebContext web = findWebContext(request.getURI() == null ? request.getContextPath() : request.getURI().getPath());
                    if (web != null) {
                        httpRequest.setApplication(web);

                        if (web.getClassLoader() != null) {
                            thread.setContextClassLoader(web.getClassLoader());
                        } else if (web.getAppContext().getClassLoader() != null) {
                            thread.setContextClassLoader(web.getAppContext().getClassLoader());
                        }

                        final String ctx = (web.getContextRoot().startsWith("/") ? "" : "/") + web.getContextRoot();
                        httpRequest.initPathFromContext(ctx);
                        wbc = web.getWebbeansContext() != null ? web.getWebbeansContext() : web.getAppContext().getWebBeansContext();
                    } else {
                        thread.setContextClassLoader(classLoader);

                        if (SystemInstance.isInitialized()) { // avoid to rely on default if we didnt init it and then create lazily a context
                            try { // surely an issue or something just tolerated for fake webapps
                                wbc = WebBeansContext.currentInstance();
                            } catch (final IllegalStateException ise) {
                                // no-op
                            }
                        }
                    }
                    if (wbc != null) {
                        httpRequest.setAttribute("openejb_owb_context", wbc);
                        initCdi(wbc, httpRequest).init();
                    }
                }

                delegate.onMessage(request, response);
            } finally {
                if (wbc != null) {
                    HttpRequestImpl.class.cast(request).destroy();
                }

                thread.setContextClassLoader(oldCl);
            }
        }

        private static WebContext findWebContext(final String path) { // TODO: this is a poor impl, would need registration of app etc to be better and have a rooter
            for (final AppContext app : SystemInstance.get().getComponent(ContainerSystem.class).getAppContexts()) {
                for (final WebContext web : app.getWebContexts()) {
                    if (path.startsWith(web.getContextRoot()) || path.startsWith('/' + web.getContextRoot())) {
                        return web;
                    }
                }
            }
            return null;
        }

        private static HttpRequestImpl initCdi(final WebBeansContext context, final HttpRequestImpl request) {
            try {
                if (context.getBeanManagerImpl().isInUse()) {
                    request.setBeginListener(new BeginWebBeansListener(context));
                    request.setEndListener(new EndWebBeansListener(context));
                }
            } catch (IllegalStateException ise) {
                // no-op: ignore
            }
            return request;
        }

        public HttpListener getDelegate() {
            return delegate;
        }
    }
}
