package org.carrot2.elasticsearch;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.carrot2.clustering.ClusteringAlgorithmProvider;
import org.carrot2.elasticsearch.ClusteringAction.TransportClusteringAction;
import org.carrot2.language.LanguageComponentsProvider;
import org.elasticsearch.action.ActionRequest;
import org.elasticsearch.action.ActionResponse;
import org.elasticsearch.client.Client;
import org.elasticsearch.client.transport.TransportClient;
import org.elasticsearch.cluster.metadata.IndexNameExpressionResolver;
import org.elasticsearch.cluster.node.DiscoveryNodes;
import org.elasticsearch.cluster.service.ClusterService;
import org.elasticsearch.common.io.stream.NamedWriteableRegistry;
import org.elasticsearch.common.settings.ClusterSettings;
import org.elasticsearch.common.settings.IndexScopedSettings;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.settings.SettingsFilter;
import org.elasticsearch.common.xcontent.NamedXContentRegistry;
import org.elasticsearch.env.Environment;
import org.elasticsearch.env.NodeEnvironment;
import org.elasticsearch.plugins.ActionPlugin;
import org.elasticsearch.plugins.ExtensiblePlugin;
import org.elasticsearch.plugins.Plugin;
import org.elasticsearch.rest.RestController;
import org.elasticsearch.rest.RestHandler;
import org.elasticsearch.script.ScriptService;
import org.elasticsearch.threadpool.ThreadPool;
import org.elasticsearch.watcher.ResourceWatcherService;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.ServiceLoader;
import java.util.function.Supplier;

public class ClusteringPlugin extends Plugin implements ExtensiblePlugin, ActionPlugin {
   /**
    * Master on/off switch property for the plugin (general settings).
    */
   public static final String DEFAULT_ENABLED_PROPERTY_NAME = "carrot2.enabled";

   /**
    * Plugin name.
    */
   public static final String PLUGIN_NAME = "elasticsearch-carrot2";

   /**
    * A property key holding
    * the default location of additional resources (stopwords, etc.) for
    * algorithms. The location is resolved relative to <code>es/conf</code>
    * but can be absolute. By default it is <code>.</code>.
    */
   public static final String DEFAULT_RESOURCES_PROPERTY_NAME = "resources";

   /**
    * All algorithm providers.
    */
   private LinkedHashMap<String, ClusteringAlgorithmProvider> algorithmProviders = new LinkedHashMap<>();

   /**
    * All language component providers.
    */
   private Map<String, List<LanguageComponentsProvider>> languageComponentProviders = new LinkedHashMap<>();

   private final boolean transportClient;
   private final boolean pluginEnabled;
   private final Logger logger;

   public ClusteringPlugin(Settings settings) {
      this.pluginEnabled = settings.getAsBoolean(DEFAULT_ENABLED_PROPERTY_NAME, true);
      this.transportClient = TransportClient.CLIENT_TYPE.equals(Client.CLIENT_TYPE_SETTING_S.get(settings));
      this.logger = LogManager.getLogger("plugin.carrot2");

      // Invoke loadSPI on our own class loader to load default algorithms.
      reloadSPI(getClass().getClassLoader());
   }

   @Override
   public List<ActionHandler<? extends ActionRequest, ? extends ActionResponse>> getActions() {
      if (pluginEnabled) {
         return Arrays.asList(
             new ActionHandler<>(ClusteringAction.INSTANCE, TransportClusteringAction.class),
             new ActionHandler<>(ListAlgorithmsAction.INSTANCE, ListAlgorithmsAction.TransportListAlgorithmsAction.class));
      }
      return Collections.emptyList();
   }

   @Override
   public List<RestHandler> getRestHandlers(Settings settings, RestController restController,
                                            ClusterSettings clusterSettings,
                                            IndexScopedSettings indexScopedSettings, SettingsFilter settingsFilter,
                                            IndexNameExpressionResolver indexNameExpressionResolver,
                                            Supplier<DiscoveryNodes> nodesInCluster) {
      return Arrays.asList(
          new ClusteringAction.RestClusteringAction(restController),
          new ListAlgorithmsAction.RestListAlgorithmsAction(restController));
   }

   @Override
   public Collection<Object> createComponents(Client client, ClusterService clusterService,
                                              ThreadPool threadPool, ResourceWatcherService resourceWatcherService,
                                              ScriptService scriptService, NamedXContentRegistry xContentRegistry,
                                              Environment environment, NodeEnvironment nodeEnvironment,
                                              NamedWriteableRegistry namedWriteableRegistry) {
      List<Object> components = new ArrayList<>();
      if (pluginEnabled && !transportClient) {
         components.add(new ClusteringContext(environment,
             new LinkedHashMap<>(algorithmProviders),
             new LinkedHashMap<>(languageComponentProviders)));
      }
      return components;
   }

   @Override
   public void reloadSPI(ClassLoader loader) {
      ServiceLoader.load(ClusteringAlgorithmProvider.class, loader).forEach((provider) -> {
         String name = provider.name();
         if (algorithmProviders.containsKey(name)) {
            throw new RuntimeException("More than one provider for algorithm " + name + "?");
         }
         algorithmProviders.put(name, provider);
      });

      for (LanguageComponentsProvider provider :
          ServiceLoader.load(LanguageComponentsProvider.class, loader)) {
         for (String lang : provider.languages()) {
            languageComponentProviders
                .computeIfAbsent(lang, (k) -> new ArrayList<>())
                .add(provider);
         }
      }
   }
}

@@ -5054,24 +5054,25 @@
 rtClient) %7B%0A
+%0A
          com
@@ -5131,35 +5131,33 @@
         
-new LinkedHashMap%3C%3E
+reorderAlgorithms
 (algorit
@@ -5234,32 +5234,32 @@
 ers)));%0A      %7D%0A
-
       return com
@@ -5265,32 +5265,815 @@
 mponents;%0A   %7D%0A%0A
+   /**%0A    * This places Lingo3G in front of the algorithm list if it is available.%0A    */%0A   private LinkedHashMap%3CString, ClusteringAlgorithmProvider%3E reorderAlgorithms(%0A       LinkedHashMap%3CString, ClusteringAlgorithmProvider%3E providers) %7B%0A      String %5B%5D desiredOrder = %7B%0A          %22Lingo3G%22,%0A          %22Lingo%22,%0A          %22STC%22,%0A          %22Bisecting K-Means%22%0A      %7D;%0A      LinkedHashMap%3CString, ClusteringAlgorithmProvider%3E copy = new LinkedHashMap%3C%3E();%0A      for (String name  : desiredOrder) %7B%0A         if (providers.containsKey(name)) %7B%0A            copy.put(name, providers.get(name));%0A         %7D%0A      %7D%0A      providers.forEach((name, provider) -%3E %7B%0A         if (!copy.containsKey(name)) %7B%0A            copy.put(name, provider);%0A         %7D%0A      %7D);%0A      return copy;%0A   %7D%0A%0A
    @Override%0A   

Reorder algorithms so that we're not sensitive to SPI calls.