/*
 * 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.uima.ruta;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import org.apache.uima.cas.CAS;
import org.apache.uima.cas.ConstraintFactory;
import org.apache.uima.cas.FSIntConstraint;
import org.apache.uima.cas.FSIterator;
import org.apache.uima.cas.FSMatchConstraint;
import org.apache.uima.cas.FeaturePath;
import org.apache.uima.cas.Type;
import org.apache.uima.cas.text.AnnotationFS;
import org.apache.uima.ruta.constraint.BasicTypeConstraint;
import org.apache.uima.ruta.constraint.NotConstraint;

public class FilterManager {

  private final Collection<Type> defaultFilterTypes;

  private final FSMatchConstraint additionalWindow;

  private final AnnotationFS windowAnnotation;

  private final Type windowType;

  private Collection<Type> currentFilterTypes;

  private Collection<Type> currentRetainTypes;

  private ConstraintFactory cf;

  private final CAS cas;

  private Set<Type> currentHiddenTypes;

  private boolean emptyIsInvisible;

  public FilterManager(Collection<Type> defaultFilterTypes, boolean emptyIsInvisible, CAS cas) {
    super();
    this.defaultFilterTypes = defaultFilterTypes;

    currentFilterTypes = new ArrayList<Type>();
    currentRetainTypes = new ArrayList<Type>();

    cf = cas.getConstraintFactory();

    this.windowAnnotation = null;
    this.windowType = null;
    this.additionalWindow = null;
    this.emptyIsInvisible = emptyIsInvisible;

    this.cas = cas;
  }

  public FilterManager(Collection<Type> defaultFilterTypes, Collection<Type> filterTypes,
          Collection<Type> retainTypes, AnnotationFS windowAnnotation, Type windowType,
          boolean emptyIsInvisible, CAS cas) {
    super();
    this.defaultFilterTypes = defaultFilterTypes;

    currentFilterTypes = new ArrayList<Type>(filterTypes);
    currentRetainTypes = new ArrayList<Type>(retainTypes);

    cf = cas.getConstraintFactory();

    this.windowAnnotation = windowAnnotation;
    this.windowType = windowType;
    this.additionalWindow = createWindowConstraint(windowAnnotation, cas);
    this.emptyIsInvisible = emptyIsInvisible;

    this.cas = cas;
  }

  private FSMatchConstraint createWindowConstraint(AnnotationFS windowAnnotation, CAS cas) {
    if (windowAnnotation == null)
      return null;
    FeaturePath beginFP = cas.createFeaturePath();
    Type type = windowAnnotation.getType();
    beginFP.addFeature(type.getFeatureByBaseName("begin"));
    FSIntConstraint intConstraint = cf.createIntConstraint();
    intConstraint.geq(windowAnnotation.getBegin());
    FSMatchConstraint beginConstraint = cf.embedConstraint(beginFP, intConstraint);

    FeaturePath endFP = cas.createFeaturePath();
    endFP.addFeature(type.getFeatureByBaseName("end"));
    intConstraint = cf.createIntConstraint();
    intConstraint.leq(windowAnnotation.getEnd());
    FSMatchConstraint endConstraint = cf.embedConstraint(endFP, intConstraint);

    FSMatchConstraint windowConstraint = cf.and(beginConstraint, endConstraint);
    return windowConstraint;
  }

  public FSMatchConstraint getDefaultConstraint() {
    return createCurrentConstraint(true);
  }

  private FSMatchConstraint createCurrentConstraint(boolean windowConstraint) {
    Set<Type> filterTypes = new HashSet<Type>();
    filterTypes.addAll(defaultFilterTypes);
    filterTypes.addAll(currentFilterTypes);
    filterTypes.removeAll(currentRetainTypes);
    for (Type type : currentRetainTypes) {
      if (type != null) {
        List<Type> subsumedTypes = cas.getTypeSystem().getProperlySubsumedTypes(type);
        filterTypes.removeAll(subsumedTypes);
      }
    }
    currentHiddenTypes = filterTypes;
    FSMatchConstraint typeConstraint = createTypeConstraint(filterTypes);

    FSMatchConstraint constraint = new NotConstraint(typeConstraint);
    if (additionalWindow != null && windowConstraint) {
      constraint = cf.and(additionalWindow, constraint);
    }
    return constraint;
  }

  private FSMatchConstraint createTypeConstraint(Collection<Type> types) {
    BasicTypeConstraint result = new BasicTypeConstraint(types, emptyIsInvisible);
    return result;
  }

  public void retainTypes(List<Type> list) {
    currentRetainTypes = list;
  }

  public void filterTypes(List<Type> list) {
    currentFilterTypes = list;
  }

  public void addFilterTypes(List<Type> types) {
    currentFilterTypes.addAll(types);
  }

  public void addRetainTypes(List<Type> types) {
    currentRetainTypes.addAll(types);
  }

  public void removeFilterTypes(List<Type> types) {
    currentFilterTypes.removeAll(types);
  }

  public void removeRetainTypes(List<Type> types) {
    currentRetainTypes.removeAll(types);
  }

  public Collection<Type> getDefaultFilterTypes() {
    return defaultFilterTypes;
  }

  public FSMatchConstraint getAdditionalWindow() {
    return additionalWindow;
  }

  public Collection<Type> getCurrentFilterTypes() {
    return currentFilterTypes;
  }

  public Collection<Type> getCurrentRetainTypes() {
    return currentRetainTypes;
  }

  public AnnotationFS getWindowAnnotation() {
    return windowAnnotation;
  }

  public Type getWindowType() {
    return windowType;
  }

  public FSIterator<AnnotationFS> createFilteredIterator(CAS cas, Type basicType) {
    if (windowAnnotation != null) {
      // TODO: UIMA-6281 replace select
      FSIterator<AnnotationFS> windowIt = cas.getAnnotationIndex(basicType).select()
              .coveredBy(windowAnnotation).fsIterator();
//     was: FSIterator<AnnotationFS> windowIt = cas.getAnnotationIndex(basicType)
//              .subiterator(windowAnnotation);
      FSIterator<AnnotationFS> iterator = cas.createFilteredIterator(windowIt,
              createCurrentConstraint(false));
      return iterator;
    } else {
      FSIterator<AnnotationFS> iterator = cas.createFilteredIterator(
              cas.getAnnotationIndex(basicType).iterator(), createCurrentConstraint(false));
      return iterator;
    }
  }

  public Set<Type> getCurrentHiddenTypes() {
    return currentHiddenTypes;
  }

}
