package com.airlock.iam.plugin.configeditor.ui.dialog;

import com.airlock.iam.elog.api.ELogger;
import com.airlock.iam.elog.api.ELoggerFactory;
import com.airlock.iam.elog.api.LogTypes;
import com.airlock.iam.plugin.api.annotation.config.DeprecatedPlugin;
import com.airlock.iam.plugin.api.exception.ConfigurationException;
import com.airlock.iam.plugin.api.exception.InitializeException;
import com.airlock.iam.plugin.configeditor.ConfigEditorApplication;
import com.airlock.iam.plugin.configeditor.field.PluginComboBox;
import com.airlock.iam.plugin.configeditor.session.ConfigEditorSessionAccessor;
import com.airlock.iam.plugin.configeditor.util.PluginDocumentation;
import com.airlock.iam.plugin.configeditor.util.VaadinHelper;
import com.airlock.iam.plugin.framework.application.configuration.DefaultPluginConfiguration;
import com.airlock.iam.plugin.framework.application.configuration.MutablePluginConfiguration;
import com.airlock.iam.plugin.framework.misc.config.PluginConfigurationDictionary;
import com.airlock.iam.plugin.framework.misc.ioc.PluginDictionary;
import com.airlock.iam.plugin.framework.misc.ioc.PluginDictionaryLoader;
import com.airlock.iam.plugin.framework.misc.ioc.PluginLoader;
import com.google.common.base.Preconditions;
import com.google.common.collect.Lists;
import com.vaadin.server.Sizeable.Unit;
import com.vaadin.shared.ui.ContentMode;
import com.vaadin.ui.Alignment;
import com.vaadin.ui.Button;
import com.vaadin.ui.HorizontalLayout;
import com.vaadin.ui.Notification;
import com.vaadin.ui.UI;
import com.vaadin.ui.VerticalLayout;
import com.vaadin.ui.Window;
import com.vaadin.ui.Button.ClickEvent;
import com.vaadin.ui.Notification.Type;
import com.vaadin.v7.data.Item;
import com.vaadin.v7.data.Property.ReadOnlyException;
import com.vaadin.v7.data.Property.ValueChangeEvent;
import com.vaadin.v7.data.Property.ValueChangeListener;
import com.vaadin.v7.data.util.DefaultItemSorter;
import com.vaadin.v7.data.util.IndexedContainer;
import com.vaadin.v7.data.util.converter.Converter.ConversionException;
import com.vaadin.v7.shared.ui.combobox.FilteringMode;
import com.vaadin.v7.ui.CheckBox;
import com.vaadin.v7.ui.ComboBox;
import com.vaadin.v7.ui.TextField;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Locale;
import java.util.Objects;
import java.util.Set;
import org.apache.commons.lang3.StringUtils;

public class NewPluginDialog extends Window {
   private static final ELogger LOG = ELoggerFactory.getLogger(NewPluginDialog.class);
   private static final String STYLE_DEPRECATED = "deprecated";
   private static final String STYLE_PRIMARY = "primary";
   private final TextField displayNameField;
   private final PluginConfigurationDictionary configDict;
   private final PluginDictionary pluginDict;
   private final List<Class<?>> deprecatedPlugins;
   private final List<Class<?>> unlicensedPlugins;
   private final ComboBox pluginClassSelect;
   private final PluginComboBox parentConfiguration;
   private final Button createButton;
   private final Button createAndEditButton;
   private final CheckBox showDeprecatedPlugins;
   private final CheckBox showUnlicensedPlugins;
   private MutablePluginConfiguration pc;
   private boolean ignoreClassValueChange;
   private boolean editAfterCreatingPlugin;
   private final MutablePluginConfiguration target;
   private final Class<?> basePluginType;

