package jetbrains.mps.internal.collections.runtime.impl;

/*Generated by MPS */

import jetbrains.mps.internal.collections.runtime.Sequence;
import jetbrains.mps.internal.collections.runtime.ISequence;
import java.util.Iterator;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.ArrayList;

public class ComparingSequence<U> extends Sequence<U> implements Iterable<U> {
  private final ISequence<? extends U> left;
  private final ISequence<? extends U> right;
  private final ISequence<?> rightGeneric;
  private final Kind kind;
  public ComparingSequence(ISequence<? extends U> left, ISequence<? extends U> right, Kind kind) {
    if (left == null || right == null) {
      throw new NullPointerException();
    }
    this.left = left;
    this.right = right;
    this.rightGeneric = right;
    this.kind = kind;
  }

  public ComparingSequence(Kind kind, ISequence<? extends U> left, ISequence<?> right) {
    if (left == null || right == null) {
      throw new NullPointerException();
    }
    assert kind == Kind.SUBSTRACTION || kind == Kind.INTERSECTION;
    this.left = left;
    this.right = null;
    this.rightGeneric = right;
    this.kind = kind;
  }

  @Override
  public Iterator<U> iterator() {
    return new ComparingIterator();
  }
  public enum Kind {
    UNION(),
    INTERSECTION(),
    SUBSTRACTION(),
    DISJUNCTION();

    private Kind() {
    }
  }
  private class ComparingIterator implements Iterator<U> {
    private CardinalityMap<Object> cardMap = new CardinalityMap<Object>();
    private List<U> cache;
    private Iterator<? extends U> leftIt;
    private Iterator<? extends U> rightIt;
    private U next;
    private HasNextState hasNext = HasNextState.UNKNOWN;
    private ComparingIterator() {
    }
    @Override
    public boolean hasNext() {
      if (leftIt == null && rightIt == null) {
        init();
      }
      if (hasNext.unknown()) {
        moveToNext();
      }
      return hasNext.hasNext();
    }
    @Override
    public U next() {
      if (leftIt == null && rightIt == null) {
        init();
      }
      if (hasNext.unknown()) {
        moveToNext();
      }
      if (!(hasNext.hasNext())) {
        throw new NoSuchElementException();
      }
      return clearNext();
    }
    @Override
    public void remove() {
      throw new UnsupportedOperationException();
    }
    private void init() {
      switch (kind) {
        case SUBSTRACTION:
        case INTERSECTION:
          for (Object o : rightGeneric.toIterable()) {
            cardMap.postInc(o);
          }
          leftIt = left.toIterable().iterator();
          break;
        case UNION:
          leftIt = left.toIterable().iterator();
          rightIt = right.toIterable().iterator();
          break;
        case DISJUNCTION:
          cache = new ArrayList<U>();
          for (U o : right.toIterable()) {
            cardMap.postInc(o);
            cache.add(o);
          }
          leftIt = left.toIterable().iterator();
          rightIt = cache.iterator();
          break;
        default:
          break;
      }
    }
    private void destroy() {
      cardMap.clear();
      if (cache != null) {
        cache.clear();
      }
    }
    private void moveToNext() {
      next = null;
      hasNext = HasNextState.AT_END;
loop:
      do {
        switch (kind) {
          case SUBSTRACTION:
            if (leftIt.hasNext()) {
              U tmp = leftIt.next();
              if (cardMap.postDec(tmp) == 0) {
                setNext(tmp);
                break loop;
              }
            } else {
              break loop;
            }
            break;
          case INTERSECTION:
            if (leftIt.hasNext()) {
              U tmp = leftIt.next();
              if (cardMap.postDec(tmp) > 0) {
                setNext(tmp);
                break loop;
              }
            } else {
              break loop;
            }
            break;
          case UNION:
            if (leftIt.hasNext()) {
              U tmp = leftIt.next();
              cardMap.postInc(tmp);
              setNext(tmp);
              break loop;
            }
            if (rightIt.hasNext()) {
              U tmp = rightIt.next();
              if (cardMap.postDec(tmp) == 0) {
                setNext(tmp);
                break loop;
              }
            } else {
              break loop;
            }
            break;
          case DISJUNCTION:
            if (leftIt.hasNext()) {
              U tmp = leftIt.next();
              if (cardMap.postDec(tmp) == 0) {
                setNext(tmp);
                break loop;
              }
            } else
            if (rightIt.hasNext()) {
              U tmp = rightIt.next();
              if (cardMap.postDec(tmp) > 0) {
                setNext(tmp);
                break loop;
              }
            } else {
              break loop;
            }
            break;
          default:
            break;
        }
      } while (true);
      if (!(hasNext.hasNext())) {
        destroy();
      }
    }
    private U clearNext() {
      U tmp = next;
      next = null;
      hasNext = HasNextState.UNKNOWN;
      return tmp;
    }
    private void setNext(U tmp) {
      next = tmp;
      hasNext = HasNextState.HAS_NEXT;
    }
  }
}
