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

import com.airlock.iam.plugin.api.config.ConfigurationContext;
import com.airlock.iam.plugin.api.config.ConfigurationEnvironment;
import com.airlock.iam.plugin.api.config.PluginConfiguration;
import com.airlock.iam.plugin.api.exception.ConfigurationException;
import com.airlock.iam.plugin.api.property.PluginConfigurationProperties;
import com.airlock.iam.plugin.api.property.PropertyConfiguration;
import com.airlock.iam.plugin.configeditor.data.tree.LabelTreeItem;
import com.airlock.iam.plugin.configeditor.data.tree.PluginConfigurationTreeItem;
import com.airlock.iam.plugin.configeditor.data.tree.PropertyTreeItem;
import com.airlock.iam.plugin.configeditor.data.tree.TreeItem;
import com.airlock.iam.plugin.configeditor.util.GuiResources;
import com.airlock.iam.plugin.configeditor.util.VaadinHelper;
import com.airlock.iam.plugin.framework.application.configuration.MutablePluginConfiguration;
import com.airlock.iam.plugin.framework.misc.config.PluginConfigurationPropertiesView;
import com.airlock.iam.plugin.framework.misc.config.properties.PluginListPropertyConfiguration;
import com.airlock.iam.plugin.framework.misc.config.properties.PluginMapPropertyConfiguration;
import com.airlock.iam.plugin.framework.misc.config.properties.PluginPropertyConfiguration;
import com.airlock.iam.system.property.IamSystemProperties;
import com.vaadin.server.ThemeResource;
import com.vaadin.v7.data.Item;
import java.nio.file.Path;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.Map.Entry;
import java.util.stream.Collectors;

abstract class CompositionTree extends ConfigurationTree {
   private final ValidationStatusHandler validationStatusHandler;

   public CompositionTree(String displayName) {
      this(displayName, new ValidationStatusHandler(), IamSystemProperties.getRootDirectory());
   }

   protected CompositionTree(String displayName, ValidationStatusHandler validationStatusHandler, Path rootDirectory) {
      super(displayName, rootDirectory, true);
      this.validationStatusHandler = validationStatusHandler;
   }

   @Override
   public void sort() {
   }

   @Override
   protected void addPluginConfigurationItem(
      TreeItem<?> parentNode,
      MutablePluginConfiguration pc,
      String caption,
      ConfigurationEnvironment env,
      ConfigurationContext context,
      Map<PluginConfiguration, List<ConfigurationException>> validations
   ) {
      if (pc != null) {
         PluginConfigurationTreeItem currentNode = new PluginConfigurationTreeItem(pc, caption, false);
         this.addTreeItem(parentNode, pc, currentNode, env, validations);
      }
   }

   private void addTreeItem(
      TreeItem<?> parentNode,
      MutablePluginConfiguration pc,
      PluginConfigurationTreeItem currentNode,
      ConfigurationEnvironment env,
      Map<PluginConfiguration, List<ConfigurationException>> validations
   ) {
      Item treeId = this.addItem(currentNode);
      if (treeId != null) {
         if (parentNode != null) {
            this.setParent(currentNode, parentNode);
         }

         this.setValidationStatusIconForPluginNodes(pc, currentNode, env, validations);
         this.addPluginConfigurationChildren(currentNode, pc, env, validations);
      }
   }

   private void addPluginConfigurationChildren(
      PluginConfigurationTreeItem parentNode,
      MutablePluginConfiguration pc,
      ConfigurationEnvironment env,
      Map<PluginConfiguration, List<ConfigurationException>> validations
   ) {
      OrderedTreeItemExtractor orderExtractor = new OrderedTreeItemExtractor(pc, env);
      PluginConfigurationProperties properties = new PluginConfigurationPropertiesView(pc, env)
         .inheritFromParent()
         .inheritFromCommonEnvironment()
         .getProperties();

      for (ConfigurationContext currentContext : properties.getAllConfigurationContexts()) {
         TreeItem<?> currentNode;
         if (currentContext.isDefaultContext()) {
            currentNode = parentNode;
         } else {
            currentNode = new LabelTreeItem("Context: " + currentContext, parentNode, currentContext);
            Item contextItem = this.addItem(currentNode);
            VaadinHelper.setPropertyValue(contextItem, "icon", GuiResources.ICON_CONTEXT);
            this.setParent(currentNode, parentNode);
         }

         for (OrderedTreeItemExtractor.PropertyWithDisplayName property : orderExtractor.extractOrderedPropertiesWithDisplayName(currentContext)) {
            this.addConfigurationToTree(env, currentContext, validations, currentNode, property.getDisplayName(), property.getPropertyConfiguration());
         }

         if (currentNode instanceof LabelTreeItem && !this.hasChildren(currentNode)) {
            this.removeItem(currentNode);
         }
      }
   }