   @SuppressFBWarnings(value = "MC_OVERRIDABLE_METHOD_CALL_IN_CONSTRUCTOR", justification = "See ALIAM-15315")
   public NewPluginDialog(MutablePluginConfiguration target, MutablePluginConfiguration fixedParent, Class<?> basePluginType) throws InitializeException {
      this.target = target;
      this.basePluginType = basePluginType;
      this.pluginDict = ConfigEditorSessionAccessor.resolve(PluginDictionaryLoader.class).getPluginDictionary();
      this.configDict = this.getConfigEditorApplication().getConfigurationDictionary();
      this.setProperties();
      VerticalLayout vl = new VerticalLayout();
      this.displayNameField = new TextField("Display Name");
      this.displayNameField.setWidth("100%");
      this.displayNameField.setId("newPluginIdentifier");
      vl.addComponent(this.displayNameField);
      this.deprecatedPlugins = new ArrayList<>();
      this.unlicensedPlugins = new ArrayList<>();
      this.pluginClassSelect = this.createPluginClassSelect();
      this.pluginClassSelect.setId("newPluginClassField");
      this.initAssignablePluginClasses();
      this.handlePluginClass(fixedParent);
      this.parentConfiguration = this.createParentSelect();
      this.parentConfiguration.setId("newPluginParentField");
      this.parentConfiguration.initSelectablePlugins(target, basePluginType, this.getConfigEditorApplication().getSelectedConfigurationEnvironment());
      this.handleParentConfiguration(fixedParent);
      this.showDeprecatedPlugins = this.createShowDeprecatedCheckbox();
      this.showUnlicensedPlugins = this.createShowUnlicensedCheckbox();
      vl.addComponent(this.pluginClassSelect);
      vl.addComponent(this.parentConfiguration);
      vl.addComponent(this.showDeprecatedPlugins);
      vl.addComponent(this.showUnlicensedPlugins);
      HorizontalLayout buttonLayout = new HorizontalLayout();
      buttonLayout.setWidth(100.0F, Unit.PERCENTAGE);
      this.createAndEditButton = new Button("Create & Edit");
      this.createAndEditButton.setId("createAndEditPlugin");
      this.createAndEditButton.addClickListener(this::createButtonClick);
      this.createAndEditButton.setClickShortcut(13, new int[0]);
      this.createAndEditButton.addStyleName("primary");
      this.createButton = new Button("Create");
      this.createButton.setId("createPlugin");
      this.createButton.addClickListener(this::createButtonClick);
      this.createButton.setClickShortcut(13, new int[]{16});
      Button cancelButton = new Button("Cancel");
      cancelButton.setId("cancelButton");
      cancelButton.addClickListener(this::cancelButtonClick);
      buttonLayout.addComponent(cancelButton);
      buttonLayout.addComponent(this.createButton);
      buttonLayout.addComponent(this.createAndEditButton);
      buttonLayout.setComponentAlignment(this.createAndEditButton, Alignment.MIDDLE_RIGHT);
      buttonLayout.setComponentAlignment(this.createButton, Alignment.MIDDLE_RIGHT);
      vl.addComponent(buttonLayout);
      this.setContent(vl);
      this.displayNameField.focus();
   }

   public void cancelButtonClick(ClickEvent event) {
      UI.getCurrent().removeWindow(this);
   }

   public void createButtonClick(ClickEvent event) {
      this.editAfterCreatingPlugin = event.getSource() == this.createAndEditButton;
      String displayNameValue = (String)this.displayNameField.getValue();
      if (StringUtils.isEmpty(displayNameValue)) {
         displayNameValue = null;
      }

      if (this.displayNameField.isRequired() && displayNameValue == null) {
         Notification.show("A display name is required.", Type.HUMANIZED_MESSAGE);
      } else {
         Class<?> pluginClassValue = (Class<?>)this.pluginClassSelect.getValue();
         if (this.getSelectedParent() == null && pluginClassValue == null) {
            Notification.show("Please specify a parent or a plugin class.", Type.HUMANIZED_MESSAGE);
         } else if (displayNameValue != null && this.configDict.displayNameExists(displayNameValue)) {
            Notification.show("The display name \"" + displayNameValue + "\" is already in use.", Type.HUMANIZED_MESSAGE);
         } else {
            if (pluginClassValue != null) {
               this.createPluginConfiguration(displayNameValue, pluginClassValue);
            }

            UI.getCurrent().removeWindow(this);
         }
      }
   }

