package com.airlock.iam.plugin.configeditor.field;

import com.airlock.iam.configeditor.shared.domain.IamModuleConfigRoot;
import com.airlock.iam.plugin.api.config.ConfigurationEnvironment;
import com.airlock.iam.plugin.api.exception.InitializeException;
import com.airlock.iam.plugin.configeditor.util.GuiResources;
import com.airlock.iam.plugin.framework.application.configuration.MutablePluginConfiguration;
import com.airlock.iam.plugin.framework.misc.config.AllPropertiesWalkerScope;
import com.airlock.iam.plugin.framework.misc.config.PluginConfigurationDictionary;
import com.airlock.iam.plugin.framework.misc.config.SubtreeWalker;
import com.airlock.iam.plugin.framework.misc.ioc.PluginLoader;
import com.google.common.annotations.VisibleForTesting;
import com.vaadin.v7.data.Container;
import com.vaadin.v7.data.util.IndexedContainer;
import com.vaadin.v7.ui.ComboBox;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.function.Predicate;
import java.util.stream.Stream;

public class SelectablePluginConfigurations {
   private final List<SelectablePluginConfigurations.PluginConfigurationItem> list = new ArrayList<>();
   private final Set<MutablePluginConfiguration> selectableConfigs;

   public SelectablePluginConfigurations(
      MutablePluginConfiguration target, Class<?> baseClass, PluginConfigurationDictionary dictionary, ConfigurationEnvironment env
   ) throws InitializeException {
      this.selectableConfigs = dictionary.getAssignablePluginConfiguration(baseClass);
      this.removeUnsuitableConfigurations(target, dictionary, this.selectableConfigs);
      this.addItemsToContainer(target, createSortedItemsFromAssignablePlugins(this.selectableConfigs), env);
   }

   public void fillComboBoxWithDefaultPlugin(ComboBox comboBox, Class<?> defaultValue) {
      this.fillComboBox(comboBox);
      this.addDefaultValueAsNullSelectionItem(comboBox, defaultValue);
   }

   private void addDefaultValueAsNullSelectionItem(ComboBox comboBox, Class<?> defaultValue) {
      if (defaultValue != Object.class) {
         this.addNullSelection(comboBox, PluginLoader.getDisplayName(defaultValue) + " - DEFAULT");
      }
   }

   public void fillComboBoxWithGlobalPluginReference(ComboBox comboBox) {
      this.fillComboBox(comboBox);
      this.addGlobalPluginReferenceAsNullSelectionItem(comboBox);
   }

   private void addGlobalPluginReferenceAsNullSelectionItem(ComboBox comboBox) {
      MutablePluginConfiguration lastGlobalPC = null;

      for (SelectablePluginConfigurations.PluginConfigurationItem item : this.list) {
         MutablePluginConfiguration pluginConfiguration = item.getPluginConfiguration();
         if (item.isGlobal()) {
            lastGlobalPC = pluginConfiguration;
         }
      }

      if (lastGlobalPC != null) {
         this.addNullSelection(comboBox, lastGlobalPC);
         comboBox.setItemIcon(new NullSelectionItemWrapper(lastGlobalPC), GuiResources.ICON_GLOBAL_REFERENCE);
      }
   }

   public void fillComboBox(ComboBox comboBox) {
      Stream<MutablePluginConfiguration> confs = this.list
         .stream()
         .filter(Predicate.not(item -> item.isGlobal() && item.getPluginConfiguration().isSynthetic()))
         .map(SelectablePluginConfigurations.PluginConfigurationItem::getPluginConfiguration);
      Stream<NullSelectionItemWrapper> nullSelectionItemId = Stream.of(comboBox.getNullSelectionItemId())
         .filter(NullSelectionItemWrapper.class::isInstance)
         .map(NullSelectionItemWrapper.class::cast);
      IndexedContainer container = new IndexedContainer(Stream.concat(nullSelectionItemId, confs).toList());
      comboBox.setContainerDataSource(container);
   }

   public void removePluginsWithoutDisplayName(ComboBox comboBox) {
      Container containerDataSource = comboBox.getContainerDataSource();

      for (SelectablePluginConfigurations.PluginConfigurationItem pluginConfigurationItem : this.list) {
         MutablePluginConfiguration pluginConfiguration = pluginConfigurationItem.getPluginConfiguration();
         if (containerDataSource.containsId(pluginConfiguration) && !pluginConfiguration.hasPluginDisplayName()) {
            containerDataSource.removeItem(pluginConfiguration);
         }
      }
   }

