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

import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.stream.Collectors;

import org.apache.commons.lang3.StringUtils;
import org.apache.uima.ruta.RutaElement;
import org.apache.uima.ruta.RutaStatement;
import org.apache.uima.ruta.ScriptApply;
import org.apache.uima.ruta.action.AbstractRutaAction;
import org.apache.uima.ruta.block.ForEachBlock;
import org.apache.uima.ruta.block.RutaBlock;
import org.apache.uima.ruta.condition.AbstractRutaCondition;
import org.apache.uima.ruta.expression.IRutaExpression;
import org.apache.uima.ruta.expression.bool.IBooleanExpression;
import org.apache.uima.ruta.expression.number.INumberExpression;
import org.apache.uima.ruta.expression.string.IStringExpression;
import org.apache.uima.ruta.expression.type.ITypeExpression;
import org.apache.uima.ruta.rule.AbstractRuleElement;
import org.apache.uima.ruta.rule.ComposedRuleElement;
import org.apache.uima.ruta.rule.ConjunctRulesRuleElement;
import org.apache.uima.ruta.rule.RegExpRule;
import org.apache.uima.ruta.rule.RuleElement;
import org.apache.uima.ruta.rule.RutaMatcher;
import org.apache.uima.ruta.rule.RutaOptionalRuleElement;
import org.apache.uima.ruta.rule.RutaRule;
import org.apache.uima.ruta.rule.RutaRuleElement;
import org.apache.uima.ruta.rule.WildCardRuleElement;
import org.apache.uima.ruta.rule.quantifier.MinMaxGreedy;
import org.apache.uima.ruta.rule.quantifier.MinMaxReluctant;
import org.apache.uima.ruta.rule.quantifier.NormalQuantifier;
import org.apache.uima.ruta.rule.quantifier.PlusGreedy;
import org.apache.uima.ruta.rule.quantifier.PlusReluctant;
import org.apache.uima.ruta.rule.quantifier.QuestionGreedy;
import org.apache.uima.ruta.rule.quantifier.QuestionReluctant;
import org.apache.uima.ruta.rule.quantifier.RuleElementQuantifier;
import org.apache.uima.ruta.rule.quantifier.StarGreedy;
import org.apache.uima.ruta.rule.quantifier.StarReluctant;

public class ScriptVerbalizer {

  private static final String COLON = ":";

  private static final String CBCLOSE = "}";

  private static final String CBOPEN = "{";

  private static final String THEN = "->";

  private static final String THEN2 = "<-";

  private RutaVerbalizer verbalizer;

  public ScriptVerbalizer(RutaVerbalizer verbalizer) {
    super();
    this.verbalizer = verbalizer;
  }

  public String verbalizeBlock(RutaBlock block, boolean withElements) {
    StringBuilder result = new StringBuilder();
    RutaRule rule = block.getRule();
    List<RutaStatement> elements = block.getElements();
    String name = block.getName();
    if (block instanceof ForEachBlock) {
      result.append("FOREACH");
    } else {
      result.append("BLOCK");
    }
    if (name != null) {
      result.append("(");
      result.append(name);
      if (block instanceof ForEachBlock) {
        IBooleanExpression direction = ((ForEachBlock) block).getDirection();
        if (direction != null) {
          result.append(", ");
          result.append(verbalizer.verbalize(direction));
        }
      }
      result.append(")");
    }
    result.append(" ");
    if (rule != null) {
      result.append(verbalizeRule(rule));
    }
    if (withElements) {
      result.append(" {\n");
      for (RutaStatement each : elements) {
        if (each instanceof RutaBlock) {
          result.append(verbalizeBlock((RutaBlock) each, withElements));
        } else if (each instanceof RutaRule) {
          result.append(verbalizeRule((RutaRule) each));
        }
        result.append(";");
        result.append("\n");
      }
      result.append(" }\n");
    }
    return result.toString();
  }

  public String verbalizeRule(RutaRule rule) {
    List<RuleElement> elements = rule.getRuleElements();
    StringBuilder result = new StringBuilder();
    Iterator<RuleElement> iterator = elements.iterator();
    while (iterator.hasNext()) {
      RuleElement each = iterator.next();
      result.append(verbalizeRuleElement(each));
      if (iterator.hasNext()) {
        result.append(" ");
      }

    }
    result.append(";");
    return result.toString();
  }

