/*
 * Copyright (C) 1998-2018  Gerwin Klein <lsf@jflex.de>
 * SPDX-License-Identifier: BSD-3-Clause
 */
package jflex.state;

import java.util.NoSuchElementException;
import java.util.PrimitiveIterator;
import jflex.logging.Out;

/**
 * Enumerates the states of a {@link StateSet}. Also provides an iterator for native int.
 *
 * @author Gerwin Klein
 * @version JFlex 1.9.1
 * @see StateSet
 */
public final class StateSetEnumerator implements PrimitiveIterator.OfInt {

  /** Local compile-time DEBUG flag */
  private static final boolean DEBUG = false;

  /**
   * Current index into the StateSet array. {@code index >= bits.length} indicates that there are no
   * further elements in the set.
   */
  private int index;
  /** Current offset into the StateSet array */
  private int offset;
  /** {@code mask = 1 << offset} */
  private long mask;

  /** Reference to the array of the StateSet to iterate over */
  private long[] bits;

  /**
   * Creates a new StateSetEnumerator that is not yet associated with a StateSet. {@link
   * #hasMoreElements()} and {@link #nextElement()} will throw {@link NullPointerException} when
   * used before {@link #reset(StateSet)}
   */
  public StateSetEnumerator() {
    this.bits = new long[0];
  }

  /**
   * Construct a StateSetEnumerator for a given StateSet. This should be the default constructor to
   * use.
   *
   * @param states the {@link StateSet} object to iterate over.
   * @see StateSet#states()
   */
  public StateSetEnumerator(StateSet states) {
    this.bits = states.bits;
    reset(states);
  }

  /**
   * Reset this enumerator/iterator and associate it with a given StateSet.
   *
   * @param states the {@link StateSet} object to iterate over.
   */
  public void reset(StateSet states) {
    this.bits = states.bits;
    this.index = 0;
    this.offset = 0;
    this.mask = 1;

    // find the first index with elements in the array (= first non-zero bits[index])
    while (index < bits.length && bits[index] == 0) index++;

    // if there are none, the set is empty
    if (index >= bits.length) return;

    // find the first non-zero bit in bits[index]
    while (offset <= StateSet.MASK && (bits[index] & mask) == 0) {
      mask <<= 1;
      offset++;
    }
  }

  /**
   * Advance to the next element in the set.
   *
   * <p>Precondition: there are more elements in the set.
   */
  private void advance() {

    if (DEBUG) Out.dump("Advancing, at start, index = " + index + ", offset = " + offset);

    // cache fields in local variable for faster access
    int _index = this.index;
    int _offset = this.offset;
    long _mask = this.mask;
    long[] _bits = this.bits;

    long bi = _bits[_index];

    // check if there are further bits set at the current index
    do {
      _offset++;
      _mask <<= 1;
    } while (_offset <= StateSet.MASK && ((bi & _mask) == 0));

    // if there are no further bits set at the current index
    if (_offset > StateSet.MASK) {
      int length = _bits.length;

      // find next index with elements
      do _index++;
      while (_index < length && _bits[_index] == 0);

      // if there are none, there were no further elements
      if (_index >= length) {
        this.index = length; // indicates "no more elements"
        return;
      }

      // search for first non-zero bit in bits[index]
      _offset = 0;
      _mask = 1;
      bi = _bits[_index];

      // terminates, because bi != 0
      while ((bi & _mask) == 0) {
        _mask <<= 1;
        _offset++;
      }
    }

    // write back cached values
    this.index = _index;
    this.mask = _mask;
    this.offset = _offset;
  }

  /**
   * Determine if there are further elements in the set to be returned.
   *
   * @return true iff there are more elements in the set.
   */
  public boolean hasMoreElements() {
    if (DEBUG) {
      Out.dump("hasMoreElements, index = " + index + ", offset = " + offset);
    }
    return index < bits.length;
  }

  /**
   * Return the next element from the set.
   *
   * <p>Precondition: {@link #hasMoreElements()} returns true
   *
   * @return the next element.
   * @exception NoSuchElementException if there is no further element
   * @see #hasMoreElements()
   */
  public int nextElement() {
    if (DEBUG) {
      Out.dump("nextElement, index = " + index + ", offset = " + offset);
    }
    if (index >= bits.length) throw new NoSuchElementException();
    int x = (index << StateSet.BITS) + offset;
    advance();
    return x;
  }

  /** Iterator interface method for {@link #nextElement()}. */
  @Override
  public boolean hasNext() {
    return hasMoreElements();
  }

  /** Iterator interface method for {@link #hasMoreElements()} */
  @Override
  public int nextInt() {
    return nextElement();
  }
}
