/*
 * 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.textruler.learner.kep;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.apache.uima.cas.CAS;
import org.apache.uima.cas.ConstraintFactory;
import org.apache.uima.cas.FSIterator;
import org.apache.uima.cas.FSMatchConstraint;
import org.apache.uima.cas.FSTypeConstraint;
import org.apache.uima.cas.FeatureStructure;
import org.apache.uima.cas.Type;
import org.apache.uima.cas.text.AnnotationFS;
import org.apache.uima.ruta.engine.RutaEngine;
import org.apache.uima.ruta.textruler.core.TextRulerBasicLearner;
import org.apache.uima.ruta.textruler.core.TextRulerExample;
import org.apache.uima.ruta.textruler.core.TextRulerExampleDocument;
import org.apache.uima.ruta.textruler.core.TextRulerRuleItem;
import org.apache.uima.ruta.textruler.core.TextRulerRulePattern;
import org.apache.uima.ruta.textruler.core.TextRulerStatisticsCollector;
import org.apache.uima.ruta.textruler.core.TextRulerTarget;
import org.apache.uima.ruta.textruler.core.TextRulerToolkit;
import org.apache.uima.ruta.textruler.extension.TextRulerLearnerDelegate;
import org.apache.uima.ruta.textruler.learner.kep.KEPRuleItemCondition.Condition;

public class KEPLearner extends TextRulerBasicLearner {

  public static final String MAX_EXPAND_RULES = "maxExpandRules";

  public static final String MAX_INFILLER_RULES = "maxInfillerRules";

  public static final String FILLER_WINDOW = "fillerWindow";

  public static final String MAX_FILLER_LENGTH = "maxFillerLength";

  public static final int DEFAULT_MAX_EXPAND_RULES = 50;

  public static final int DEFAULT_MAX_INFILLER_RULES = 10;

  public static final int DEFAULT_FILLER_WINDOW = 5;

  public static final int DEFAULT_MAX_FILLER_LENGTH = 3;

  private int fillerWindow;

  private int maxFillerLength;

  private int maxInfillerRules;

  private int maxExpandRules;

  private Map<String, List<KEPRule>> ruleLists = new HashMap<String, List<KEPRule>>();

  private Map<String, List<KEPRule>> correctionRules = new HashMap<String, List<KEPRule>>();

  private Map<String, List<TextRulerExample>> coveredExamples = new HashMap<String, List<TextRulerExample>>();

  private Map<String, Type> blocks = new HashMap<String, Type>();

  private String[] slotNamesWithBoundaries;

  private Map<String, Boolean> hasPerfectRules = new HashMap<String, Boolean>();

  public KEPLearner(String inputDir, String prePropTMFile, String tmpDir, String[] slotNames,
          Set<String> filterSet, boolean skip, TextRulerLearnerDelegate delegate) {
    super(inputDir, prePropTMFile, tmpDir, slotNames, filterSet, skip, delegate);
    supportBoundaries = true;
  }

  @Override
  protected void doRun() {

    long startTime = System.nanoTime();

    this.exampleDocuments.clearCurrentExamples();
    prepareCachedCASesWithBoundaries();
    this.slotNamesWithBoundaries = new String[slotNames.length * 3];
    for (int i = 0; i < this.slotNames.length; i++) {
      this.slotNamesWithBoundaries[i * 3] = slotNames[i] + TextRulerToolkit.LEFT_BOUNDARY_EXTENSION;
      this.slotNamesWithBoundaries[i * 3 + 1] = slotNames[i]
              + TextRulerToolkit.RIGHT_BOUNDARY_EXTENSION;
      this.slotNamesWithBoundaries[i * 3 + 2] = slotNames[i];

    }
    for (int i = 0; i < this.slotNamesWithBoundaries.length; i++) {
      if (!filterSetWithSlotNames.contains(slotNamesWithBoundaries[i]))
        this.filterSetWithSlotNames.add(slotNamesWithBoundaries[i]);
      initializeMapEntries(this.slotNamesWithBoundaries[i]);
    }
    for (int i = 0; i < slotNamesWithBoundaries.length; i++) {
      String currentSlot = slotNamesWithBoundaries[i];
      if (isCompleteSlot(currentSlot)) {
        if (hasPerfectRules.get(slotNamesWithBoundaries[i - 1])
                && hasPerfectRules.get(slotNamesWithBoundaries[i - 2])) {
          filterSetWithSlotNames.remove(slotNamesWithBoundaries[i]);
          continue;
        }
      }
      runForSlot(currentSlot);
      if (hasPerfectRules.get(currentSlot)) {
        filterSetWithSlotNames.remove(currentSlot);
      }
      if (shouldAbort())
        return;
    }
    removeBadRules();
    for (int i = 0; i < this.slotNamesWithBoundaries.length; i++) {
      List<KEPRule> list = this.ruleLists.get(slotNamesWithBoundaries[i]);
      if (!shouldAbort() && list != null && !list.isEmpty()) {
        this.exampleDocuments.createExamplesForTarget(list.get(0).getTarget());
        if (!hasPerfectRules.get(slotNamesWithBoundaries[i]))
          makeRemovalRules(list.get(0).getTarget());
        list = getOptimalRuleCombination(list);
      }
    }
    removeBadRules();

    long estimatedTime = (System.nanoTime() - startTime) / 1000000000;
    Logger.getLogger(getClass().getName()).log(Level.INFO,
            estimatedTime + " seconds needed to learn all rules");
    sendStatusUpdateToDelegate("Done", TextRulerLearnerState.ML_DONE, true);

  }

  private boolean isCompleteSlot(String slot) {
    return !slot.endsWith(TextRulerToolkit.RIGHT_BOUNDARY_EXTENSION)
            && !slot.endsWith(TextRulerToolkit.LEFT_BOUNDARY_EXTENSION);
  }

  /**
   * Execute algorithms for slot denoted by slotName
   * 
   * @param slotName
   *          the name of a slot
   */
  private void runForSlot(String slotName) {

    sendStatusUpdateToDelegate("Working on " + slotName, TextRulerLearnerState.ML_RUNNING, true);
    TextRulerTarget target = new TextRulerTarget(slotName,
            TextRulerTarget.MLTargetType.SINGLE_WHOLE_SLOT, this);
    this.exampleDocuments.createExamplesForTarget(target);
    if (!shouldAbort())
      blocks.put(slotName, getBlocks());
    if (!shouldAbort())
      learnRules(target);
    this.ruleLists.put(slotName, getOptimalRuleCombination(this.ruleLists.get(slotName)));
    sendStatusUpdateToDelegate(slotName + " done", TextRulerLearnerState.ML_RUNNING, true);
  }

  private Type getBlocks() {
    sendStatusUpdateToDelegate("Searching for Blocks", TextRulerLearnerState.ML_RUNNING, false);
    Map<String, List<TextRulerExample>> exampleMap = new HashMap<String, List<TextRulerExample>>();
    Map<String, Double> lengthMap = new HashMap<String, Double>();
    Map<String, Integer> countMap = new HashMap<String, Integer>();
    for (TextRulerExampleDocument exampleDocument : exampleDocuments.getDocuments()) {
      for (AnnotationFS annotation : exampleDocument.getCAS().getAnnotationIndex()) {
        for (TextRulerExample example : exampleDocument.getPositiveExamples()) {
          if (annotation.getBegin() <= example.getAnnotation().getBegin()
                  && annotation.getEnd() >= example.getAnnotation().getEnd()
                  && annotation.getEnd() - annotation.getBegin() > example.getAnnotation().getEnd()
                          - example.getAnnotation().getBegin()
                  && !filterSetWithSlotNames.contains(annotation.getType().getName())) {
            List<TextRulerExample> list = exampleMap.get(annotation.getType().getName());
            if (list == null)
              list = new ArrayList<TextRulerExample>();
            if (!list.contains(example))
              list.add(example);
            exampleMap.put(annotation.getType().getName(), list);
          }
        }
        double aLength = annotation.getEnd() - annotation.getBegin();
        lengthMap.put(annotation.getType().getName(),
                lengthMap.get(annotation.getType().getName()) == null ? aLength
                        : lengthMap.get(annotation.getType().getName()) + aLength);
        countMap.put(annotation.getType().getName(),
                countMap.get(annotation.getType().getName()) == null ? 1
                        : countMap.get(annotation.getType().getName()) + 1);
      }
    }

    List<Type> result = new ArrayList<Type>();
    for (String typeString : exampleMap.keySet()) {
      if (exampleMap.get(typeString).size() == exampleDocuments.getAllPositiveExamples().size())
        result.add(exampleDocuments.getDocuments().get(0).getCAS().getTypeSystem()
                .getType(typeString));
    }
    double exLength = 0;
    for (TextRulerExample ex : exampleDocuments.getAllPositiveExamples()) {
      exLength += ex.getAnnotation().getEnd() - ex.getAnnotation().getBegin();
    }
    double bestRatio = 0;
    Type bestType = null;
    for (Type type : result) {
      if ((exLength / lengthMap.get(type.getName()) > bestRatio
              && countMap.get(type.getName()) <= exampleDocuments.getAllPositiveExamples().size())
              || (exLength / lengthMap.get(type.getName()) == bestRatio
                      && countMap.get(type.getName()) > countMap.get(bestType.getName())
                      && countMap.get(type.getName()) <= exampleDocuments.getAllPositiveExamples()
                              .size())) {
        bestType = type;
        bestRatio = exLength / lengthMap.get(type.getName());
      }

    }
    sendStatusUpdateToDelegate("Searching for Blocks done", TextRulerLearnerState.ML_RUNNING, true);
    return bestType;
  }

  private void learnRules(TextRulerTarget target) {

    List<KEPRule> ruleList = this.ruleLists.get(target.getSingleSlotTypeName());
    List<TextRulerExample> coveredExamples = this.coveredExamples
            .get(target.getSingleSlotTypeName());
    List<TextRulerExample> positiveExamples = this.exampleDocuments.getAllPositiveExamples();

    for (TextRulerExample e : positiveExamples) {
      if (!coveredExamples.contains(e)) {
        ruleList.addAll(makeInFillerRulesForExample(e));
        // ruleList.addAll(generalizeForRepitition(ruleList));
      }
      for (KEPRule rule : ruleList)
        for (TextRulerExample ex : rule.getCoveringStatistics().getCoveredPositiveExamples())
          if (!coveredExamples.contains(ex))
            coveredExamples.add(ex);

      if (shouldAbort())
        return;
    }
    ruleList.addAll(getCandidateClassificationRules(target));
    ruleList = getBestAndOptimalRules(ruleList);
    ruleList.addAll(makePostFillers(ruleList, true));
    ruleList = getOptimalRuleCombination(ruleList);
    this.ruleLists.put(target.getSingleSlotTypeName(), ruleList);
  }

  private List<KEPRule> makeInFillerRulesForExample(TextRulerExample e) {
    sendStatusUpdateToDelegate(
            "Searching for Infiller Rules for " + e.getTarget().getSingleSlotTypeName()
                    .substring(e.getTarget().getSingleSlotTypeName().lastIndexOf(".") + 1),
            TextRulerLearnerState.ML_RUNNING, false);
    Collection<KEPRule> rules = new HashSet<KEPRule>();

    rules = new HashSet<KEPRule>();
    rules = expandInFillerRules(e, rules, true);
    if (rules.size() > maxInfillerRules) {
      List<KEPRule> list = new ArrayList<KEPRule>(rules);
      Collections.sort(list, new KEPRuleComparator(e.getDocumentCAS()));
      // TODO this is a parameter!
      rules = new HashSet<KEPRule>(list.subList(0, maxInfillerRules));
    }
    List<KEPRule> result = new ArrayList<KEPRule>(rules);
    if (shouldAbort())
      return result;

    testRulesOnDocumentSet(result, exampleDocuments);
    sendStatusUpdateToDelegate(
            "Searching for Infiller Rules for " + e.getTarget().getSingleSlotTypeName().substring(
                    e.getTarget().getSingleSlotTypeName().lastIndexOf(".") + 1) + " done",
            TextRulerLearnerState.ML_RUNNING, true);
    return new ArrayList<KEPRule>(rules);
  }

  private Collection<KEPRule> expandInFillerRules(TextRulerExample e, Collection<KEPRule> rules,
          boolean expanding) {

    if (rules.size() > maxExpandRules) {
      List<KEPRule> list = new ArrayList<KEPRule>(rules);
      Collections.sort(list, new KEPRuleComparator(e.getDocumentCAS()));
      rules = new HashSet<KEPRule>(list.subList(0, maxExpandRules));
    }
    if (!expanding) {
      return rules;
    }

    Collection<KEPRule> expandedRules = new HashSet<KEPRule>();
    if (rules.isEmpty()) {
      List<AnnotationFS> seeds = getAnnotationsStartingAt(e.getDocumentCAS(),
              e.getAnnotation().getBegin(), e.getAnnotation().getEnd());
      if (seeds.isEmpty()) {
        expanding = false;
      }
      for (AnnotationFS each : seeds) {
        KEPRuleItem item = new KEPRuleItem(each);
        KEPRule rule = new KEPRule(this, e.getTarget());
        rule.addInFillerItem(item);
        expandedRules.add(rule);
      }
    } else {
      expanding = false;
      for (KEPRule eachRule : rules) {
        TextRulerRulePattern inFiller = eachRule.getInFiller();
        KEPRuleItem lastItem = (KEPRuleItem) inFiller.get(inFiller.size() - 1);
        int end = lastItem.getEnd();
        if (end >= e.getAnnotation().getEnd()) {
          if (!expandedRules.contains(eachRule)) {
            expandedRules.add(eachRule);
          }
        } else {
          List<AnnotationFS> annotationsStartingAt = getAnnotationsStartingAt(e.getDocumentCAS(),
                  end, e.getAnnotation().getEnd());
          if (annotationsStartingAt.isEmpty() && !expandedRules.contains(eachRule)) {
            expandedRules.add(eachRule);
          } else {
            expanding = true;
            for (AnnotationFS eachFS : annotationsStartingAt) {
              if (eachFS.getType().getName().equals(lastItem.getType().getName())) {
                lastItem.setAnnotation(eachFS);
                lastItem.setMax(lastItem.getMax() + 1).setReluctant(true);
                expandedRules.add(eachRule);
              } else {
                KEPRule newRule = new KEPRule(eachRule);
                KEPRuleItem newItem = new KEPRuleItem(eachFS);
                newRule.addInFillerItem(newItem);
                expandedRules.add(newRule);
              }
            }
          }
        }
      }
    }
    return expandInFillerRules(e, expandedRules, expanding);
  }

  private List<KEPRule> getCandidateClassificationRules(TextRulerTarget target) {
    sendStatusUpdateToDelegate(
            "Searching for Candidate Classification Rules for " + target.getSingleSlotTypeName()
                    .substring(target.getSingleSlotTypeName().lastIndexOf(".") + 1),
            TextRulerLearnerState.ML_RUNNING, false);
    List<KEPRule> result = new ArrayList<KEPRule>();
    List<Type> types = getTokensInNExamples(exampleDocuments.getAllPositiveExamples(),
            exampleDocuments.getAllPositiveExamples().size() / 2, true);

    for (Type type : types) {
      result.add(new KEPRule(this, target).addInFillerItem(new KEPRuleItem(type)));
    }
    testRulesOnDocumentSet(result, exampleDocuments);
    // result = getBestAndOptimalRules(result);
    result = addConditions(result, target);
    sendStatusUpdateToDelegate(
            "Searching for Candidate Classification Rules for " + target.getSingleSlotTypeName()
                    .substring(target.getSingleSlotTypeName().lastIndexOf(".") + 1) + " done",
            TextRulerLearnerState.ML_RUNNING, true);
    return result;
  }

  private List<KEPRule> addConditions(List<KEPRule> rules, TextRulerTarget target) {
    List<KEPRule> result = new ArrayList<KEPRule>();
    List<TextRulerExample> allCoveredExamples = getCoveredExamples(rules);

    List<Type> containedTypes = getTokensInNExamples(exampleDocuments.getAllPositiveExamples(),
            exampleDocuments.getAllPositiveExamples().size() / 3, false);
    if (!containedTypes.isEmpty()) {
      for (KEPRule rule : rules) {
        KEPRuleItem ruleItem = (KEPRuleItem) rule.getInFiller().get(0);
        for (Type type : containedTypes) {
          if (!type.getName().equals(ruleItem.getType().getName())
                  && !ruleItem.containsAndCondition(type)) {
            result.add(new KEPRule(this, target).addInFillerItem(ruleItem.copy()
                    .addAndCondition(new KEPRuleItemCondition(type, Condition.CONTAINS, false))));
          }
        }
      }
    }

    testRulesOnDocumentSet(result, exampleDocuments);
    result = getBestAndOptimalRules(result);
    List<KEPRule> toRefine = new ArrayList<KEPRule>();
    List<KEPRule> toRemove = new ArrayList<KEPRule>();
    for (KEPRule rule : result) {
      if (rule.getCoveringStatistics().getCoveredPositivesCount() == 0)
        toRemove.add(rule);
      else if (rule.getCoveringStatistics().getCoveredNegativesCount() > 0
              && rule.getPostFiller().size() < 5)
        toRefine.add(rule);
    }
    result.removeAll(toRemove);
    result.removeAll(toRefine);
    result = getBestAndOptimalRules(result);
    if (getCoveredExamples(result).size() == allCoveredExamples.size()) {
      return result;
    }
    if (toRefine.size() > 0) {
      result.addAll(addConditions(toRefine, target));
    }
    sendStatusUpdateToDelegate(
            "Adding conditions to rules for " + target.getSingleSlotTypeName()
                    .substring(target.getSingleSlotTypeName().lastIndexOf(".") + 1) + " done",
            TextRulerLearnerState.ML_RUNNING, true);
    return result;
  }

  private List<KEPRule> makePostFillers(List<KEPRule> baseRules, boolean changed) {
    if (!baseRules.isEmpty() && !shouldAbort()) {
      sendStatusUpdateToDelegate(
              "Adding postfillers to rules for "
                      + baseRules.get(0).getTarget().getSingleSlotTypeName()
                              .substring(baseRules.get(0).getTarget().getSingleSlotTypeName()
                                      .lastIndexOf(".") + 1),
              TextRulerLearnerState.ML_RUNNING, true);
    } else {
      return new ArrayList<KEPRule>();
    }
    // List<TextRulerExample> allCoveredExamples = getCoveredExamples(baseRules);
    Set<KEPRule> result = new HashSet<KEPRule>();
    for (KEPRule rule : baseRules) {
      for (TextRulerExample e : rule.getCoveringStatistics().getCoveredPositiveExamples()) {
        if (rule.getCoveringStatistics().getCoveredNegativesCount() > 0) {
          KEPRuleItem lastItem = ((KEPRuleItem) rule.getPostFiller().lastItem());
          int end = e.getAnnotation().getEnd();
          if (lastItem != null) {
            end = lastItem.getEnd();
          }
          List<AnnotationFS> annotations = getAnnotationsStartingAt(e.getDocumentCAS(), end,
                  e.getDocumentCAS().getDocumentText().length());
          boolean blockBoundaryHit = false;
          for (AnnotationFS annotationFS : annotations) {
            if (annotationFS.getType()
                    .equals(blocks.get(baseRules.get(0).getTarget().getSingleSlotTypeName()))) {
              blockBoundaryHit = true;
              break;
            }
          }
          if (blockBoundaryHit) {
            continue;
          }
          for (AnnotationFS annotationFS : annotations) {
            if (annotationFS.getType().getName()
                    .equals(lastItem != null ? lastItem.getType().getName() : null)) {
              lastItem.setReluctant(true).setMax(lastItem.getMax() + 1).setAnnotation(annotationFS);
            } else {
              result.add(rule.copy().addPostFillerItem(new KEPRuleItem(annotationFS)));
            }
            if (rule.getPreFiller().isEmpty())
              result.add(rule);
          }
        } else {
          result.add(rule);
        }
      }
    }
    List<KEPRule> resultList = new ArrayList<KEPRule>(result);
    testRulesOnDocumentSet(resultList, exampleDocuments);
    resultList.addAll(baseRules);
    resultList = getBestAndOptimalRules(resultList);
    if (baseRules.containsAll(resultList)) {
      if (!changed)
        return resultList;
      else
        changed = false;
    } else {
      changed = true;
    }
    resultList.addAll(makePreFillers(resultList, changed));
    sendStatusUpdateToDelegate(
            "Adding postfillers to rules for "
                    + baseRules.get(0).getTarget().getSingleSlotTypeName()
                            .substring(baseRules.get(0).getTarget().getSingleSlotTypeName()
                                    .lastIndexOf(".") + 1)
                    + " done",
            TextRulerLearnerState.ML_RUNNING, true);
    return resultList;
  }

  private List<KEPRule> makePreFillers(List<KEPRule> baseRules, boolean changed) {
    if (!baseRules.isEmpty() && !shouldAbort()) {
      sendStatusUpdateToDelegate(
              "Adding prefillers to rules for "
                      + baseRules.get(0).getTarget().getSingleSlotTypeName()
                              .substring(baseRules.get(0).getTarget().getSingleSlotTypeName()
                                      .lastIndexOf(".") + 1),
              TextRulerLearnerState.ML_RUNNING, true);
    } else {
      return new ArrayList<KEPRule>();
    }
    // List<TextRulerExample> allCoveredExamples = getCoveredExamples(baseRules);
    Set<KEPRule> result = new HashSet<KEPRule>();
    for (KEPRule rule : baseRules) {
      for (TextRulerExample e : rule.getCoveringStatistics().getCoveredPositiveExamples()) {
        if (rule.getCoveringStatistics().getCoveredNegativesCount() > 0) {
          int begin = e.getAnnotation().getBegin();
          KEPRuleItem firstItem = (KEPRuleItem) rule.getPreFiller().firstItem();
          if (firstItem != null) {
            begin = firstItem.getBegin();
          }
          List<AnnotationFS> annotations = getAnnotationsEndingAt(begin, e.getDocumentCAS());
          boolean blockBoundaryHit = false;
          for (AnnotationFS annotationFS : annotations) {
            if (annotationFS.getType()
                    .equals(blocks.get(baseRules.get(0).getTarget().getSingleSlotTypeName()))) {
              blockBoundaryHit = true;
              break;
            }
          }
          if (blockBoundaryHit) {
            continue;
          }
          for (AnnotationFS annotationFS : annotations) {
            if (annotationFS.getType().getName()
                    .equals(firstItem != null ? firstItem.getType().getName() : null)) {
              firstItem.setReluctant(true).setMax(firstItem.getMax() + 1)
                      .setAnnotation(annotationFS);
            } else {
              result.add(rule.copy().addPreFillerItem(new KEPRuleItem(annotationFS)));
            }
          }
        } else {
          result.add(rule);
        }
      }
    }
    List<KEPRule> resultList = new ArrayList<KEPRule>(result);
    testRulesOnDocumentSet(resultList, exampleDocuments);
    resultList.addAll(baseRules);
    resultList = getBestAndOptimalRules(resultList);
    if (baseRules.containsAll(resultList)) {
      if (!changed)
        return resultList;
      else
        changed = false;
    } else {
      changed = true;
    }
    resultList.addAll(makePostFillers(resultList, changed));

    sendStatusUpdateToDelegate(
            "Adding prefillers to rules for "
                    + baseRules.get(0).getTarget().getSingleSlotTypeName()
                            .substring(baseRules.get(0).getTarget().getSingleSlotTypeName()
                                    .lastIndexOf(".") + 1)
                    + " done",
            TextRulerLearnerState.ML_RUNNING, true);
    return resultList;
  }

  private void removeBadRules() {
    for (int i = 0; i < slotNames.length; i++) {
      if (!hasPerfectRules.get(slotNames[i])
              && hasPerfectRules.get(slotNamesWithBoundaries[3 * i + 1])
              && hasPerfectRules.get(slotNamesWithBoundaries[3 * i + 2])) {
        List<KEPRule> list = new ArrayList<KEPRule>();
        for (KEPRule kepRule : ruleLists.get(slotNames[i])) {
          List<TextRulerExample> exList = new ArrayList<TextRulerExample>(
                  kepRule.getCoveringStatistics().getCoveredNegativeExamples());
          exList.removeAll(getCorrectedExamples(slotNames[i]));
          if (exList.size() == 0) {
            list.add(kepRule);
          }
        }
        ruleLists.put(slotNames[i], list);
      } else {
        if (!hasPerfectRules.get(slotNamesWithBoundaries[3 * i + 1])) {
          List<KEPRule> list = new ArrayList<KEPRule>();
          for (KEPRule kepRule : ruleLists.get(slotNamesWithBoundaries[3 * i + 1])) {
            List<TextRulerExample> exList = new ArrayList<TextRulerExample>(
                    kepRule.getCoveringStatistics().getCoveredNegativeExamples());
            exList.removeAll(getCorrectedExamples(slotNamesWithBoundaries[3 * i + 1]));
            if (exList.size() == 0) {
              list.add(kepRule);
            }
          }
        }
        if (!hasPerfectRules.get(slotNamesWithBoundaries[3 * i + 2])) {
          List<KEPRule> list = new ArrayList<KEPRule>();
          for (KEPRule kepRule : ruleLists.get(slotNamesWithBoundaries[3 * i + 2])) {
            List<TextRulerExample> exList = new ArrayList<TextRulerExample>(
                    kepRule.getCoveringStatistics().getCoveredNegativeExamples());
            exList.removeAll(getCorrectedExamples(slotNamesWithBoundaries[3 * i + 2]));
            if (exList.size() == 0) {
              list.add(kepRule);
            }
          }
        }
      }
    }
  }

  private List<KEPRule> makeRemovalRules(TextRulerTarget target) {
    sendStatusUpdateToDelegate(
            "Searching for Removal Rules for " + target.getSingleSlotTypeName()
                    .substring(target.getSingleSlotTypeName().lastIndexOf(".") + 1),
            TextRulerLearnerState.ML_RUNNING, false);
    if (!hasFalsePositives(target.getSingleSlotTypeName()))
      return new ArrayList<KEPRule>();
    List<KEPRule> result = correctionRules.get(target.getSingleSlotTypeName());
    Type targetType = exampleDocuments.getDocuments().get(0).getCAS().getTypeSystem()
            .getType(target.getSingleSlotTypeName());

    List<Type> containedTypes = getTokensInNExamples(exampleDocuments.getAllPositiveExamples(),
            exampleDocuments.getAllPositiveExamples().size(), false);
    List<Type> notContainedTypes = getTokensInNoExample(exampleDocuments.getAllPositiveExamples());
    // notContainedTypes.retainAll(getTokensInNExamples(getFalsePositives(target), 1, false));
    if (!containedTypes.isEmpty()) {
      KEPRuleItem containsRuleItem = new KEPRuleItem(targetType);
      for (Type type : containedTypes) {
        result.add(new KEPRule(this, target)
                .addInFillerItem(containsRuleItem.copy()
                        .addAndCondition(new KEPRuleItemCondition(type, Condition.CONTAINS, true)))
                .setCorrectionRule(true));
      }
    }
    if (!notContainedTypes.isEmpty()) {
      KEPRuleItem notContainsRuleItem = new KEPRuleItem(targetType);
      for (Type type : notContainedTypes) {
        result.add(new KEPRule(this, target)
                .addInFillerItem(notContainsRuleItem.copy()
                        .addAndCondition(new KEPRuleItemCondition(type, Condition.CONTAINS, false)))
                .setCorrectionRule(true));
      }
    }
    testCorrectionRules(target);
    List<KEPRule> toRemove = new ArrayList<KEPRule>();
    List<KEPRuleItemCondition> toMerge = new ArrayList<KEPRuleItemCondition>();
    for (KEPRule rule : result) {
      if (!(rule.getCoveringStatistics().getCoveredPositivesCount() == 0)
              || !(rule.getCoveringStatistics().getCoveredNegativesCount() > 0)) {
        toRemove.add(rule);
      } else {
        toMerge.addAll(((KEPRuleItem) rule.getInFiller().get(0)).getConditions().get(0));
        toRemove.add(rule);
      }
    }
    result.removeAll(toRemove);
    if (!toMerge.isEmpty()) {
      result.add(new KEPRule(this, target)
              .addInFillerItem(new KEPRuleItem(targetType).addConditions(toMerge))
              .setCorrectionRule(true));
      testCorrectionRules(target);
    }
    sendStatusUpdateToDelegate(
            "Searching for Removal Rules for " + target.getSingleSlotTypeName()
                    .substring(target.getSingleSlotTypeName().lastIndexOf(".") + 1) + " done",
            TextRulerLearnerState.ML_RUNNING, true);
    return result;
  }

  private void initializeMapEntries(String slotName) {
    this.ruleLists.put(slotName, new ArrayList<KEPRule>());
    this.correctionRules.put(slotName, new ArrayList<KEPRule>());
    this.coveredExamples.put(slotName, new ArrayList<TextRulerExample>());
    this.hasPerfectRules.put(slotName, false);
  }

  private List<AnnotationFS> getAnnotationsEndingAt(int end, CAS cas) {
    List<AnnotationFS> result = new ArrayList<AnnotationFS>();
    FSIterator<AnnotationFS> it = cas
            .getAnnotationIndex(cas.getTypeSystem().getType(TextRulerToolkit.RUTA_ALL_TYPE_NAME))
            .iterator();
    while (it.isValid() && it.get().getBegin() < end) {
      it.moveToNext();
    }
    do
      it.moveToPrevious();
    while (it.isValid() && (it.get().getBegin() >= end
            || filterSetWithSlotNames.contains(it.get().getType().getName())));
    if (!it.isValid())
      return result;
    end = it.get().getEnd();
    it = cas.getAnnotationIndex().iterator();
    while (it.isValid() && it.get().getBegin() <= end) {
      if (it.get().getEnd() == end
              && !filterSetWithSlotNames.contains(it.get().getType().getName()))
        result.add(it.get());
      it.moveToNext();
    }
    return result;
  }

  private List<AnnotationFS> getAnnotationsStartingAt(CAS cas, int begin, int till) {
    List<AnnotationFS> result = new ArrayList<AnnotationFS>();
    if (begin > cas.getDocumentText().length()) {
      return new ArrayList<AnnotationFS>();
    }
    if (begin == 0) {
      begin++;
    }
    AnnotationFS pointer = cas.createAnnotation(cas.getAnnotationType(), begin - 1, begin);
    FSIterator<AnnotationFS> it = cas.getAnnotationIndex().iterator(pointer);
    FSMatchConstraint constraint = getConstraint(cas);
    FSIterator<AnnotationFS> iterator = cas.createFilteredIterator(it, constraint);
    iterator.moveTo(pointer);

    int firstBegin = -1;
    while (iterator.isValid()) {
      AnnotationFS fs = iterator.get();
      if (firstBegin == -1 && fs.getBegin() >= begin) {
        firstBegin = fs.getBegin();
      }
      if (firstBegin >= 0) {
        if (fs.getBegin() > firstBegin) {
          break;
        } else if (fs.getBegin() == firstBegin && fs.getEnd() <= till) {
          if (!filterSetWithSlotNames.contains(fs.getType().getName())) {
            result.add(fs);
          }
        }
      }
      iterator.moveToNext();
    }
    return result;
  }

  private List<KEPRule> getOptimalRuleCombination(List<KEPRule> rules) {

    if (rules.isEmpty()) {
      return new ArrayList<KEPRule>();
    }
    List<KEPRule> tmpList = new ArrayList<KEPRule>();
    List<TextRulerExample> coveredExamples = new ArrayList<TextRulerExample>();
    List<TextRulerExample> positiveExamples = exampleDocuments.getAllPositiveExamples();
    List<TextRulerExample> correctedExamples = getCorrectedExamples(
            rules.get(0).getTarget().getSingleSlotTypeName());
    for (KEPRule rule : rules) {
      List<TextRulerExample> uncorrectedExamples = new ArrayList<TextRulerExample>(
              rule.getCoveringStatistics().getCoveredNegativeExamples());
      uncorrectedExamples.removeAll(correctedExamples);
      if (uncorrectedExamples.size() == 0
              && rule.getCoveringStatistics().getCoveredPositivesCount() > 0)
        tmpList.add(rule);
    }
    tmpList = getBestRules(tmpList);
    for (KEPRule rule : tmpList) {
      coveredExamples.addAll(rule.getCoveringStatistics().getCoveredPositiveExamples());
    }
    if (coveredExamples.containsAll(exampleDocuments.getAllPositiveExamples()))
      hasPerfectRules.put(rules.get(0).getTarget().getSingleSlotTypeName(), true);
    else
      hasPerfectRules.put(rules.get(0).getTarget().getSingleSlotTypeName(), false);
    List<KEPRule> bestRules = getBestRules(rules);
    while (!coveredExamples.containsAll(positiveExamples) && !bestRules.isEmpty()) {
      KEPRule bestRule = bestRules.get(0);
      if (!coveredExamples
              .containsAll(bestRule.getCoveringStatistics().getCoveredPositiveExamples())) {
        coveredExamples.removeAll(bestRule.getCoveringStatistics().getCoveredPositiveExamples());
        coveredExamples.addAll(bestRule.getCoveringStatistics().getCoveredPositiveExamples());
        tmpList.add(bestRule);
      }
      bestRules.remove(0);
    }
    return tmpList;

  }

  private List<KEPRule> getBestRules(List<KEPRule> rules) {
    if (rules.isEmpty())
      return new ArrayList<KEPRule>();
    final class AComparator implements Comparator<KEPRule> {
      @Override
      public int compare(KEPRule r1, KEPRule r2) {
        if (r1.getCoveringStatistics().getCoveredPositivesCount() < r2.getCoveringStatistics()
                .getCoveredPositivesCount())
          return 1;
        else if (r1.getCoveringStatistics().getCoveredPositivesCount() > r2.getCoveringStatistics()
                .getCoveredPositivesCount())
          return -1;
        else if (r1.getCoveringStatistics().getCoveredNegativesCount() > r2.getCoveringStatistics()
                .getCoveredNegativesCount())
          return 1;
        else if (r1.getCoveringStatistics().getCoveredNegativesCount() < r2.getCoveringStatistics()
                .getCoveredNegativesCount())
          return -1;
        else if (r1.getPreFiller().size() + r1.getInFiller().size()
                + r1.getPostFiller().size() < r2.getPreFiller().size() + r2.getInFiller().size()
                        + r2.getPostFiller().size())
          return -1;
        return 0;
      }
    }

    Collections.sort(rules, new AComparator());
    List<KEPRule> result = new ArrayList<KEPRule>();
    List<TextRulerExample> positiveExamples = exampleDocuments.getAllPositiveExamples();
    List<TextRulerExample> coveredExamples = new ArrayList<TextRulerExample>();
    for (int i = 0; i < rules.size(); i++) {
      KEPRule rule = rules.get(i);
      if ((3 * rule.getCoveringStatistics().getCoveredPositivesCount() >= rule
              .getCoveringStatistics().getCoveredNegativesCount())
              && (rule.getCoveringStatistics().getCoveredPositivesCount() >= positiveExamples.size()
                      || !coveredExamples.containsAll(
                              rule.getCoveringStatistics().getCoveredPositiveExamples()))) {
        result.add(rule);
        coveredExamples.addAll(rule.getCoveringStatistics().getCoveredPositiveExamples());

        if (coveredExamples.containsAll(positiveExamples))
          return result;
      }
    }
    for (int i = 0; i < rules.size(); i++) {
      KEPRule rule = rules.get(i);
      if (rule.getCoveringStatistics().getCoveredPositivesCount() >= positiveExamples.size()
              || !coveredExamples
                      .containsAll(rule.getCoveringStatistics().getCoveredPositiveExamples())) {
        result.add(rule);
        coveredExamples.addAll(rule.getCoveringStatistics().getCoveredPositiveExamples());

        if (coveredExamples.containsAll(positiveExamples))
          return result;
      }
    }
    return result;
  }

  private List<KEPRule> getBestAndOptimalRules(List<KEPRule> rules) {
    List<KEPRule> result = new ArrayList<KEPRule>();

    result.addAll(getBestRules(rules));
    List<KEPRule> tmp = getOptimalRuleCombination(rules);
    for (KEPRule rule : tmp)
      if (!result.contains(rule))
        result.add(rule);
    return result;
  }

  private List<Type> getTokensInNExamples(List<TextRulerExample> examples, int n,
          boolean countOnlyCoveringTokens) {
    if (examples.isEmpty())
      return new ArrayList<Type>();
    List<Type> result = new ArrayList<Type>();
    Map<String, List<TextRulerExample>> countMap = new HashMap<String, List<TextRulerExample>>();
    for (TextRulerExample example : examples) {
      for (AnnotationFS a : TextRulerToolkit.getAnnotationsWithinBounds(example.getDocumentCAS(),
              example.getAnnotation().getBegin(), example.getAnnotation().getEnd(),
              filterSetWithSlotNames, null)) {
        if (!filterSetWithSlotNames.contains(a.getType().getName()))
          if (((!countOnlyCoveringTokens) && (a.getBegin() >= example.getAnnotation().getBegin()
                  && a.getEnd() <= example.getAnnotation().getEnd()))
                  || (a.getBegin() == example.getAnnotation().getBegin()
                          && a.getEnd() == example.getAnnotation().getEnd())) {
            List<TextRulerExample> list = countMap.get(a.getType().getName());
            if (list == null) {
              list = new ArrayList<TextRulerExample>();
              list.add(example);
            } else if (!list.contains(example))
              list.add(example);
            countMap.put(a.getType().getName(), list);
          }
      }
    }
    for (String typeString : countMap.keySet()) {
      if (countMap.get(typeString).size() >= n)
        result.add(examples.get(0).getDocumentCAS().getTypeSystem().getType(typeString));
    }
    return result;
  }

  private List<Type> getTokensInNoExample(List<TextRulerExample> examples) {
    List<String> types = new ArrayList<String>();
    for (TextRulerExampleDocument doc : exampleDocuments.getDocuments()) {
      for (AnnotationFS a : doc.getCAS().getAnnotationIndex()) {
        if (!types.contains(a.getType().getName())
                && !filterSetWithSlotNames.contains(a.getType().getName()))
          types.add(a.getType().getName());
      }
    }
    List<Type> containedTypes = getTokensInNExamples(examples, 1, false);
    for (Type type : containedTypes) {
      types.remove(type.getName());
    }
    List<Type> result = new ArrayList<Type>();
    for (String typeString : types) {
      result.add(examples.get(0).getDocumentCAS().getTypeSystem().getType(typeString));
    }
    return result;
  }

  @Override
  public String getResultString() {
    StringBuffer ruleStrings = new StringBuffer();
    if (slotNamesWithBoundaries == null || slotNamesWithBoundaries.length == 0)
      return "No results available yet!";

    for (int i = 0; i < slotNamesWithBoundaries.length; i++) {
      List<KEPRule> ruleList = this.ruleLists.get(slotNamesWithBoundaries[i]);
      Type blockType = blocks.get(slotNamesWithBoundaries[i]);
      if (blockType != null
              && !(i > 0 && blocks.get(slotNamesWithBoundaries[i - 1]) != null && blocks
                      .get(slotNamesWithBoundaries[i - 1]).getName().equals(blockType.getName()))) {
        ruleStrings.append(
                "BLOCK(" + blockType.getShortName() + ") " + blockType.getShortName() + "{} { \n");
      }
      if (ruleList == null || ruleList.isEmpty()) {
        if (blockType != null && !(i < slotNamesWithBoundaries.length - 1
                && blocks.get(slotNamesWithBoundaries[i + 1]) != null && blocks
                        .get(slotNamesWithBoundaries[i + 1]).getName().equals(blockType.getName())))
          ruleStrings.append("} \n");
        continue;
      }
      ruleStrings.append("// " + slotNamesWithBoundaries[i] + " RULES \n");
      for (KEPRule rule : new ArrayList<KEPRule>(ruleList)) {
        ruleStrings.append((blockType != null ? "\t" : "") + rule.getRuleString() + "\t// "
                + rule.getCoveringStatistics() + "\n");
      }
      if (blockType != null && !(i < slotNamesWithBoundaries.length - 1
              && blocks.get(slotNamesWithBoundaries[i + 1]) != null
              && blocks.get(slotNamesWithBoundaries[i + 1]).getName().equals(blockType.getName())))
        ruleStrings.append("}");
      ruleStrings.append("\n");
    }
    StringBuffer boundaryCorrectors = new StringBuffer();
    StringBuffer wholeSlotCorrectors = new StringBuffer();
    boundaryCorrectors.append("\n // BOUNDARY CORRECTION RULES: \n");
    wholeSlotCorrectors.append("\n // CORRECTION RULES: \n");
    for (int i = 0; i < slotNamesWithBoundaries.length; i++) {
      List<KEPRule> ruleList = this.correctionRules.get(slotNamesWithBoundaries[i]);
      if (ruleList == null || ruleList.isEmpty())
        continue;
      for (KEPRule rule : ruleList) {
        if (slotNamesWithBoundaries[i].contains(TextRulerToolkit.LEFT_BOUNDARY_EXTENSION)
                || slotNamesWithBoundaries[i].contains(TextRulerToolkit.RIGHT_BOUNDARY_EXTENSION)) {
          boundaryCorrectors
                  .append(rule.getRuleString() + "\t// " + rule.getCoveringStatistics() + "\n");
        } else {
          wholeSlotCorrectors
                  .append(rule.getRuleString() + "\t// " + rule.getCoveringStatistics() + "\n");
        }
      }
    }
    return getFileHeaderString(true) + ruleStrings + boundaryCorrectors + "\n // CONNECTORS: \n"
            + getConnectorsRuleString() + wholeSlotCorrectors;
  }

  private String getAnnotationRulesString(String slotName) {
    StringBuffer result = new StringBuffer();
    result.append(getPackageString());
    result.append("// " + slotName + " RULES \n");
    Type blockType = blocks.get(slotName);
    if (blockType != null) {
      result.append(
              "BLOCK(" + blockType.getShortName() + ") " + blockType.getShortName() + "{} { \n");
    }
    List<KEPRule> ruleList = this.ruleLists.get(slotName);
    if (ruleList != null && !ruleList.isEmpty()) {

      for (KEPRule rule : ruleList) {
        String theRuleString = rule.getRuleString();
        result.append((blockType != null ? "\t" : "") + theRuleString + "\t// "
                + rule.getCoveringStatistics() + "\n");
      }
    }
    if (blockType != null) {
      result.append("}");
    }
    result.append("\n");

    if (!slotName.contains(TextRulerToolkit.LEFT_BOUNDARY_EXTENSION)
            && !slotName.contains(TextRulerToolkit.RIGHT_BOUNDARY_EXTENSION)) {
      result.append(getAnnotationRulesString(slotName + TextRulerToolkit.LEFT_BOUNDARY_EXTENSION));
      result.append(getAnnotationRulesString(slotName + TextRulerToolkit.RIGHT_BOUNDARY_EXTENSION));
      if (blockType != null) {
        result.append("BLOCK(" + blockType.getShortName() + "Correction) "
                + blockType.getShortName() + "{} { \n");
      }
      String shortName = slotName.substring(slotName.lastIndexOf(".") + 1);
      String str = shortName + TextRulerToolkit.LEFT_BOUNDARY_EXTENSION + "{->MARKONCE(" + shortName
              + ",1,3)} ANY*? " + shortName + TextRulerToolkit.RIGHT_BOUNDARY_EXTENSION + ";"
              + "\n";
      str += shortName + TextRulerToolkit.LEFT_BOUNDARY_EXTENSION + "{IS(" + shortName
              + TextRulerToolkit.RIGHT_BOUNDARY_EXTENSION + ")->MARKONCE(" + shortName + ")} " + ";"
              + "\n";
      result.append(str);
      if (blockType != null) {
        result.append("}");
      }
    }

    return result.toString();
  }

  private String getConnectorsRuleString() {

    Map<Type, StringBuffer> connectorBlocks = new HashMap<Type, StringBuffer>();
    StringBuffer noBlockConnectorRules = new StringBuffer();
    StringBuffer result = new StringBuffer();

    for (int i = 0; i < this.slotNames.length; i++) {
      Type slotBlock = blocks.get(slotNames[i]);
      String shortName = slotNames[i].substring(slotNames[i].lastIndexOf(".") + 1);
      String str = (slotBlock == null ? "" : "\t") + shortName
              + TextRulerToolkit.LEFT_BOUNDARY_EXTENSION + "{->MARKONCE(" + shortName
              + ",1,3)} ANY*? " + shortName + TextRulerToolkit.RIGHT_BOUNDARY_EXTENSION + ";"
              + "\n";
      str += (slotBlock == null ? "" : "\t") + shortName + TextRulerToolkit.LEFT_BOUNDARY_EXTENSION
              + "{IS(" + shortName + TextRulerToolkit.RIGHT_BOUNDARY_EXTENSION + ")->MARKONCE("
              + shortName + ")} " + ";" + "\n";
      if (slotBlock != null) {
        if (connectorBlocks.get(slotBlock) == null) {
          connectorBlocks.put(slotBlock, new StringBuffer());
        }
        connectorBlocks.get(slotBlock).append(str);
      } else {
        noBlockConnectorRules.append(str);
      }
    }

    for (Type block : connectorBlocks.keySet()) {
      result.append("BLOCK(" + block.getShortName() + "Connectors) " + block.getShortName()
              + "{} { \n" + connectorBlocks.get(block) + "} \n");
    }
    result.append(noBlockConnectorRules);
    return result.toString();
  }

  private List<TextRulerExample> getCoveredExamples(List<KEPRule> rules) {
    List<TextRulerExample> result = new ArrayList<TextRulerExample>();
    for (KEPRule rule : rules)
      for (TextRulerExample ex : rule.getCoveringStatistics().getCoveredPositiveExamples())
        if (!result.contains(ex))
          result.add(ex);
    return result;
  }

  private List<TextRulerExample> getCorrectedExamples(String slotName) {
    List<TextRulerExample> result = new ArrayList<TextRulerExample>();
    for (KEPRule rule : correctionRules.get(slotName))
      for (TextRulerExample ex : rule.getCoveringStatistics().getCoveredNegativeExamples())
        if (!result.contains(ex))
          result.add(ex);
    return result;
  }

  private boolean hasFalsePositives(String singleSlotTypeName) {
    List<KEPRule> list = this.ruleLists.get(singleSlotTypeName);
    if (list == null || list.isEmpty())
      return false;
    for (KEPRule kepRule : list) {
      if (kepRule.getCoveringStatistics().getCoveredNegativesCount() > 0)
        return true;
    }
    return false;
  }

  public void testCorrectionRules(TextRulerTarget target) {
    if (shouldAbort())
      return;
    String rStr = getAnnotationRulesString(target.getSingleSlotTypeName());
    for (TextRulerExampleDocument doc : exampleDocuments.getDocuments()) {
      CAS processedCAS = applyScriptOnDocument(rStr, doc, target);
      TextRulerStatisticsCollector scriptStatistics = new TextRulerStatisticsCollector();
      compareOriginalDocumentWithTestCAS(doc, processedCAS, target, scriptStatistics,
              collectNegativeCoveredInstancesWhenTesting());
      for (KEPRule cRule : correctionRules.get(target.getSingleSlotTypeName())) {
        if (shouldAbort())
          break;
        if (cRule.getCoveringStatistics() == null) {
          cRule.setCoveringStatistics(new TextRulerStatisticsCollector());
        }
        processedCAS = applyScriptOnDocument(rStr, doc, target);
        TextRulerStatisticsCollector correctedStats = new TextRulerStatisticsCollector();
        testRuleOnDocument(cRule, doc, correctedStats, processedCAS);
        for (TextRulerExample ex : scriptStatistics.getCoveredNegativeExamples()) {
          if (!correctedStats.getCoveredNegativeExamples().contains(ex)) {
            cRule.getCoveringStatistics().addCoveredNegative(ex);
          }
        }
        for (TextRulerExample ex : scriptStatistics.getCoveredPositiveExamples()) {
          if (!correctedStats.getCoveredPositiveExamples().contains(ex)) {
            cRule.getCoveringStatistics().addCoveredPositive(ex);
          }
        }
        cRule.getCoveringStatistics().reflectCountsFromCoveredExamples();
      }
    }
  }

  private void prepareCASWithBoundaries(CAS cas) {
    for (String slotName : slotNames)
      if (!(slotName.contains(TextRulerToolkit.LEFT_BOUNDARY_EXTENSION)
              || slotName.contains(TextRulerToolkit.RIGHT_BOUNDARY_EXTENSION)))
        TextRulerExampleDocument.createBoundaryAnnotationsForCas(cas, slotName, filterSet);
  }

  @Override
  public CAS loadCAS(String fileName, CAS reuseCAS) {
    CAS cas = super.loadCAS(fileName, reuseCAS);
    prepareCASWithBoundaries(cas);
    return cas;
  }

  private void prepareCachedCASesWithBoundaries() {
    for (CAS cas : exampleDocuments.getCachedCASes())
      prepareCASWithBoundaries(cas);
  }

  @Override
  public boolean collectNegativeCoveredInstancesWhenTesting() {
    return true;
  }

  @Override
  public void setParameters(Map<String, Object> params) {
    if (TextRulerToolkit.DEBUG)
      saveParametersToTempFolder(params);

    // TODO try catch
    if (params.containsKey(FILLER_WINDOW))
      fillerWindow = (Integer) params.get(FILLER_WINDOW);

    if (params.containsKey(MAX_EXPAND_RULES))
      maxExpandRules = (Integer) params.get(MAX_EXPAND_RULES);

    if (params.containsKey(MAX_FILLER_LENGTH))
      maxFillerLength = (Integer) params.get(MAX_FILLER_LENGTH);

    if (params.containsKey(MAX_INFILLER_RULES))
      maxInfillerRules = (Integer) params.get(MAX_INFILLER_RULES);

  }

  protected FSMatchConstraint getConstraint(CAS cas) {
    ConstraintFactory cf = cas.getConstraintFactory();
    final FSTypeConstraint constraint = cf.createTypeConstraint();

    for (String each : getFilterSet()) {
      constraint.add(each);
    }
    constraint.add(RutaEngine.BASIC_TYPE);
    // TODO check if this is a legal alternative to "new NotConstraint(constraint)":
    FSMatchConstraint result = new FSMatchConstraint() {
      private static final long serialVersionUID = -6744378612440830298L;

      private final FSTypeConstraint c = constraint;

      @Override
      public boolean match(FeatureStructure fs) {
        return !c.match(fs);
      }
    };
    return result;
  }

  public class KEPRuleComparator implements Comparator<KEPRule> {

    private CAS cas;

    public KEPRuleComparator(CAS cas) {
      super();
      this.cas = cas;
    }

    @Override
    public int compare(KEPRule o1, KEPRule o2) {
      ArrayList<TextRulerRuleItem> items1 = o1.getInFiller();
      items1.addAll(o1.getPostFiller());
      items1.addAll(o1.getPreFiller());
      double occ1 = 0;
      double occ2 = 0;
      for (TextRulerRuleItem each : items1) {
        KEPRuleItem eachItem = (KEPRuleItem) each;
        int ratio = exampleDocuments.getAllPositiveExamples().size()
                / cas.getAnnotationIndex(eachItem.getType()).size();
        occ1 += (ratio < 1) ? 1 : ratio;
      }
      ArrayList<TextRulerRuleItem> items2 = o2.getInFiller();
      items2.addAll(o2.getPostFiller());
      items2.addAll(o2.getPreFiller());
      for (TextRulerRuleItem each : items2) {
        KEPRuleItem eachItem = (KEPRuleItem) each;
        int ratio = exampleDocuments.getAllPositiveExamples().size()
                / cas.getAnnotationIndex(eachItem.getType()).size();
        occ2 += (ratio < 1) ? 1 : ratio;
      }
      double v1 = occ1;
      double v2 = occ2;
      if (v1 > v2) {
        return 1;
      } else if (v2 > v1) {
        return -1;
      } else {
        return 0;
      }
    }
  }

}