   private void addConfigurationToTree(
      ConfigurationEnvironment env,
      ConfigurationContext currentContext,
      Map<PluginConfiguration, List<ConfigurationException>> validations,
      TreeItem<?> currentNode,
      String propertyDisplayName,
      PropertyConfiguration propertyConf
   ) {
      if (propertyConf instanceof PluginPropertyConfiguration) {
         MutablePluginConfiguration childPc = (MutablePluginConfiguration)((PluginPropertyConfiguration)propertyConf).getValue();
         if (childPc != null) {
            PluginConfigurationTreeItem treeItem = new PluginConfigurationTreeItem(childPc, propertyDisplayName, false);
            this.addTreeItem(currentNode, childPc, treeItem, env, validations);
         }
      } else if (propertyConf instanceof PluginListPropertyConfiguration) {
         List<MutablePluginConfiguration> childList = Optional.ofNullable(((PluginListPropertyConfiguration)propertyConf).getValue())
            .map(list -> list.stream().map(MutablePluginConfiguration.class::cast).collect(Collectors.toList()))
            .orElse(null);
         if (childList != null) {
            PropertyTreeItem listTreeItem = new PropertyTreeItem(propertyConf, currentContext, propertyDisplayName, currentNode);
            this.addChildPluginsOfListToTheTree(env, validations, currentNode, childList, listTreeItem);
         }
      } else if (propertyConf instanceof PluginMapPropertyConfiguration) {
         Map<String, PluginConfiguration> childMap = ((PluginMapPropertyConfiguration)propertyConf).getValue();
         PropertyTreeItem mapTreeItem = new PropertyTreeItem(propertyConf, currentContext, propertyDisplayName, currentNode);
         this.addAllChildPluginsOfTheMapToTheTree(env, validations, currentNode, childMap, mapTreeItem);
      }
   }

   private void addAllChildPluginsOfTheMapToTheTree(
      ConfigurationEnvironment env,
      Map<PluginConfiguration, List<ConfigurationException>> validations,
      TreeItem<?> currentNodeItemId,
      Map<String, PluginConfiguration> childPcMap,
      TreeItem<?> mapTreeItem
   ) {
      Item mapItem = this.addItem(mapTreeItem);
      VaadinHelper.setPropertyValue(mapItem, "icon", GuiResources.ICON_PLUGIN_LISTMAP);
      this.setParent(mapTreeItem, currentNodeItemId);
      if (childPcMap != null) {
         for (Entry<String, PluginConfiguration> childEntry : childPcMap.entrySet()) {
            MutablePluginConfiguration childPc = (MutablePluginConfiguration)childEntry.getValue();
            if (childPc != null) {
               PluginConfigurationTreeItem treeItem = new PluginConfigurationTreeItem(childPc, childEntry.getKey(), false);
               this.addTreeItem(mapTreeItem, childPc, treeItem, env, validations);
            }
         }
      }
   }

   private void addChildPluginsOfListToTheTree(
      ConfigurationEnvironment env,
      Map<PluginConfiguration, List<ConfigurationException>> validations,
      TreeItem<?> currentNodeItemId,
      List<MutablePluginConfiguration> childPcList,
      PropertyTreeItem listTreeItem
   ) {
      Item listItem = this.addItem(listTreeItem);
      VaadinHelper.setPropertyValue(listItem, "icon", GuiResources.ICON_PLUGIN_LISTMAP);
      this.setParent(listTreeItem, currentNodeItemId);

      for (MutablePluginConfiguration childPc : childPcList) {
         if (childPc != null) {
            PluginConfigurationTreeItem treeItem = new PluginConfigurationTreeItem(childPc, childPc.getPluginDisplayName(), false);
            this.addTreeItem(listTreeItem, childPc, treeItem, env, validations);
         }
      }
   }

   private void setValidationStatusIconForPluginNodes(
      MutablePluginConfiguration pc,
      PluginConfigurationTreeItem currentNode,
      ConfigurationEnvironment env,
      Map<PluginConfiguration, List<ConfigurationException>> validations
   ) {
      this.setErrors(currentNode, this.validationStatusHandler.collectLocalValidationErrors(pc, env, validations, this.getApplication()));
      ThemeResource localValidationIcon = this.validationStatusHandler.getValidationStatusIconForPluginNode(currentNode);
      this.setIcon(currentNode, localValidationIcon);

      for (Object parentNode = this.getParent(currentNode); parentNode != null; parentNode = this.getParent(parentNode)) {
         if (parentNode instanceof PluginConfigurationTreeItem) {
            this.setIcon(parentNode, this.validationStatusHandler.getParentValidationIcon(currentNode, (PluginConfigurationTreeItem)parentNode));
         }
      }
   }

   private void setErrors(PluginConfigurationTreeItem treeItem, Set<ConfigurationException> localExceptions) {
      if (!localExceptions.isEmpty()) {
         treeItem.setErrors(localExceptions);
      }
   }

   private void setIcon(Object treeItem, ThemeResource icon) {
      if (icon != null) {
         VaadinHelper.setPropertyValue(this.getItem(treeItem), "icon", icon);
      }
   }
}
