package org.infinispan.objectfilter.impl;

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

import org.infinispan.objectfilter.FilterCallback;
import org.infinispan.objectfilter.FilterSubscription;
import org.infinispan.objectfilter.SortField;
import org.infinispan.objectfilter.impl.predicateindex.PredicateIndex;
import org.infinispan.objectfilter.impl.predicateindex.be.BETree;
import org.infinispan.objectfilter.impl.predicateindex.be.BETreeMaker;
import org.infinispan.objectfilter.impl.syntax.BooleanExpr;
import org.infinispan.objectfilter.impl.syntax.BooleanFilterNormalizer;
import org.infinispan.objectfilter.impl.util.StringHelper;

/**
 * A registry for filters on the same type of entity.
 *
 * @author anistor@redhat.com
 * @since 7.0
 */
public final class FilterRegistry<TypeMetadata, AttributeMetadata, AttributeId extends Comparable<AttributeId>> {

   private final PredicateIndex<AttributeMetadata, AttributeId> predicateIndex;

   private final List<FilterSubscriptionImpl> filterSubscriptions = new ArrayList<>();

   private final BooleanFilterNormalizer booleanFilterNormalizer = new BooleanFilterNormalizer();

   private final BETreeMaker<AttributeId> treeMaker;

   private final MetadataAdapter<TypeMetadata, AttributeMetadata, AttributeId> metadataAdapter;

   private final boolean useIntervals;

   public FilterRegistry(MetadataAdapter<TypeMetadata, AttributeMetadata, AttributeId> metadataAdapter, boolean useIntervals) {
      this.metadataAdapter = metadataAdapter;
      this.useIntervals = useIntervals;
      treeMaker = new BETreeMaker<>(metadataAdapter, useIntervals);
      predicateIndex = new PredicateIndex<>(metadataAdapter);
   }

   public MetadataAdapter<TypeMetadata, AttributeMetadata, AttributeId> getMetadataAdapter() {
      return metadataAdapter;
   }

   public PredicateIndex<AttributeMetadata, AttributeId> getPredicateIndex() {
      return predicateIndex;
   }

   public List<FilterSubscriptionImpl> getFilterSubscriptions() {
      return filterSubscriptions;
   }

   public FilterSubscriptionImpl<TypeMetadata, AttributeMetadata, AttributeId> addFilter(String queryString, Map<String, Object> namedParameters, BooleanExpr query, String[] projection, Class<?>[] projectionTypes, SortField[] sortFields, FilterCallback callback, boolean isDeltaFilter, Object[] eventTypes) {
      if (eventTypes != null) {
         if (eventTypes.length == 0) {
            eventTypes = null;
         } else {
            for (Object et : eventTypes) {
               if (et == null) {
                  eventTypes = null;
                  break;
               }
            }
         }
      }

      List<List<AttributeId>> translatedProjections = null;
      if (projection != null && projection.length != 0) {
         translatedProjections = new ArrayList<>(projection.length);
         for (String projectionPath : projection) {
            translatedProjections.add(metadataAdapter.mapPropertyNamePathToFieldIdPath(StringHelper.split(projectionPath)));
         }
      }

      List<List<AttributeId>> translatedSortFields = null;
      if (sortFields != null) {
         // deduplicate sort fields
         LinkedHashMap<String, SortField> sortFieldMap = new LinkedHashMap<>();
         for (SortField sf : sortFields) {
            String path = sf.getPath().asStringPath();
            if (!sortFieldMap.containsKey(path)) {
               sortFieldMap.put(path, sf);
            }
         }
         sortFields = sortFieldMap.values().toArray(new SortField[sortFieldMap.size()]);
         // translate sort field paths
         translatedSortFields = new ArrayList<>(sortFields.length);
         for (SortField sortField : sortFields) {
            translatedSortFields.add(metadataAdapter.mapPropertyNamePathToFieldIdPath(sortField.getPath().asArrayPath()));
         }
      }

      BooleanExpr normalizedQuery = booleanFilterNormalizer.normalize(query);
      BETree beTree = treeMaker.make(normalizedQuery, namedParameters);

      FilterSubscriptionImpl<TypeMetadata, AttributeMetadata, AttributeId> filterSubscription = new FilterSubscriptionImpl<>(queryString, namedParameters, useIntervals, metadataAdapter, beTree, callback, isDeltaFilter, projection, projectionTypes, translatedProjections, sortFields, translatedSortFields, eventTypes);
      filterSubscription.registerProjection(predicateIndex);
      filterSubscription.subscribe(predicateIndex);
      filterSubscription.index = filterSubscriptions.size();
      filterSubscriptions.add(filterSubscription);
      return filterSubscription;
   }

   public void removeFilter(FilterSubscription filterSubscription) {
      FilterSubscriptionImpl<TypeMetadata, AttributeMetadata, AttributeId> filterSubscriptionImpl = (FilterSubscriptionImpl<TypeMetadata, AttributeMetadata, AttributeId>) filterSubscription;
      filterSubscriptionImpl.unregisterProjection(predicateIndex);
      filterSubscriptionImpl.unsubscribe(predicateIndex);
      filterSubscriptions.remove(filterSubscriptionImpl);
      for (int i = filterSubscriptionImpl.index; i < filterSubscriptions.size(); i++) {
         filterSubscriptions.get(i).index--;
      }
      filterSubscriptionImpl.index = -1;
   }
}
