package org.infinispan.query.core.impl;

import java.util.Arrays;
import java.util.Comparator;
import java.util.Iterator;
import java.util.Map;
import java.util.NoSuchElementException;

import org.infinispan.AdvancedCache;
import org.infinispan.commons.util.CloseableIterator;
import org.infinispan.objectfilter.ObjectFilter;
import org.infinispan.query.dsl.Query;
import org.infinispan.query.dsl.QueryFactory;

/**
 * A non-indexed query performed on top of the results returned by another query (usually a Lucene based query). This
 * mechanism is used to implement hybrid two-stage queries that perform an index query using a partial query using only
 * the indexed fields and then filter the result again in memory with the full filter.
 *
 * @author anistor@redhat.com
 * @since 8.0
 */
public class HybridQuery extends BaseEmbeddedQuery {

   protected final ObjectFilter objectFilter;

   protected final Query baseQuery;

   public HybridQuery(QueryFactory queryFactory, AdvancedCache<?, ?> cache, String queryString, Map<String, Object> namedParameters,
                      ObjectFilter objectFilter,
                      long startOffset, int maxResults,
                      Query baseQuery) {
      super(queryFactory, cache, queryString, namedParameters, objectFilter.getProjection(), startOffset, maxResults);
      this.objectFilter = objectFilter;
      this.baseQuery = baseQuery;
   }

   @Override
   protected Comparator<Comparable[]> getComparator() {
      return objectFilter.getComparator();
   }

   @Override
   protected CloseableIterator<ObjectFilter.FilterResult> getIterator() {
      return new CloseableIterator<ObjectFilter.FilterResult>() {

         private final Iterator<?> it = getBaseIterator();

         private ObjectFilter.FilterResult nextResult = null;

         private boolean isReady = false;

         @Override
         public void close() {
         }

         @Override
         public boolean hasNext() {
            updateNext();
            return nextResult != null;
         }

         @Override
         public ObjectFilter.FilterResult next() {
            updateNext();
            if (nextResult != null) {
               ObjectFilter.FilterResult next = nextResult;
               isReady = false;
               nextResult = null;
               return next;
            } else {
               throw new NoSuchElementException();
            }
         }

         private void updateNext() {
            if (!isReady) {
               while (it.hasNext()) {
                  Object next = it.next();
                  nextResult = objectFilter.filter(next);
                  if (nextResult != null) {
                     break;
                  }
               }
               isReady = true;
            }
         }
      };
   }

   protected Iterator<?> getBaseIterator() {
      return baseQuery.list().iterator();
   }

   @Override
   public String toString() {
      return "HybridQuery{" +
            "queryString=" + queryString +
            ", namedParameters=" + namedParameters +
            ", projection=" + Arrays.toString(projection) +
            ", startOffset=" + startOffset +
            ", maxResults=" + maxResults +
            ", baseQuery=" + baseQuery +
            '}';
   }
}
