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

import com.airlock.iam.configeditor.shared.domain.IamModuleConfigRoot;
import com.airlock.iam.elog.api.ELogger;
import com.airlock.iam.elog.api.ELoggerFactory;
import com.airlock.iam.plugin.configeditor.ConfigEditorApplication;
import com.airlock.iam.plugin.configeditor.util.GuiResources;
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.PluginLoader;
import com.vaadin.server.Sizeable.Unit;
import com.vaadin.ui.Alignment;
import com.vaadin.ui.Button;
import com.vaadin.ui.HorizontalLayout;
import com.vaadin.ui.Notification;
import com.vaadin.ui.Panel;
import com.vaadin.ui.UI;
import com.vaadin.ui.VerticalLayout;
import com.vaadin.ui.Window;
import com.vaadin.ui.Notification.Type;
import com.vaadin.v7.data.Item;
import com.vaadin.v7.data.util.IndexedContainer;
import com.vaadin.v7.ui.ComboBox;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import java.util.List;
import java.util.Set;

public class EditRootNodeDialog extends Window {
   private static final String DELETE_NODE = "DELETE";
   private static final ELogger LOG = ELoggerFactory.getLogger(EditRootNodeDialog.class);
   private final List<EditRootNodeDialog.RootNode> rootNodes;
   private final PluginConfigurationDictionary configurationDictionary;

   @SuppressFBWarnings(value = "MC_OVERRIDABLE_METHOD_CALL_IN_CONSTRUCTOR", justification = "See ALIAM-15315")
   public EditRootNodeDialog(PluginConfigurationDictionary pluginConfigurationDictionary) {
      this.configurationDictionary = pluginConfigurationDictionary;
      this.configureWindow();
      EditRootNodeDialog.RootNode globalConfigsNode = new EditRootNodeDialog.RootNode(IamModuleConfigRoot.GLOBAL, "mainSettingsRootNodeCombobox");
      EditRootNodeDialog.RootNode loginappNode = new EditRootNodeDialog.RootNode(IamModuleConfigRoot.LOGINAPP, "loginApplicationRootNodeCombobox");
      EditRootNodeDialog.RootNode adminappNode = new EditRootNodeDialog.RootNode(IamModuleConfigRoot.ADMINAPP, "adminToolRootNodeCombobox");
      EditRootNodeDialog.RootNode transactionApprovalNode = new EditRootNodeDialog.RootNode(
         IamModuleConfigRoot.TRANSACTION_APPROVAL, "transactionApprovalRootNodeCombobox"
      );
      EditRootNodeDialog.RootNode apiPolicyServiceNode = new EditRootNodeDialog.RootNode(
         IamModuleConfigRoot.API_POLICY_SERVICE, "apiPolicyServiceRootNodeCombobox"
      );
      EditRootNodeDialog.RootNode serverNode = new EditRootNodeDialog.RootNode(IamModuleConfigRoot.SERVER, "serviceContainerRootNodeCombobox");
      this.rootNodes = List.of(globalConfigsNode, loginappNode, adminappNode, transactionApprovalNode, apiPolicyServiceNode, serverNode);
      this.setContent(this.buildLayout());
      loginappNode.comboBox.focus();
   }

   private void configureWindow() {
      this.setWidth(300.0F, Unit.PIXELS);
      this.setCaption("Edit root node plugins");
      this.setModal(true);
      this.setResizable(false);
   }

   private VerticalLayout buildLayout() {
      VerticalLayout vl = new VerticalLayout();
      Panel panel = new Panel("Select the new plugin classes.");
      VerticalLayout innerLayout = new VerticalLayout();

      for (EditRootNodeDialog.RootNode node : this.rootNodes) {
         innerLayout.addComponent(node.comboBox);
      }

      panel.setContent(innerLayout);
      vl.addComponent(panel);
      vl.addComponent(this.makeFooter());
      return vl;
   }

   private HorizontalLayout makeFooter() {
      Button okButton = this.makeConfirmButton();
      Button cancelButton = this.makeCancelButton();
      HorizontalLayout hl = new HorizontalLayout();
      hl.setSpacing(false);
      hl.setWidth(100.0F, Unit.PERCENTAGE);
      hl.addComponent(cancelButton);
      hl.addComponent(okButton);
      hl.setComponentAlignment(cancelButton, Alignment.MIDDLE_LEFT);
      hl.setComponentAlignment(okButton, Alignment.MIDDLE_RIGHT);
      return hl;
   }

   private Button makeCancelButton() {
      Button cancelButton = new Button("Cancel");
      cancelButton.setId("cancelButton");
      cancelButton.addClickListener(event -> getApplication().removeWindow(this));
      return cancelButton;
   }

   private Button makeConfirmButton() {
      Button okButton = new Button("OK");
      okButton.setId("primaryButton");
      okButton.addStyleName("primary");
      okButton.addClickListener(event -> {
         for (EditRootNodeDialog.RootNode node : this.rootNodes) {
            this.setClassValue(node);
         }

         this.updateNodes();
         getApplication().removeWindow(this);
      });
      okButton.setClickShortcut(13, new int[0]);
      return okButton;
   }

