package com.facebook.swift.exampleclient;

import com.facebook.swift.codec.guice.ThriftCodecModule;
import com.facebook.swift.fb303.Fb303;
import com.facebook.swift.service.ThriftClient;
import com.facebook.swift.service.ThriftClientManager;
import com.facebook.swift.service.guice.ThriftClientModule;
import com.google.common.base.Joiner;
import com.google.common.collect.ImmutableMap;
import com.google.inject.*;
import com.google.inject.name.Names;
import com.mycila.inject.jsr250.Jsr250;
import com.mycila.inject.jsr250.Jsr250Injector;
import io.airlift.bootstrap.LifeCycleManager;
import io.airlift.bootstrap.LifeCycleModule;
import io.airlift.configuration.ConfigurationFactory;
import io.airlift.configuration.ConfigurationModule;
import io.airlift.log.Logger;

import java.util.*;
import java.util.concurrent.ExecutionException;

import static com.facebook.swift.service.guice.ThriftClientBinder.thriftClientBinder;

public class App
{
    private static final Logger LOGGER = Logger.get(App.class);

    public static void main(String[] args)
            throws Exception
    {
        new App().run();
    }

    public void run() throws Exception
    {
        // Make a client call using various different client setup mechanisms
        plainClientExample();
        thriftClientFactoryExample();
        guiceLifecycleExample();
        jsr250InjectorExample();

        collectCounters();

        // Shutdown server
        shutdownServer();
    }

    /**
     * An example that manually creates a {@link ThriftClientManager} and from that manager
     * manually creates and connects the client.
     *
     * {@link ClientExampleHelper} is used only to make the actually thrift method calls
     */
    private static void plainClientExample() throws ExecutionException, InterruptedException
    {
        try (ThriftClientManager clientManager = new ThriftClientManager();
             ExampleService rawClient = clientManager.createClient(ClientExampleHelper.getConnector(),
                                                                    ExampleService.class).get())
        {
            ClientExampleHelper.executeWithClientAndInput(rawClient, "plainClientExample");
        }
    }

    /**
     * An example that manually creates a {@link ThriftClientManager} and a {@link ThriftClient}
     * but lets the {@link ClientExampleHelper} open the connection
     */
    private static void thriftClientFactoryExample() throws ExecutionException, InterruptedException
    {
        try (ThriftClientManager clientManager = new ThriftClientManager())
        {
            ThriftClient<ExampleService> thriftClient = new ThriftClient<>(clientManager, ExampleService.class);
            new ClientExampleHelper(thriftClient, "thriftClientFactoryExample").execute();
        }
    }

    /**
     * An example that uses the standard guice injector, and a {@link LifeCycleManager} to manage
     * lifetimes of injected instances.
     *
     * Guice configuration handles creation of the {@link ThriftClient}
     */
    private static void guiceLifecycleExample() throws Exception
    {
        ImmutableMap<String, String> configuration = ImmutableMap.of(
                "ExampleService.thrift.client.read-timeout", "1000ms"
        );
        Injector injector = Guice.createInjector(
                Stage.PRODUCTION,
                new LifeCycleModule(),
                new ConfigurationModule(new ConfigurationFactory(configuration)),
                new ThriftCodecModule(),
                new ThriftClientModule(),
                new Module()
                {
                    @Override
                    public void configure(Binder binder)
                    {
                        binder.bind(String.class)
                              .annotatedWith(Names.named("stringToProcess"))
                              .toInstance("guiceLifecycleExample");
                        thriftClientBinder(binder).bindThriftClient(ExampleService.class);
                    }
                });

        LifeCycleManager manager = injector.getInstance(LifeCycleManager.class);
        manager.start();

        try {
            ThriftClient<ExampleService> clientFactory =
                    injector.getInstance(Key.get(new TypeLiteral<ThriftClient<ExampleService>>() {}));
            String stringToProcess =
                    injector.getInstance(Key.get(String.class, Names.named("stringToProcess")));

            new ClientExampleHelper(clientFactory, stringToProcess).execute();
        }
        finally {
            manager.stop();
        }
    }

    /**
     * An example that uses the Mycila Guice injector (with JSR250 extensions for controlling
     * lifetimes of injected instances)
     *
     * Guice configuration handles creation of the {@link ThriftClient}
     */
    private static void jsr250InjectorExample() throws ExecutionException, InterruptedException
    {
        ImmutableMap<String, String> configuration = ImmutableMap.of(
                "ExampleService.thrift.client.read-timeout", "1000ms"
        );

        // In this case, no child injector is needed because Jsr250 handles post-construct
        // exceptions gracefully by running the @PreDestroy for any injected instances
        Jsr250Injector injector = Jsr250.createInjector(
                Stage.PRODUCTION,
                new ConfigurationModule(new ConfigurationFactory(configuration)),
                new ThriftCodecModule(),
                new ThriftClientModule(),
                new Module()
                {
                    @Override
                    public void configure(Binder binder)
                    {
                        binder.bind(String.class)
                              .annotatedWith(Names.named("stringToProcess"))
                              .toInstance("jsr250InjectorExample");
                        thriftClientBinder(binder).bindThriftClient(ExampleService.class);
                    }
                });

        ThriftClient<ExampleService> clientFactory =
                injector.getInstance(Key.get(new TypeLiteral<ThriftClient<ExampleService>>() {}));
        String stringToProcess =
                injector.getInstance(Key.get(String.class, Names.named("stringToProcess")));
        new ClientExampleHelper(clientFactory, stringToProcess).execute();

        injector.destroy();
    }

    private static void collectCounters() throws ExecutionException, InterruptedException
    {
        try (ThriftClientManager clientManager = new ThriftClientManager();
             Fb303 rawClient = clientManager.createClient(ClientExampleHelper.getConnector(), Fb303.class).get())
        {
            LOGGER.info("fb303 counters:\n{}", Joiner.on("\n").join(new TreeMap<>(rawClient.getCounters()).entrySet()));
        }
    }

    private static void shutdownServer() throws ExecutionException, InterruptedException
    {
        try (ThriftClientManager clientManager = new ThriftClientManager();
             Fb303 rawClient = clientManager.createClient(ClientExampleHelper.getConnector(), Fb303.class).get())
        {
            rawClient.shutdown();
        }
    }
}
