/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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 org.apache.druid.segment;

import it.unimi.dsi.fastutil.ints.IntIterator;
import it.unimi.dsi.fastutil.ints.IntIterators;
import it.unimi.dsi.fastutil.longs.LongHeaps;
import org.apache.druid.java.util.common.IAE;

import java.util.List;
import java.util.NoSuchElementException;

public final class IntIteratorUtils
{
  /**
   * Implements {@link IntIterator#skip(int)}.
   */
  public static int skip(IntIterator it, int n)
  {
    if (n < 0) {
      throw new IAE("n: " + n);
    }
    int skipped = 0;
    while (skipped < n && it.hasNext()) {
      it.nextInt();
      skipped++;
    }
    return skipped;
  }

  /**
   * Merges several iterators of ascending {@code int} values into a single iterator of ascending {@code int} values.
   * It isn't checked if the given source iterators are actually ascending, if they are not, the order of values in the
   * returned iterator is undefined.
   * <p>
   * This is similar to what {@link org.apache.druid.java.util.common.guava.MergeIterator} does with simple
   * {@link java.util.Iterator}s.
   *
   * @param iterators iterators to merge, must return ascending values
   */
  public static IntIterator mergeAscending(List<IntIterator> iterators)
  {
    if (iterators.isEmpty()) {
      return IntIterators.EMPTY_ITERATOR;
    }
    if (iterators.size() == 1) {
      return iterators.get(0);
    }
    return new MergeIntIterator(iterators);
  }

  /**
   * This class is designed mostly after {@link org.apache.druid.java.util.common.guava.MergeIterator}.
   * {@code MergeIterator} uses a priority queue of wrapper
   * "peeking" iterators. Peeking wrappers are not available in fastutil for specialized iterators like IntIterator, so
   * they should be implemented manually in the druid codebase. Instead, another approach is taken: a priority queue
   * of primitive long values is used, in long values the high 32-bits is the last value from some iterator, and the low
   * 32 bits is the index of this iterator in the given list (copied to array, to avoid indirection during the
   * iteration) of source iterators. Since values are in the high bits, the composed longs are still compared using the
   * natural order. So this approach avoids indirections and implementing PeekingIntIterator.
   * <p>
   * Instead of {@link it.unimi.dsi.fastutil.longs.LongHeapPriorityQueue}, a priority queue is implemented on lower
   * level, to avoid heap array shuffling on each iteration with dequeue and than enqueue, when merged iterators tend
   * to stay in the head of the heap for at least several iterations.
   */
  static final class MergeIntIterator implements IntIterator
  {

    private final IntIterator[] iterators;
    private final long[] pQueue;
    private int pQueueSize;

    private static long makeQueueElement(int value, int index)
    {
      // Don't have to mask index because this is a Java array index => positive => no sign bit extension
      return index | (((long) value) << 32);
    }

    private static int value(long queueElement)
    {
      return (int) (queueElement >>> 32);
    }

    private static int iterIndex(long queueElement)
    {
      return (int) queueElement;
    }

    MergeIntIterator(List<IntIterator> iterators)
    {
      this.iterators = iterators.toArray(new IntIterator[0]);
      pQueue = new long[iterators.size()];
      pQueueSize = 0;

      for (int iterIndex = 0; iterIndex < this.iterators.length; iterIndex++) {
        IntIterator iter = this.iterators[iterIndex];
        if (iter != null && iter.hasNext()) {
          pQueue[pQueueSize] = makeQueueElement(iter.nextInt(), iterIndex);
          pQueueSize++;
          LongHeaps.upHeap(pQueue, pQueueSize, pQueueSize - 1, null);
        }
      }

    }

    @Override
    public boolean hasNext()
    {
      return pQueueSize != 0;
    }

    @Override
    public int nextInt()
    {
      if (!hasNext()) {
        throw new NoSuchElementException();
      }

      long queueHead = pQueue[0];
      int retVal = value(queueHead);
      int iterIndex = iterIndex(queueHead);
      IntIterator retIt = iterators[iterIndex];

      if (retIt.hasNext()) {
        // stay in the head, likely no elements will be moved in the heap
        pQueue[0] = makeQueueElement(retIt.nextInt(), iterIndex);
        LongHeaps.downHeap(pQueue, pQueueSize, 0, null);
      } else {
        pQueueSize--;
        if (pQueueSize != 0) {
          pQueue[0] = pQueue[pQueueSize];
          LongHeaps.downHeap(pQueue, pQueueSize, 0, null);
        }
      }

      return retVal;
    }

    @Override
    public int skip(int n)
    {
      return IntIteratorUtils.skip(this, n);
    }
  }

  public static IntIterator fromRoaringBitmapIntIterator(org.roaringbitmap.IntIterator iterator)
  {
    return new RoaringBitmapDelegatingIntIterator(iterator);
  }

  private static class RoaringBitmapDelegatingIntIterator implements IntIterator
  {
    private final org.roaringbitmap.IntIterator delegate;

    private RoaringBitmapDelegatingIntIterator(org.roaringbitmap.IntIterator delegate)
    {
      this.delegate = delegate;
    }

    @Override
    public boolean hasNext()
    {
      return delegate.hasNext();
    }

    @Override
    public int nextInt()
    {
      return delegate.next();
    }

    @Override
    public int skip(int n)
    {
      return IntIteratorUtils.skip(this, n);
    }
  }

  private IntIteratorUtils()
  {
  }
}