   private void createPluginConfiguration(String identifierValue, Class<?> pluginClassValue) {
      this.pc = new DefaultPluginConfiguration(identifierValue, pluginClassValue.getName());
      MutablePluginConfiguration parentPc = this.getSelectedParent();
      if (parentPc != null) {
         try {
            this.pc.setParentConfiguration(parentPc);
         } catch (ConfigurationException e) {
            LOG.error(LogTypes.CONFIGURATION_ERROR, "ConfigurationException: " + e.getMessage(), e);
            Notification.show("ConfigurationException: " + e.getMessage(), Type.ERROR_MESSAGE);
         }
      }
   }

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

   private MutablePluginConfiguration getSelectedParent() {
      return (MutablePluginConfiguration)this.parentConfiguration.getValue();
   }

   private void createPluginDescription() {
      Class<?> pluginClass = (Class<?>)this.pluginClassSelect.getValue();
      String documentation = PluginDocumentation.buildDocumentation(pluginClass);
      this.pluginClassSelect.setDescription(documentation, ContentMode.HTML);
   }

   private void initAssignablePluginClasses() {
      IndexedContainer dataSource = new IndexedContainer();
      dataSource.addContainerProperty("displayName", String.class, null);
      this.pluginClassSelect.setContainerDataSource(dataSource);
      Set<Class<?>> pluginClasses = this.pluginDict.getAssignablePluginClasses(this.basePluginType);
      this.pluginClassSelect.removeAllItems();
      this.putPluginClassesInCategories(pluginClasses);
      this.pluginClassSelect.setItemCaptionPropertyId("displayName");
      this.updatePluginClassSelectMode();
      this.createPluginDescription();
      this.sortPluginClassSelectByName();
   }

   private void updatePluginClassSelectMode() {
      if (this.pluginClassSelect.getItemIds().size() == 1) {
         this.pluginClassSelect.select(Lists.newArrayList(this.pluginClassSelect.getItemIds()).getFirst());
         this.pluginClassSelect.setReadOnly(true);
      } else {
         this.pluginClassSelect.setReadOnly(false);
      }
   }

   private void putPluginClassesInCategories(Set<Class<?>> pluginClasses) {
      for (Class<?> classObj : pluginClasses) {
         boolean isPluginClassUnlicensed = !this.isPluginClassLicensed(classObj);
         boolean isPluginClassDeprecated = classObj.isAnnotationPresent(DeprecatedPlugin.class);
         if (isPluginClassUnlicensed) {
            this.unlicensedPlugins.add(classObj);
         }

         if (isPluginClassDeprecated) {
            this.deprecatedPlugins.add(classObj);
         }

         if (!isPluginClassUnlicensed && !isPluginClassDeprecated) {
            Item item = this.pluginClassSelect.addItem(classObj);
            VaadinHelper.setPropertyValue(item, "displayName", PluginLoader.getDisplayName(classObj));
         }
      }
   }

   public final void hideParentSelect() {
      this.parentConfiguration.setVisible(false);
   }

   public void makeIdentifierMandatory() {
      this.displayNameField.setRequired(true);
   }

   public boolean isEditAfterCreatingPlugin() {
      return this.editAfterCreatingPlugin;
   }

   private boolean isPluginClassLicensed(Class<?> pluginClass) {
      Preconditions.checkNotNull(pluginClass);
      return this.getConfigEditorApplication().getLicenseManager().isPluginClassLicensed(pluginClass.getName());
   }

   public void hideCreateButton() {
      this.createAndEditButton.setVisible(true);
      this.createButton.setVisible(false);
   }

