package io.digdag.guice.rs;

import java.util.Map;
import java.util.Set;
import java.io.IOException;
import com.google.inject.Guice;
import com.google.inject.Injector;
import com.google.inject.Module;
import com.google.inject.TypeLiteral;
import com.google.common.base.Predicates;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import javax.servlet.ServletContainerInitializer;
import javax.servlet.ServletContext;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.ServletException;
import javax.servlet.ServletRegistration;
import javax.servlet.annotation.HandlesTypes;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@HandlesTypes(GuiceRsBootstrap.class)
public class GuiceRsServletContainerInitializer
        implements ServletContainerInitializer
{
    @Override
    @SuppressWarnings("unchecked")
    public void onStartup(Set<Class<?>> classes, ServletContext ctx)
    {
        ctx.setAttribute(GuiceRsServletContainerInitializer.class.getName(), this);

        for (Class<?> bootstrap : classes) {
            if (GuiceRsBootstrap.class.isAssignableFrom(bootstrap)) {
                processBootstrap((Class<GuiceRsBootstrap>) bootstrap, ctx);
            }
            else {
                // TODO show warnings
            }
        }
    }

    private void processBootstrap(final Class<GuiceRsBootstrap> bootstrap, final ServletContext context)
    {
        ImmutableList.Builder<Module> modules = ImmutableList.builder();
        Module module;

        module = GuiceRsServerControlModule.fromInitParameter(context);
        if (module != null) {
            modules.add(module);
        }

        modules.add((binder) -> {
            binder.bind(GuiceRsBootstrap.class).to(bootstrap).asEagerSingleton();
            binder.bind(ServletContext.class).toInstance(context);
        });

        final Injector injector = Guice.createInjector(modules.build())
            .getInstance(GuiceRsBootstrap.class)
            .initialize(context);

        if (injector instanceof AutoCloseable) {
            context.addListener(new CloseableInjectorDestroyListener((AutoCloseable) injector));
        }

        injector.findBindingsByType(TypeLiteral.get(GuiceRsServletInitializer.class))
            .stream()
            .map(binding -> binding.getProvider().get())
            .forEach(initializer -> initializer.register(injector, context));

        // return 404 Not Found if all URL patterns don't match
        context.addServlet("default", new DefaultServlet())
            .addMapping("/");
    }

    private static class DefaultServlet extends HttpServlet
    {
        @Override
        protected void service(HttpServletRequest request, HttpServletResponse response)
                throws ServletException, IOException
        {
            response.sendError(HttpServletResponse.SC_NOT_FOUND);
        }
    }

    private static class CloseableInjectorDestroyListener
            implements ServletContextListener
    {
        private final AutoCloseable closeable;

        public CloseableInjectorDestroyListener(AutoCloseable closeable)
        {
            this.closeable = closeable;
        }

        @Override
        public void contextDestroyed(ServletContextEvent sce)
        {
            try {
                closeable.close();
            }
            catch (Exception exception) {
                throw new RuntimeException(exception);
            }
        }

        @Override
        public void contextInitialized(ServletContextEvent sce)
        { }
    }
}