  public String verbalizeRuleElement(RuleElement re) {
    List<AbstractRutaCondition> conditions = re.getConditions();
    List<AbstractRutaAction> actions = re.getActions();
    RuleElementQuantifier quantifier = re.getQuantifier();
    StringBuilder result = new StringBuilder();
    if (!StringUtils.isBlank(re.getLabel())) {
      result.append(re.getLabel());
      result.append(COLON);
    }
    if (re.isStartAnchor()) {
      result.append("@");
    }
    if (re instanceof ConjunctRulesRuleElement) {
      result.append(verbalizeConjunct((ConjunctRulesRuleElement) re));
    } else if (re instanceof ComposedRuleElement) {
      result.append(verbalizeComposed((ComposedRuleElement) re));
    } else if (re instanceof RutaOptionalRuleElement) {
      result.append("_");
    } else if (re instanceof RutaRuleElement) {
      RutaRuleElement tmre = (RutaRuleElement) re;
      RutaMatcher matcher = tmre.getMatcher();
      // action-only rule
      boolean actionOnlyRule = false;
      if (matcher == null) {
        result.append("_");
      } else {
        IRutaExpression expression = matcher.getExpression();
        actionOnlyRule = expression == null;
        if (expression != null) {
          String verbalize = verbalizer.verbalize(expression);
          if (StringUtils.isBlank(verbalize)) {
            actionOnlyRule = true;
          } else {
            result.append(verbalize);
          }
        }
      }
      if (actionOnlyRule) {
        Iterator<AbstractRutaAction> ait = actions.iterator();
        while (ait.hasNext()) {
          AbstractRutaAction each = ait.next();
          result.append(verbalizer.verbalize(each));
          if (ait.hasNext()) {
            result.append(",");
          }
        }
        return result.toString();
      }
    } else if (re instanceof WildCardRuleElement) {
      result.append("#");
    }
    result.append(verbalizeQuantifier(quantifier));

    if (!conditions.isEmpty() || !actions.isEmpty()) {
      result.append(CBOPEN);
      Iterator<AbstractRutaCondition> cit = conditions.iterator();
      while (cit.hasNext()) {
        AbstractRutaCondition each = cit.next();
        result.append(verbalizer.verbalize(each));
        if (cit.hasNext()) {
          result.append(",");
        }
      }
      if (!actions.isEmpty()) {
        result.append(THEN);
        Iterator<AbstractRutaAction> ait = actions.iterator();
        while (ait.hasNext()) {
          AbstractRutaAction each = ait.next();
          result.append(verbalizer.verbalize(each));
          if (ait.hasNext()) {
            result.append(",");
          }
        }
      }
      result.append(CBCLOSE);
    }
    if (re instanceof AbstractRuleElement) {
      AbstractRuleElement are = (AbstractRuleElement) re;

      List<List<RutaStatement>> inlinedConditionRuleBlocks = are.getInlinedConditionRuleBlocks();
      for (List<RutaStatement> inlinedConditionRules : inlinedConditionRuleBlocks) {
        result.append(verbalizeInlinedConditionRuleBlock(inlinedConditionRules));
      }
      List<List<RutaStatement>> inlinedActionRuleBlocks = are.getInlinedActionRuleBlocks();
      for (List<RutaStatement> inlinedActionRules : inlinedActionRuleBlocks) {
        result.append(verbalizeInlinedActionRuleBlock(inlinedActionRules));
      }
    }
    return result.toString();
  }

  public String verbalizeInlinedActionRuleBlock(List<RutaStatement> inlinedActionRules) {
    StringBuilder result = new StringBuilder();
    if (inlinedActionRules != null && !inlinedActionRules.isEmpty()) {
      result.append(THEN);
      result.append(CBOPEN);
      for (RutaStatement rutaStatement : inlinedActionRules) {
        result.append(verbalize(rutaStatement));
      }
      result.append(CBCLOSE);
    }
    return result.toString();
  }

  public String verbalizeInlinedConditionRuleBlock(List<RutaStatement> inlinedConditionRules) {
    StringBuilder result = new StringBuilder();
    if (inlinedConditionRules != null && !inlinedConditionRules.isEmpty()) {
      result.append(THEN2);
      result.append(CBOPEN);
      for (RutaStatement rutaStatement : inlinedConditionRules) {
        result.append(verbalize(rutaStatement));
      }
      result.append(CBCLOSE);
    }
    return result.toString();
  }

  public String verbalizeInlinedConditionRuleApplyBlock(List<ScriptApply> block) {

    List<RutaStatement> list = block.stream().map(a -> a.getElement())
            .filter(RutaStatement.class::isInstance).map(s -> (RutaStatement) s)
            .collect(Collectors.toList());
    return verbalizeInlinedConditionRuleBlock(list);
  }

  public String verbalizeInlinedActionRuleApplyBlock(List<ScriptApply> block) {

    List<RutaStatement> list = block.stream().map(a -> a.getElement())
            .filter(RutaStatement.class::isInstance).map(s -> (RutaStatement) s)
            .collect(Collectors.toList());
    return verbalizeInlinedActionRuleBlock(list);
  }

