package org.testng;

import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentLinkedQueue;
import org.testng.collections.Maps;
import org.testng.internal.XmlMethodSelector;

/**
 * This class maintains a map of {@code <Class, List<ITestNGMethod>>}. It is used by TestWorkers to
 * determine if the method they just ran is the last of its class, in which case it's time to invoke
 * all the afterClass methods.
 *
 * @author <a href='mailto:the[dot]mindstorm[at]gmail[dot]com'>Alex Popescu</a>
 */
public class ClassMethodMap {
  private final Map<Object, Collection<ITestNGMethod>> classMap = Maps.newConcurrentMap();
  // These two variables are used throughout the workers to keep track
  // of what beforeClass/afterClass methods have been invoked
  private final Map<ITestClass, Set<Object>> beforeClassMethods = Maps.newConcurrentMap();
  private final Map<ITestClass, Set<Object>> afterClassMethods = Maps.newConcurrentMap();

  public ClassMethodMap(List<ITestNGMethod> methods, XmlMethodSelector xmlMethodSelector) {
    for (ITestNGMethod m : methods) {
      // Only add to the class map methods that are included in the
      // method selector. We can pass a null context here since the selector
      // should already have been initialized
      if (xmlMethodSelector != null && !xmlMethodSelector.includeMethod(null, m, true)) {
        continue;
      }

      Object instance = m.getInstance();
      classMap.computeIfAbsent(instance, k -> new ConcurrentLinkedQueue<>()).add(m);
    }
  }

  /**
   * Remove the method from this map.
   *
   * @param m The test method
   * @param instance The test instance
   * @return true if it is the last of its class
   */
  public boolean removeAndCheckIfLast(ITestNGMethod m, Object instance) {
    Collection<ITestNGMethod> l = classMap.get(instance);
    if (l == null) {
      throw new IllegalStateException(
          "Could not find any methods associated with test class instance " + instance);
    }
    l.remove(m);
    // It's the last method of this class if all the methods remaining in the list belong to a
    // different class
    for (ITestNGMethod tm : l) {
      if (tm.getEnabled() && tm.getTestClass().equals(m.getTestClass())) {
        return false;
      }
    }
    return true;
  }

  public Map<ITestClass, Set<Object>> getInvokedBeforeClassMethods() {
    return beforeClassMethods;
  }

  public Map<ITestClass, Set<Object>> getInvokedAfterClassMethods() {
    return afterClassMethods;
  }

  public void clear() {
    for (Set<Object> instances : beforeClassMethods.values()) {
      instances.clear();
    }
    for (Set<Object> instances : afterClassMethods.values()) {
      instances.clear();
    }
    beforeClassMethods.clear();
    afterClassMethods.clear();
  }
}