   private void setClassValue(EditRootNodeDialog.RootNode node) {
      Object value = node.getValue();
      if (value != null) {
         node.resetNode = true;
         if (!value.equals("DELETE")) {
            node.nodeClass = (Class<?>)value;
         } else {
            node.nodeClass = null;
         }
      }
   }

   private void updateNodes() {
      for (EditRootNodeDialog.RootNode node : this.rootNodes) {
         if (node.resetNode) {
            this.updateNode(node);
         }
      }
   }

   private void updateNode(EditRootNodeDialog.RootNode node) {
      this.removeNodeFromConfiguration(node);
      this.addNodeToConfiguration(node);
   }

   private void showDeletePluginConfigurationDialog(MutablePluginConfiguration toBeDeleted) {
      DeletePluginConfigurationDialog dialog = new DeletePluginConfigurationDialog(toBeDeleted, toBeDeleted.getPluginDisplayName());
      dialog.addCloseListener(e -> {
         DeletePluginConfigurationDialog dialog1 = (DeletePluginConfigurationDialog)e.getSource();
         if (dialog1.didIt()) {
            if (toBeDeleted.equals(getApplication().getSelectedConfiguration())) {
               getApplication().selectConfiguration(null);
               Notification.show("Plugin configuration deleted.", Type.TRAY_NOTIFICATION);
            }

            getApplication().updateValidationState();
            getApplication().updateFormFields();
         }
      });
   }

   private static ConfigEditorApplication getApplication() {
      return (ConfigEditorApplication)UI.getCurrent();
   }

   private void removeNodeFromConfiguration(EditRootNodeDialog.RootNode node) {
      MutablePluginConfiguration pluginCfg = this.configurationDictionary.getByDisplayName(node.configRoot.getPluginDisplayName());
      if (pluginCfg != null) {
         LOG.debug("setRootNode() - removing :" + pluginCfg.getPluginDisplayName());
         this.showDeletePluginConfigurationDialog(pluginCfg);
      }
   }

   private void addNodeToConfiguration(EditRootNodeDialog.RootNode node) {
      if (node.nodeClass != null) {
         LOG.debug("setRootNode() - adding :" + node.configRoot.getConfigEditorCaption() + ": " + node.nodeClass.getCanonicalName());
         this.configurationDictionary
            .addConfigTreeRootPluginConfiguration(new DefaultPluginConfiguration(node.configRoot.getPluginDisplayName(), node.nodeClass.getCanonicalName()));
      }
   }

   private static class RootNode {
      private static final String PROPERTY_DISPLAY_NAME = "displayName";
      private final ComboBox comboBox;
      private final IamModuleConfigRoot configRoot;
      private Class<?> nodeClass;
      private boolean resetNode;

      public RootNode(IamModuleConfigRoot root, String htmlId) {
         this.comboBox = this.makeRootNodeComboBox(root.getConfigEditorCaption(), root);
         this.comboBox.setId(htmlId);
         this.configRoot = root;
      }

      public Object getValue() {
         return this.comboBox.getValue();
      }

      private ComboBox makeRootNodeComboBox(String caption, IamModuleConfigRoot medusaModuleConfigRoot) {
         ComboBox combo = new ComboBox(caption);
         combo.setWidth("100%");
         combo.setNullSelectionAllowed(true);
         combo.setDescription("Base class: " + medusaModuleConfigRoot.getPluginClass().getCanonicalName());
         this.initAssignablePluginClasses(combo, medusaModuleConfigRoot);
         return combo;
      }

      private void initAssignablePluginClasses(ComboBox combo, IamModuleConfigRoot rootNode) {
         Set<Class<?>> pluginClasses = EditRootNodeDialog.getApplication().getPluginDictionary().getAssignablePluginClasses(rootNode.getPluginClass());
         IndexedContainer dataSource = new IndexedContainer();
         dataSource.addContainerProperty("displayName", String.class, null);
         combo.setItemCaptionPropertyId("displayName");
         combo.setContainerDataSource(dataSource);
         MutablePluginConfiguration rootPc = EditRootNodeDialog.getApplication().getConfigurationDictionary().getByDisplayName(rootNode.getPluginDisplayName());
         if (rootPc == null) {
            this.addPluginItem(combo, pluginClasses);
         } else {
            this.addNodeRemoveItem(combo, rootNode);
         }

         dataSource.sort(new Object[]{"displayName"}, new boolean[]{false});
      }

      private void addPluginItem(ComboBox combo, Set<Class<?>> pluginClasses) {
         for (Class<?> classObj : pluginClasses) {
            Item item = combo.addItem(classObj);
            VaadinHelper.setPropertyValue(item, "displayName", PluginLoader.getDisplayName(classObj));
         }
      }

      private void addNodeRemoveItem(ComboBox combo, IamModuleConfigRoot rootNode) {
         combo.addItem("DELETE");
         combo.setItemIcon("DELETE", GuiResources.ICON_DELETE);
         VaadinHelper.setPropertyValue(combo.getItem("DELETE"), "displayName", " remove root node: " + rootNode.getConfigEditorCaption());
      }
   }
}
