/*
 * Copyright 2003-2016 JetBrains s.r.o.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package jetbrains.mps.aspects;

import jetbrains.mps.util.containers.MultiMap;
import jetbrains.mps.util.containers.SetBasedMultiMap;
import org.apache.log4j.Logger;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
import java.util.List;

public class InOrderSorter<T extends OrderParticipant<String>> {
  private Collection<T> myItems;

  public InOrderSorter(Collection<T> items) {
    myItems = items;
  }

  public static final Logger LOG = Logger.getLogger(InOrderSorter.class);

  public List<T> sort() {
    Comparator<T> sourceIndependent = (o1, o2) -> {
      int r1 = o1.compareTo(o2);
      int r2 = o2.compareTo(o1);

      return Math.max(r1, -r2);
    };

    MultiMap<T, T> greater = new SetBasedMultiMap<T, T>();
    for (T item1: myItems) {
      for (T item2: myItems) {
        if (sourceIndependent.compare(item1, item2) > 0) {
          greater.putValue(item1, item2);
        }
      }
    }
    {
      boolean changed;
      do {
        changed = false;
        for (T item1 : myItems) {
          for (T item2 : new ArrayList<T>(greater.get(item1))) {
            Collection<T> toAdd = greater.get(item2);
            changed |= !greater.get(item1).containsAll(toAdd);
            greater.putValues(item1, toAdd);
          }
        }
      } while (changed);
    }
    ArrayList<T> items = new ArrayList<>(myItems);
    items.sort(Comparator.comparing(OrderParticipant::getId));
    ArrayList<T> sorted = new ArrayList<>();
    while (!items.isEmpty()) {
      boolean changed = false;
      for (T candidate : items) {
        if (sorted.containsAll(greater.get(candidate))) {
          sorted.add(candidate);
          items.remove(candidate);
          changed = true;
          break;
        }
      }
      if (!changed) {
        LOG.error("aspects sorting conflict: " + items);
        sorted.addAll(items);
        items.clear();
      }
    }
    return sorted;
  }
}
