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

import java.util.List;

import org.apache.uima.ruta.textruler.core.TextRulerTarget;

public class ExpansionRule extends ShiftingRule {

  public ExpansionRule(TrabalLearner parentAlgorithm, TextRulerTarget target,
          TrabalAnnotation annotation, TrabalAnnotation targetAnnotation,
          AnnotationErrorType errorType) {
    super(parentAlgorithm, target, annotation, targetAnnotation, errorType);
  }

  public ExpansionRule(ExpansionRule copyFrom) {
    super(copyFrom);
  }

  @Override
  public void compileRuleString() {
    ruleString = "";

    int errorBegin = annotation.getBegin();
    int truthBegin = targetAnnotation.getBegin();
    int truthEnd = targetAnnotation.getEnd();
    boolean expandToLeft = truthBegin < errorBegin;

    if (expandToLeft) // (frontBoundaryItem ANY*? annotation){-> MARK(targetAnnotation)};
      expandToLeft(truthBegin);
    else
      // (annotation ANY*? rearBoundaryItem){-> MARK(targetAnnotation)};
      expandToRight(truthEnd);
    setNeedsCompile(false);
  }

  private void expandToLeft(int truthBegin) {
    // should the boundary item be part of the new annotation?
    boolean frontItemInBorders = frontBoundaryItem.getAnnotation().getBegin() == truthBegin;

    // parse the positive conditions
    String conditions = parseConditions(conditionTypes, true);
    if (conditions.length() > 0)
      conditions += " ";

    // parse the negative conditions
    conditionTypes.add(ConditionType.BEFORE);
    String negativeConditions = parseConditions(conditionTypes, false);
    if (negativeConditions.length() > 0)
      negativeConditions += " ";

    String before = parseConditions(ConditionType.BEFORE, true);
    if (before.length() > 0 && negativeConditions.length() > 0)
      negativeConditions = before + ", " + negativeConditions;
    else if (before.length() > 0)
      negativeConditions = before + " ";

    // this strings will be attached to the original annotation
    String unmark = "{" + negativeConditions + "-> UNMARK(" + annotation.getType().getShortName()
            + ")}";

    // this string will be attached to the front boundary item
    String after = parseConditions(ConditionType.AFTER);
    if (after.length() > 0)
      after = "{" + after + "}";

    // If the front item is part of the future annotation, it has to be included in the brackets.
    if (frontItemInBorders)
      ruleString += "(" + frontBoundaryItem + after + " ";
    else
      ruleString += frontBoundaryItem + after + " (";

    // We include all tokens between the boundaries.
    ruleString += "ANY*{-PARTOF(" + annotation.getType().getShortName() + ")} "; // like ANY*? but
    // faster
//    ruleString += "#{-CONTAINS(" + annotation.getType().getShortName() + ")} ";
    
    // these strings will be attached to the brackets
    String mark;
    if (((TrabalLearner) algorithm).getEnableFeatures())
      mark = "-> CREATE(" + targetAnnotation.getType().getShortName() + parseFeatures() + ")";
    else
      mark = "-> MARK(" + targetAnnotation.getType().getShortName() + ")";
    String action = "{" + conditions + mark + "}";

    // The original annotation represents the rear boundary item
    ruleString += annotation.getType().getShortName() + unmark + ")" + action + ";";
  }

  private void expandToRight(int truthEnd) {
    // should the boundary items be part of the new annotation?
    boolean rearItemInBorders = rearBoundaryItem.getAnnotation().getEnd() == truthEnd;

    // parse the positive conditions
    String conditions = parseConditions(conditionTypes, true);
    if (conditions.length() > 0)
      conditions += " ";

    // parse the negative conditions
    conditionTypes.add(ConditionType.AFTER);
    String negativeConditions = parseConditions(conditionTypes, false);
    if (negativeConditions.length() > 0)
      negativeConditions += " ";

    String after = parseConditions(ConditionType.AFTER, true);
    if (after.length() > 0 && negativeConditions.length() > 0)
      negativeConditions = after + ", " + negativeConditions;
    else if (after.length() > 0)
      negativeConditions = after + " ";

    // this strings will be attached to the original annotation
    String unmark = "{" + negativeConditions + "-> UNMARK(" + annotation.getType().getShortName()
            + ")}";

    // The original annotation represents the front boundary item
    ruleString += "(" + annotation.getType().getShortName() + unmark + " ";

    // We include all tokens between the boundaries.
    ruleString += "ANY*{-PARTOF(" + rearBoundaryItem + ")}"; // like ANY*? but faster
//    ruleString += "#{-CONTAINS(" + rearBoundaryItem + ")}"; // like ... but faster
    
    // these strings will be attached to the brackets
    String mark;
    if (((TrabalLearner) algorithm).getEnableFeatures())
      mark = "-> CREATE(" + targetAnnotation.getType().getShortName() + parseFeatures() + ")";
    else
      mark = "-> MARK(" + targetAnnotation.getType().getShortName() + ")";
    String action = "{" + conditions + mark + "}";

    // this string will be attached to the rear boundary item
    String before = parseConditions(ConditionType.BEFORE);
    if (before.length() > 0)
      before = "{" + before + "}";

    // If the rear item is part of the future annotation, it has to be included in the brackets
    if (rearItemInBorders)
      ruleString += " " + rearBoundaryItem + before + ")" + action + ";";
    else
      ruleString += ")" + action + " " + rearBoundaryItem + before + ";";
  }

  /**
   * Parse either the positive or the negative conditions without negation mark.
   * 
   * @param types
   *          the types, that should be parsed
   * @param positive
   *          true for positive, false for negative conditions
   * @return String with parsed conditions
   */
  private String parseConditions(List<ConditionType> types, boolean positive) {
    String result = "";
    for (Condition each : conditions)
      if (types.contains(each.getType()) && each.isNegative() != positive)
        if (each.isNegative()) {
          each.changePosNegValue();
          result += each + ", ";
          each.changePosNegValue();
        } else
          result += each + ", ";
    if (result.length() > 0)
      return result.substring(0, result.length() - 2);
    return "";
  }

  /**
   * Parse either the positive or the negative conditions without negation mark.
   * 
   * @param type
   *          the type, that should be parsed
   * @param positive
   *          true for positive, false for negative conditions
   * @return String with parsed conditions
   */
  private String parseConditions(ConditionType type, boolean positive) {
    String result = "";
    for (Condition each : conditions)
      if (each.getType() == type && each.isNegative() != positive)
        if (each.isNegative()) {
          each.changePosNegValue();
          result += each + ", ";
          each.changePosNegValue();
        } else
          result += each + ", ";
    if (result.length() > 0)
      return result.substring(0, result.length() - 2);
    return "";
  }

  @Override
  public TrabalRule copy() {
    return new ExpansionRule(this);
  }

}
