package com.airbnb.airpal;

import com.airbnb.airpal.core.AirpalUserFactory;
import com.airbnb.airpal.core.health.PrestoHealthCheck;
import com.airbnb.airpal.resources.ExecuteResource;
import com.airbnb.airpal.resources.FilesResource;
import com.airbnb.airpal.resources.HealthResource;
import com.airbnb.airpal.resources.PingResource;
import com.airbnb.airpal.resources.QueriesResource;
import com.airbnb.airpal.resources.QueryResource;
import com.airbnb.airpal.resources.ResultsPreviewResource;
import com.airbnb.airpal.resources.S3FilesResource;
import com.airbnb.airpal.resources.SessionResource;
import com.airbnb.airpal.resources.TablesResource;
import com.airbnb.airpal.resources.UserResource;
import com.airbnb.airpal.resources.UsersResource;
import com.airbnb.airpal.resources.sse.SSEEventSourceServlet;
import com.google.inject.AbstractModule;
import com.google.inject.Guice;
import com.google.inject.Injector;
import com.google.inject.Stage;
import io.dropwizard.Application;
import io.dropwizard.Bundle;
import io.dropwizard.ConfiguredBundle;
import io.dropwizard.assets.AssetsBundle;
import io.dropwizard.db.DataSourceFactory;
import io.dropwizard.flyway.FlywayBundle;
import io.dropwizard.flyway.FlywayFactory;
import io.dropwizard.jetty.BiDiGzipHandler;
import io.dropwizard.setup.Bootstrap;
import io.dropwizard.setup.Environment;
import io.dropwizard.views.ViewBundle;
import org.eclipse.jetty.server.Handler;

import javax.servlet.ServletRegistration;

import java.util.Arrays;

import static org.glassfish.jersey.message.MessageProperties.IO_BUFFER_SIZE;

public abstract class AirpalApplicationBase<T extends AirpalConfiguration>
        extends Application<T>
{
    private static final String SERVER_SENT_EVENTS = "text/event-stream";

    protected Injector injector;

    @Override
    public void initialize(Bootstrap<T> bootstrap)
    {
        for (ConfiguredBundle<T> configuredBundle : getConfiguredBundles()) {
            bootstrap.addBundle(configuredBundle);
        }
        for (Bundle bundle : getBundles()) {
            bootstrap.addBundle(bundle);
        }
    }

    public abstract Iterable<AbstractModule> getModules(T config, Environment environment);

    public Iterable<ConfiguredBundle<T>> getConfiguredBundles()
    {
        return Arrays.asList(new ViewBundle());
    }

    public Iterable<Bundle> getBundles()
    {
        return Arrays.asList(
                new AssetsBundle("/assets", "/app", "index.html"),
                new FlywayBundle<T>()
                {
                    @Override
                    public DataSourceFactory getDataSourceFactory(T configuration)
                    {
                        return configuration.getDataSourceFactory();
                    }

                    @Override
                    public FlywayFactory getFlywayFactory(T configuration)
                    {
                        return configuration.getFlywayFactory();
                    }
                });
    }

    @Override
    public void run(T config, Environment environment)
            throws Exception
    {
        this.injector = Guice.createInjector(Stage.PRODUCTION, getModules(config, environment));

        System.setProperty(IO_BUFFER_SIZE, String.valueOf(config.getBufferSize().toBytes()));

        environment.healthChecks().register("presto", injector.getInstance(PrestoHealthCheck.class));

        environment.jersey().register(injector.getInstance(ExecuteResource.class));
        environment.jersey().register(injector.getInstance(QueryResource.class));
        environment.jersey().register(injector.getInstance(QueriesResource.class));
        environment.jersey().register(injector.getInstance(UserResource.class));
        environment.jersey().register(injector.getInstance(UsersResource.class));
        environment.jersey().register(injector.getInstance(TablesResource.class));
        environment.jersey().register(injector.getInstance(HealthResource.class));
        environment.jersey().register(injector.getInstance(PingResource.class));
        environment.jersey().register(injector.getInstance(SessionResource.class));
        environment.jersey().register(injector.getInstance(FilesResource.class));
        environment.jersey().register(injector.getInstance(ResultsPreviewResource.class));
        environment.jersey().register(injector.getInstance(S3FilesResource.class));

        environment.jersey().register(injector.getInstance(AirpalUserFactory.class));

        // Setup SSE (Server Sent Events)
        ServletRegistration.Dynamic sseServlet = environment.servlets()
                .addServlet("updates", injector.getInstance(SSEEventSourceServlet.class));
        sseServlet.setAsyncSupported(true);
        sseServlet.addMapping("/api/updates/subscribe");

        // Disable GZIP content encoding for SSE endpoints
        environment.lifecycle().addServerLifecycleListener(server -> {
            for (Handler handler : server.getChildHandlersByClass(BiDiGzipHandler.class)) {
                ((BiDiGzipHandler) handler).addExcludedMimeTypes(SERVER_SENT_EVENTS);
            }
        });
    }
}