package com.airlock.iam.plugin.framework.domain.model;

import com.airlock.iam.plugin.framework.domain.model.modification.ConfigModification;
import com.airlock.iam.plugin.framework.domain.model.property.PluginListProperty;
import com.airlock.iam.plugin.framework.domain.model.property.PluginMapProperty;
import com.airlock.iam.plugin.framework.domain.model.property.PluginProperty;
import com.airlock.iam.plugin.framework.domain.model.property.Property;
import com.airlock.iam.plugin.framework.domain.model.property.PropertyVisitor;
import com.airlock.iam.plugin.framework.domain.model.property.SimpleListProperty;
import com.airlock.iam.plugin.framework.domain.model.property.SimpleProperty;
import com.airlock.iam.system.version.ConfigSchemaVersion;
import com.google.common.base.Preconditions;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.Optional;
import java.util.Set;

public class MedusaConfigurationElement {
   private final List<Plugin> toplevelPlugins = new ArrayList<>();
   private final List<ConfigHistoryMetaDataItem> history = new ArrayList<>();
   private final List<ConfigModification> modifications = new ArrayList<>();
   private boolean isDocumentTypeSnippet;
   private ConfigSchemaVersion schemaVersion = ConfigSchemaVersion.UNDEFINED_SCHEMA;
   private Optional<String> iamVersion = Optional.empty();
   private Optional<ConfigTemplateMetaData> templateMetaData = Optional.empty();

   public void setIamVersion(String iamVersion) {
      this.iamVersion = Optional.ofNullable(iamVersion);
   }

   public void setTemplateMetaData(ConfigTemplateMetaData configTemplateMetaData) {
      this.templateMetaData = Optional.ofNullable(configTemplateMetaData);
   }

   public boolean isTemplate() {
      return this.getTemplateMetaData().isPresent();
   }

   public List<Plugin> getToplevelPlugins() {
      return List.copyOf(this.toplevelPlugins);
   }

   public void sortToplevelPlugins(Comparator<Plugin> comparator) {
      this.toplevelPlugins.sort(comparator);
   }

   public void addToplevelPlugin(Plugin plugin) {
      this.toplevelPlugins.add((Plugin)Preconditions.checkNotNull(plugin));
   }

   public List<ConfigHistoryMetaDataItem> getHistory() {
      return List.copyOf(this.history);
   }

   public void addHistoryItem(ConfigHistoryMetaDataItem item) {
      this.history.add((ConfigHistoryMetaDataItem)Preconditions.checkNotNull(item));
   }

   public void addModification(ConfigModification modification) {
      this.modifications.add((ConfigModification)Preconditions.checkNotNull(modification));
   }

   public List<ConfigModification> getModifications() {
      return List.copyOf(this.modifications);
   }

   public Iterable<Plugin> allPluginsInArbitraryOrder() {
      return () -> new MedusaConfigurationElement.MedusaConfigurationElementIterator(this.toplevelPlugins);
   }

   @SuppressFBWarnings(justification = "generated code")
   public boolean isDocumentTypeSnippet() {
      return this.isDocumentTypeSnippet;
   }

   @SuppressFBWarnings(justification = "generated code")
   public ConfigSchemaVersion getSchemaVersion() {
      return this.schemaVersion;
   }

   @SuppressFBWarnings(justification = "generated code")
   public Optional<String> getIamVersion() {
      return this.iamVersion;
   }

   @SuppressFBWarnings(justification = "generated code")
   public Optional<ConfigTemplateMetaData> getTemplateMetaData() {
      return this.templateMetaData;
   }

   @SuppressFBWarnings(justification = "generated code")
   public void setDocumentTypeSnippet(boolean isDocumentTypeSnippet) {
      this.isDocumentTypeSnippet = isDocumentTypeSnippet;
   }

   @SuppressFBWarnings(justification = "generated code")
   public void setSchemaVersion(ConfigSchemaVersion schemaVersion) {
      this.schemaVersion = schemaVersion;
   }

   private static final class MedusaConfigurationElementIterator implements Iterator<Plugin> {
      private final Set<Plugin> seen;
      private final List<Plugin> toVisit;

      private MedusaConfigurationElementIterator(List<Plugin> toplevelPlugins) {
         this.toVisit = new LinkedList<>(toplevelPlugins);
         this.seen = new HashSet<>(toplevelPlugins);
      }

      @Override
      public boolean hasNext() {
         return !this.toVisit.isEmpty();
      }

      public Plugin next() {
         if (!this.hasNext()) {
            throw new NoSuchElementException();
         }

         Plugin cur = this.toVisit.removeFirst();

         for (Property property : cur.getProperties()) {
            this.checkAndAddProperties(property);
         }

         return cur;
      }

      private void checkAndAddProperties(Property property) {
         property.accept(new PropertyVisitor() {
            @Override
            public void visitSimpleProperty(SimpleProperty property) {
            }

            @Override
            public void visitSimpleListProperty(SimpleListProperty property) {
            }

            @Override
            public void visitPluginProperty(PluginProperty property) {
               if (property.getValue().isPresent()) {
                  this.addIfNotAlreadySeen(property.getValue().get());
               }
            }

            private void addIfNotAlreadySeen(Plugin plugin) {
               if (!MedusaConfigurationElementIterator.this.seen.contains(plugin)) {
                  MedusaConfigurationElementIterator.this.seen.add(plugin);
                  MedusaConfigurationElementIterator.this.toVisit.add(plugin);
               }
            }

            @Override
            public void visitPluginMapProperty(PluginMapProperty property) {
               for (Plugin plugin : property.getPluginMap().values()) {
                  this.addIfNotAlreadySeen(plugin);
               }
            }

            @Override
            public void visitPluginListProperty(PluginListProperty property) {
               for (Plugin plugin : property.getPlugins()) {
                  this.addIfNotAlreadySeen(plugin);
               }
            }
         });
      }

      @Override
      public void remove() {
         throw new UnsupportedOperationException();
      }
   }
}