   private CheckBox createShowDeprecatedCheckbox() {
      final CheckBox checkbox = new CheckBox("Show deprecated plugins");
      checkbox.setImmediate(true);
      checkbox.setBuffered(false);
      checkbox.setDescription("Shows deprecated plugins in the plugin class drop down select.");
      checkbox.addValueChangeListener(new ValueChangeListener() {
         public void valueChange(ValueChangeEvent event) {
            if ((Boolean)checkbox.getValue()) {
               this.addDeprecatedPluginsToClassSelect();
            } else {
               NewPluginDialog.this.removePluginsFromClassSelect(NewPluginDialog.this.deprecatedPlugins);
               this.addUnlicensedPluginsAgainToClassSelectToMaintainIntersectionIfNeccessary();
               NewPluginDialog.this.sortPluginClassSelectByName();
            }

            NewPluginDialog.this.updatePluginClassSelectMode();
         }

         private void addDeprecatedPluginsToClassSelect() {
            NewPluginDialog.this.addPluginsToClassSelect(NewPluginDialog.this.deprecatedPlugins);
            NewPluginDialog.this.sortPluginClassSelectByName();
         }

         private void addUnlicensedPluginsAgainToClassSelectToMaintainIntersectionIfNeccessary() {
            if ((Boolean)NewPluginDialog.this.showUnlicensedPlugins.getValue()) {
               NewPluginDialog.this.addPluginsToClassSelect(NewPluginDialog.this.unlicensedPlugins);
            }
         }
      });
      return checkbox;
   }

   private CheckBox createShowUnlicensedCheckbox() {
      final CheckBox checkbox = new CheckBox("Show unlicensed plugins");
      checkbox.setImmediate(true);
      checkbox.setBuffered(false);
      checkbox.setDescription("Shows unlicensed plugins in the plugin class drop down select.");
      checkbox.addValueChangeListener(new ValueChangeListener() {
         public void valueChange(ValueChangeEvent event) {
            if ((Boolean)checkbox.getValue()) {
               this.addUnlicensedPluginsToClassSelect();
            } else {
               NewPluginDialog.this.removePluginsFromClassSelect(NewPluginDialog.this.unlicensedPlugins);
               this.addDeprecatedPluginsAgainToClassSelectToMaintainIntersectionIfNeccessary();
               NewPluginDialog.this.sortPluginClassSelectByName();
            }

            NewPluginDialog.this.updatePluginClassSelectMode();
         }

         private void addUnlicensedPluginsToClassSelect() {
            NewPluginDialog.this.addPluginsToClassSelect(NewPluginDialog.this.unlicensedPlugins);
            NewPluginDialog.this.sortPluginClassSelectByName();
         }

         private void addDeprecatedPluginsAgainToClassSelectToMaintainIntersectionIfNeccessary() {
            if ((Boolean)NewPluginDialog.this.showDeprecatedPlugins.getValue()) {
               NewPluginDialog.this.addPluginsToClassSelect(NewPluginDialog.this.deprecatedPlugins);
            }
         }
      });
      return checkbox;
   }

   private void addPluginsToClassSelect(List<Class<?>> pluginsToAdd) {
      this.pluginClassSelect.setReadOnly(false);

      for (Class<?> pluginClass : pluginsToAdd) {
         if (!this.pluginClassSelect.containsId(pluginClass)) {
            Item item = this.pluginClassSelect.addItem(pluginClass);
            VaadinHelper.setPropertyValue(item, "displayName", PluginLoader.getDisplayName(pluginClass));
         }
      }

      this.updatePluginClassSelectMode();
   }

   private void removePluginsFromClassSelect(List<Class<?>> pluginsToRemove) {
      this.pluginClassSelect.setReadOnly(false);

      for (Class<?> pluginClass : pluginsToRemove) {
         this.pluginClassSelect.removeItem(pluginClass);
      }
   }

   private void sortPluginClassSelectByName() {
      IndexedContainer container = (IndexedContainer)this.pluginClassSelect.getContainerDataSource();
      container.setItemSorter(new DefaultItemSorter(Comparator.comparing(property -> Objects.toString(property).toLowerCase(Locale.ENGLISH))));
      container.sort(new Object[]{"displayName"}, new boolean[]{true});
   }

