package mu.diff;

import com.google.common.base.MoreObjects;
import com.google.common.collect.Lists;

import java.util.Collection;
import java.util.List;
import java.util.Optional;

/**
 * compare two collections and compute items difference
 *
 * usage: <code>CollectionsDifference.difference(left, right, itemMatcher)</code>
 * ItemMatcher should return one of:
 * None - the item (left, right) do not match
 * Partial - the item match, but is not identical
 * Exact - the item is a perfect match
 *
 */
public class CollectionsDifference {

  public interface ItemMatch {}
  public static class ItemMatchers {
    public static final ItemMatch Exact = new ItemMatchers.Exact();
    public static final ItemMatch None = new ItemMatchers.None();

    public static class Exact implements ItemMatch {}
    public static class None implements ItemMatch {}
    public static class Partial implements ItemMatch {
      private final int rank;

      public Partial(int rank) {
        this.rank = rank;
      }

      public int getRank() {
        return rank;
      }

      @Override
      public String toString() {
        return MoreObjects.toStringHelper(this)
          .add("rank", rank)
          .toString();
      }
    }
  }


  public interface ItemMatcher<L, R> {
    ItemMatch match(L left, R right);
  }

  public static <L, R> CollectionsDifferenceImpl<L, R> difference(Collection<L> left, Collection<R> right, ItemMatcher<L, R> matcher) {
    List<L> itemsOnlyOnLeft = Lists.newArrayList();
    List<R> itemsOnlyOnRight = Lists.newArrayList();
    List<ItemDifference<L, R>> itemsOnBothWithExactMatch = Lists.newArrayList();
    List<ItemDifference<L, R>> itemsOnBothWithPartialMatch = Lists.newArrayList();
    doDifference(left, right, matcher, itemsOnlyOnLeft, itemsOnlyOnRight, itemsOnBothWithExactMatch, itemsOnBothWithPartialMatch);
    return new CollectionsDifferenceImpl<>(itemsOnlyOnLeft, itemsOnlyOnRight, itemsOnBothWithExactMatch, itemsOnBothWithPartialMatch);
  }

  private static <L, R> void doDifference(Collection<L> left, Collection<R> right, ItemMatcher<L, R> matcher, List<L> itemsOnlyOnLeft, List<R> itemsOnlyOnRight, List<ItemDifference<L, R>> itemsOnBothWithExactMatch, List<ItemDifference<L, R>> itemsOnBothWithPartialMatch) {
    List<L> itemsToProcessOnLeft = Lists.newArrayList(left);
    List<R> itemsToProcessOnRight = Lists.newArrayList(right);
    for (L leftItem : Lists.newArrayList(left)) {
      Optional<R> rightItem = getItemByExactMatch(leftItem, right, matcher);
      if (rightItem.isPresent()) {
        itemsToProcessOnLeft.remove(leftItem);
        itemsToProcessOnRight.remove(rightItem.get());
        itemsOnBothWithExactMatch.add(new ItemDifference<>(leftItem, rightItem.get()));
      }
    }
    for (L leftItem : Lists.newArrayList(itemsToProcessOnLeft)) {
      Optional<R> rightItem = getItemByPartialMatch(leftItem, itemsToProcessOnRight, matcher);
      if (rightItem.isPresent()) {
          itemsToProcessOnLeft.remove(leftItem);
          itemsToProcessOnRight.remove(rightItem.get());
          itemsOnBothWithPartialMatch.add(new ItemDifference<>(leftItem, rightItem.get()));
        }
    }
    itemsOnlyOnLeft.addAll(itemsToProcessOnLeft);
    itemsOnlyOnRight.addAll(itemsToProcessOnRight);
  }

  private static <R, L> Optional<R> getItemByExactMatch(L item, Collection<R> collection, ItemMatcher<L, R> matcher) {
    for (R itemToCompare : collection) {
      if (matcher.match(item, itemToCompare) == ItemMatchers.Exact) {
        return Optional.of(itemToCompare);
      }
    }
    return Optional.empty();
  }
  private static <R, L> Optional<R> getItemByPartialMatch(L item, Collection<R> collection, ItemMatcher<L, R> matcher) {
    int maxRank = Integer.MIN_VALUE;
    Optional<R> selectedItem = Optional.empty();
    for (R itemToCompare : collection) {
      ItemMatch matchResult = matcher.match(item, itemToCompare);
      if (matchResult instanceof ItemMatchers.Partial && ((ItemMatchers.Partial) matchResult).getRank() > maxRank) {
        maxRank = ((ItemMatchers.Partial) matchResult).getRank();
        selectedItem = Optional.of(itemToCompare);
      }
    }
    return selectedItem;
  }

}

@@ -147,16 +147,42 @@
 l.List;%0A
+import java.util.Objects;%0A
 import j
@@ -960,16 +960,66 @@
  rank;%0A%0A
+      public Partial() %7B%0A        this(0);%0A      %7D%0A
       pu
@@ -1285,32 +1285,367 @@
 tring();%0A      %7D
+%0A%0A      @Override%0A      public boolean equals(Object o) %7B%0A        if (this == o) return true;%0A        if (o == null %7C%7C getClass() != o.getClass()) return false;%0A        Partial partial = (Partial) o;%0A        return rank == partial.rank;%0A      %7D%0A%0A      @Override%0A      public int hashCode() %7B%0A%0A        return Objects.hash(rank);%0A      %7D
 %0A    %7D%0A  %7D%0A%0A%0A  p

Add equals method to Partial