package com.airlock.iam.plugin.framework.misc.config;

import com.airlock.iam.plugin.api.config.PluginConfiguration;
import com.airlock.iam.plugin.framework.application.configuration.MutablePluginConfiguration;
import com.google.common.collect.Sets;
import java.util.ArrayDeque;
import java.util.Collection;
import java.util.Deque;
import java.util.Iterator;
import java.util.NoSuchElementException;
import java.util.Set;

public final class SubtreeWalker implements Iterator<MutablePluginConfiguration> {
   private final Set<PluginConfiguration> seen = Sets.newIdentityHashSet();
   private final Deque<MutablePluginConfiguration> nexts = new ArrayDeque<>();
   private final SubtreeWalkerScope scope;

   public SubtreeWalker(PluginConfiguration root, SubtreeWalkerScope scope) {
      this.scope = scope;
      this.addNext((MutablePluginConfiguration)root);
   }

   public SubtreeWalker(Collection<PluginConfiguration> roots, SubtreeWalkerScope scope) {
      this.scope = scope;

      for (PluginConfiguration root : roots) {
         this.addNext((MutablePluginConfiguration)root);
      }
   }

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

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

      MutablePluginConfiguration next = this.nexts.removeFirst();
      this.fillNexts(next);
      return next;
   }

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

   private void fillNexts(MutablePluginConfiguration current) {
      this.scope.getNexts(current).forEach(this::addNext);
   }

   private void addNext(MutablePluginConfiguration configuration) {
      if (this.seen.add(configuration)) {
         this.nexts.addLast(configuration);
      }
   }
}
