package org.infinispan.query.dsl.embedded.impl;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import org.infinispan.objectfilter.impl.syntax.parser.FilterParsingResult;
import org.infinispan.query.CacheQuery;
import org.infinispan.query.dsl.QueryFactory;
import org.infinispan.query.dsl.impl.BaseQuery;


/**
 * A query implementation based on Lucene.
 *
 * @author anistor@redhat.com
 * @since 6.0
 */
final class EmbeddedLuceneQuery<TypeMetadata> extends BaseQuery {

   private final QueryEngine<TypeMetadata> queryEngine;

   private final ResultProcessor resultProcessor;

   private final FilterParsingResult<TypeMetadata> parsingResult;

   /**
    * An Infinispan Cache query that wraps an actual Lucene query object. This is built lazily when the query is
    * executed first.
    */
   private CacheQuery<Object> cacheQuery;

   /**
    * The cached results, lazily evaluated.
    */
   private List<Object> results;

   EmbeddedLuceneQuery(QueryEngine<TypeMetadata> queryEngine, QueryFactory queryFactory,
                       Map<String, Object> namedParameters, FilterParsingResult<TypeMetadata> parsingResult,
                       String[] projection, ResultProcessor resultProcessor,
                       long startOffset, int maxResults) {
      super(queryFactory, parsingResult.getQueryString(), namedParameters, projection, startOffset, maxResults);
      if (resultProcessor instanceof RowProcessor && (projection == null || projection.length == 0)) {
         throw new IllegalArgumentException("A RowProcessor can only be specified with projections");
      }
      this.queryEngine = queryEngine;
      this.resultProcessor = resultProcessor;
      this.parsingResult = parsingResult;
   }

   @Override
   public void resetQuery() {
      results = null;
      cacheQuery = null;
   }

   private CacheQuery<Object> createCacheQuery() {
      // query is created first time only
      if (cacheQuery == null) {
         validateNamedParameters();
         cacheQuery = queryEngine.buildLuceneQuery(parsingResult, namedParameters, startOffset, maxResults);
      }
      return cacheQuery;
   }

   @Override
   @SuppressWarnings("unchecked")
   public <T> List<T> list() {
      if (results == null) {
         results = listInternal();
      }
      return (List<T>) results;
   }

   private List<Object> listInternal() {
      List<Object> list = createCacheQuery().list();
      if (resultProcessor != null) {
         results = new ArrayList<>(list.size());
         list.forEach(r -> results.add(resultProcessor.process(r)));
      } else {
         results = list;
      }
      return results;
   }

   @Override
   public int getResultSize() {
      return createCacheQuery().getResultSize();
   }

   @Override
   public String toString() {
      return "EmbeddedLuceneQuery{" +
            "queryString=" + queryString +
            ", namedParameters=" + namedParameters +
            '}';
   }
}