   private void handleParentConfiguration(MutablePluginConfiguration fixedParent) {
      if (fixedParent == null) {
         this.parentConfiguration.addValueChangeListener(event -> {
            MutablePluginConfiguration newParent = this.getSelectedParent();
            if (newParent != null) {
               this.ignoreClassValueChange = true;

               try {
                  this.pluginClassSelect.setValue(newParent.getPluginClass());
               } catch (ReadOnlyException e) {
                  LOG.error(LogTypes.SERVER_INFO_SYS, "com.vaadin.v7.data.Property.ReadOnlyException: " + e.getMessage(), e);
               } catch (ConversionException e) {
                  LOG.error(LogTypes.SERVER_INFO_SYS, "com.vaadin.v7.data.util.converter.Converter.ConversionException: " + e.getMessage(), e);
               } catch (InitializeException e) {
                  LOG.error(LogTypes.CONFIGURATION_ERROR, "InitializeException: " + e.getMessage(), e);
               }

               this.ignoreClassValueChange = false;
            }
         });
      } else {
         this.parentConfiguration.addItem(fixedParent);
         this.parentConfiguration.setValue(fixedParent);
         this.parentConfiguration.setReadOnly(true);
      }
   }

   private PluginComboBox createParentSelect() {
      PluginComboBox select = new PluginComboBox();
      select.setCaption("Parent configuration");
      select.setWidth("100%");
      select.setNullSelectionAllowed(true);
      select.setInvalidAllowed(false);
      select.setImmediate(true);
      select.setBuffered(false);
      return select;
   }

   private void handlePluginClass(MutablePluginConfiguration fixedParent) {
      if (fixedParent == null) {
         if (this.pluginClassSelect.getItemIds().size() == 1) {
            this.pluginClassSelect.setValue(this.pluginClassSelect.getItemIds().iterator().next());
         }

         this.pluginClassSelect.addValueChangeListener(event -> {
            if (!this.ignoreClassValueChange) {
               try {
                  this.assignPluginsToSelect();
               } catch (InitializeException e) {
                  throw new IllegalStateException(e);
               }
            }
         });
      } else {
         try {
            this.pluginClassSelect.setValue(fixedParent.getPluginClass());
         } catch (ReadOnlyException e) {
            LOG.error(LogTypes.SERVER_INFO_SYS, "com.vaadin.v7.data.Property.ReadOnlyException: " + e.getMessage(), e);
         } catch (ConversionException e) {
            LOG.error(LogTypes.SERVER_INFO_SYS, "com.vaadin.v7.data.util.converter.Converter.ConversionException: " + e.getMessage(), e);
         } catch (InitializeException e) {
            LOG.error(LogTypes.CONFIGURATION_ERROR, "InitializeException: " + e.getMessage(), e);
         }

         this.pluginClassSelect.setReadOnly(true);
      }
   }

   private void assignPluginsToSelect() throws InitializeException {
      this.parentConfiguration.initSelectablePlugins(this.target, this.basePluginType, this.getConfigEditorApplication().getSelectedConfigurationEnvironment());
      this.parentConfiguration.setValue(null);
      if (this.deprecatedPlugins.contains((Class)this.pluginClassSelect.getValue())) {
         this.pluginClassSelect.addStyleName("deprecated");
      } else {
         this.pluginClassSelect.removeStyleName("deprecated");
      }

      this.createPluginDescription();
   }

   private ComboBox createPluginClassSelect() {
      ComboBox select = new ComboBox("Plugin class");
      select.setId("newPluginClassField");
      select.setImmediate(true);
      select.setBuffered(false);
      select.setWidth("100%");
      select.setNullSelectionAllowed(false);
      select.setFilteringMode(FilteringMode.CONTAINS);
      return select;
   }

   private void setProperties() {
      this.setWidth(350.0F, Unit.PIXELS);
      this.setCaption("Create new plugin configuration");
      this.setModal(true);
      this.setResizable(false);
   }

   private ConfigEditorApplication getConfigEditorApplication() {
      return (ConfigEditorApplication)UI.getCurrent();
   }
}