   private void addNullSelection(ComboBox comboBox, Object nullItem) {
      NullSelectionItemWrapper nullItemWrapper = new NullSelectionItemWrapper(nullItem);
      IndexedContainer container = (IndexedContainer)comboBox.getContainerDataSource();
      container.addItemAt(0, nullItemWrapper);
      comboBox.setTextInputAllowed(false);
      comboBox.setNullSelectionItemId(nullItemWrapper);
   }

   public boolean hasGlobalReference() {
      return this.list.stream().anyMatch(SelectablePluginConfigurations.PluginConfigurationItem::isGlobal);
   }

   private void addItemsToContainer(MutablePluginConfiguration target, List<MutablePluginConfiguration> configItems, ConfigurationEnvironment env) throws InitializeException {
      for (MutablePluginConfiguration item : configItems) {
         if (target != null) {
            this.addItem(target, item, env);
         }
      }
   }

   private void addItem(MutablePluginConfiguration target, MutablePluginConfiguration item, ConfigurationEnvironment env) throws InitializeException {
      if (isGlobalReference(target, item, env)) {
         this.list.add(new SelectablePluginConfigurations.PluginConfigurationItem(item, true));
      } else if (!item.isSynthetic()) {
         this.list.add(new SelectablePluginConfigurations.PluginConfigurationItem(item, false));
      }
   }

   private static boolean isGlobalReference(MutablePluginConfiguration target, MutablePluginConfiguration item, ConfigurationEnvironment env) throws InitializeException {
      return target.hasGlobalReference() && item.isGlobalConfiguration(env);
   }

   private void removeUnsuitableConfigurations(
      MutablePluginConfiguration target, PluginConfigurationDictionary dict, Set<MutablePluginConfiguration> pluginConfigs
   ) {
      pluginConfigs.remove(target);
      this.removeChildPluginConfigurationsFromParentSelect(target, dict, pluginConfigs);
      this.removeCompositionalParentConfigurations(target, pluginConfigs);
   }

   private void removeChildPluginConfigurationsFromParentSelect(
      MutablePluginConfiguration target, PluginConfigurationDictionary dict, Set<MutablePluginConfiguration> pluginConfigs
   ) {
      if (target != null) {
         for (MutablePluginConfiguration pc : dict.getChildren(target)) {
            this.removeUnsuitableConfigurations(pc, dict, pluginConfigs);
         }
      }
   }

   private void removeCompositionalParentConfigurations(MutablePluginConfiguration target, Set<MutablePluginConfiguration> pluginConfigs) {
      if (target != null) {
         AllPropertiesWalkerScope propertiesScope = new AllPropertiesWalkerScope();
         Iterator<MutablePluginConfiguration> pluginConfigsIterator = pluginConfigs.iterator();

         while (pluginConfigsIterator.hasNext()) {
            MutablePluginConfiguration pluginConfig = pluginConfigsIterator.next();
            SubtreeWalker treeWalker = new SubtreeWalker(pluginConfig, propertiesScope);

            while (treeWalker.hasNext()) {
               if (treeWalker.next() == target) {
                  pluginConfigsIterator.remove();
               }
            }
         }
      }
   }

   private static List<MutablePluginConfiguration> createSortedItemsFromAssignablePlugins(Set<MutablePluginConfiguration> pluginConfigs) {
      return pluginConfigs.stream()
         .filter(pc -> !IamModuleConfigRoot.isModuleTreeRoot(pc))
         .sorted(new SelectablePluginConfigurations.SelectablePluginSorter())
         .toList();
   }

   @VisibleForTesting
   Set<MutablePluginConfiguration> getSelectableConfigurations() {
      return this.selectableConfigs;
   }

   private static class PluginConfigurationItem {
      private final MutablePluginConfiguration pluginConfiguration;
      private final boolean global;

      public PluginConfigurationItem(MutablePluginConfiguration pluginConfiguration, boolean global) {
         this.pluginConfiguration = pluginConfiguration;
         this.global = global;
      }

      public MutablePluginConfiguration getPluginConfiguration() {
         return this.pluginConfiguration;
      }

      public boolean isGlobal() {
         return this.global;
      }
   }

   private static final class SelectablePluginSorter implements Comparator<MutablePluginConfiguration> {
      public int compare(MutablePluginConfiguration o1, MutablePluginConfiguration o2) {
         if (o1 == o2) {
            return 0;
         } else {
            return o1 != null && o2 != null ? String.CASE_INSENSITIVE_ORDER.compare(o1.toString(), o2.toString()) : -1;
         }
      }
   }
}