  private String verbalizeConjunct(ConjunctRulesRuleElement re) {
    StringBuilder result = new StringBuilder();
    String sep = " % ";
    List<RuleElement> ruleElements = re.getRuleElements();
    for (RuleElement each : ruleElements) {
      if (re.getRuleElements().indexOf(each) != 0) {
        result.append(sep);
      }
      result.append(verbalizeRuleElement(each));
    }
    return result.toString();
  }

  public String verbalizeComposed(ComposedRuleElement cre) {
    StringBuilder result = new StringBuilder();
    List<RuleElement> ruleElements = cre.getRuleElements();
    result.append("(");
    String sep = " ";
    Boolean conjunct = cre.getConjunct();
    if (conjunct != null) {
      if (conjunct) {
        sep = " & ";
      } else {
        sep = " | ";
      }
    }
    for (RuleElement each : ruleElements) {
      if (cre.getRuleElements().indexOf(each) != 0) {
        result.append(sep);
      }
      result.append(verbalizeRuleElement(each));
    }
    result.append(")");
    return result.toString();
  }

  public String verbalizeMatcher(RutaRuleElement re) {

    StringBuilder result = new StringBuilder();
    RutaMatcher matcher = re.getMatcher();
    if (matcher != null) {
      result.append(verbalizer.verbalize(matcher.getExpression()));
    }
    return result.toString();
  }

  public String verbalizeQuantifier(RuleElementQuantifier quantifier) {
    if (quantifier instanceof NormalQuantifier) {
      return "";
    } else if (quantifier instanceof MinMaxGreedy) {
      MinMaxGreedy mmg = (MinMaxGreedy) quantifier;
      return "[" + verbalizer.verbalize(mmg.getMin()) + "," + verbalizer.verbalize(mmg.getMax())
              + "]";
    } else if (quantifier instanceof MinMaxReluctant) {
      MinMaxReluctant mmr = (MinMaxReluctant) quantifier;
      return "[" + verbalizer.verbalize(mmr.getMin()) + "," + verbalizer.verbalize(mmr.getMax())
              + "]?";
    } else if (quantifier instanceof PlusGreedy) {
      return "+";
    } else if (quantifier instanceof PlusReluctant) {
      return "+?";
    } else if (quantifier instanceof QuestionGreedy) {
      return "?";
    } else if (quantifier instanceof QuestionReluctant) {
      return "??";
    } else if (quantifier instanceof StarGreedy) {
      return "*";
    } else if (quantifier instanceof StarReluctant) {
      return "*?";
    }
    return null;
  }

  public String verbalize(RutaElement element) {
    if (element instanceof RutaBlock) {
      return verbalizeBlock((RutaBlock) element, false);
    } else if (element instanceof RuleElementQuantifier) {
      return verbalizeQuantifier((RuleElementQuantifier) element);
    } else if (element instanceof RutaRule) {
      return verbalizeRule((RutaRule) element);
    } else if (element instanceof RegExpRule) {
      return verbalizeRegExpRule((RegExpRule) element);
    } else if (element instanceof RutaRuleElement) {
      return verbalizeRuleElement((RutaRuleElement) element);
    }
    return null;
  }

  private String verbalizeRegExpRule(RegExpRule rule) {
    StringBuilder sb = new StringBuilder();
    String regexp = verbalizer.verbalize(rule.getRegExp());
    sb.append(regexp);
    sb.append(THEN);

    Iterator<Entry<ITypeExpression, INumberExpression>> iterator = rule.getTypeMap().entrySet()
            .iterator();
    while (iterator.hasNext()) {
      Entry<ITypeExpression, INumberExpression> next = iterator.next();
      String type = verbalizer.verbalize(next.getKey());
      INumberExpression value = next.getValue();
      if (value != null) {
        String group = verbalizer.verbalize(value);
        sb.append(group + " = " + type);
      } else {
        sb.append(type);
      }
      Map<ITypeExpression, Map<IStringExpression, IRutaExpression>> featureAssignments = rule
              .getFeatureAssignments();
      if (featureAssignments != null) {
        Map<IStringExpression, IRutaExpression> map = featureAssignments.get(next.getKey());
        if (map != null) {
          sb.append("(");
          Iterator<Entry<IStringExpression, IRutaExpression>> fit = map.entrySet().iterator();
          while (fit.hasNext()) {
            Map.Entry<IStringExpression, IRutaExpression> entry = fit.next();
            sb.append(verbalizer.verbalize(entry.getKey()));
            sb.append(" = ");
            sb.append(verbalizer.verbalize(entry.getValue()));
            if (fit.hasNext()) {
              sb.append(", ");
            }
          }
          sb.append(")");
        }

      }
      if (iterator.hasNext()) {
        sb.append(", ");
      }
    }
    sb.append(";");
    return sb.toString();
  }

}
