/*
 * 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.ide.parser.ast;

import java.util.ArrayList;
import java.util.List;

import org.antlr.runtime.CommonToken;
import org.antlr.runtime.Token;
import org.apache.uima.ruta.parser.RutaLexer;
import org.eclipse.dltk.ast.ASTNode;
import org.eclipse.dltk.ast.expressions.BooleanLiteral;
import org.eclipse.dltk.ast.expressions.Expression;
import org.eclipse.dltk.ast.expressions.ExpressionConstants;
import org.eclipse.dltk.ast.expressions.FloatNumericLiteral;
import org.eclipse.dltk.ast.expressions.NumericLiteral;
import org.eclipse.dltk.ast.expressions.StringLiteral;
import org.eclipse.dltk.ast.references.VariableReference;

public class ExpressionFactory extends AbstractFactory implements ExpressionConstants {

  private static VariableReference newVariableReference(Token ref, int kind) {
    int bounds[] = getBounds(ref);
    return new RutaVariableReference(bounds[0], bounds[1], ref.getText(), kind);
  }

  public static VariableReference createGenericVariableReference(Token ref) {
    return newVariableReference(ref, RutaTypeConstants.RUTA_TYPE_G);
  }

  public static RutaQuantifierLiteralExpression createQuantifierLiteralExpression(Token q,
          Token q2) {
    int bounds[] = getBounds(q);
    if (q2 != null) {
      bounds[1] = Math.max(bounds[1], getBounds(q2)[1]);
    }
    return new RutaQuantifierLiteralExpression(bounds[0], bounds[1], q.getText());
  }

  // =====> BOOLEAN-EXPRESSIONS <======
  public static Expression createBooleanExpression(Expression e) {
    if (e == null)
      return null;
    return new RutaExpression(e.sourceStart(), e.sourceEnd(), e, RutaTypeConstants.RUTA_TYPE_B);
  }

  public static RutaBooleanCompareExpression createBooleanNumberExpression(Expression e1, Token op,
          Expression e2) {
    int lexerOpID = op.getType(); // Integer.valueOf(op.getText());
    int operatorID = 0;
    // convert lexer-opId to dltk-opId:
    switch (lexerOpID) {
      case RutaLexer.LESS:
        operatorID = E_LT;
        break;
      case RutaLexer.LESSEQUAL:
        operatorID = E_LE;
        break;
      case RutaLexer.GREATER:
        operatorID = E_GT;
        break;
      case RutaLexer.GREATEREQUAL:
        operatorID = E_GE;
        break;
      case RutaLexer.EQUAL:
        operatorID = E_EQUAL;
        break;
      case RutaLexer.NOTEQUAL:
        operatorID = E_NOT_EQUAL;
        break;
      default:
        break;
    }
    return new RutaBooleanCompareExpression(e1.sourceStart(), e2.sourceEnd(), operatorID, e1, e2);
  }

  public static VariableReference createBooleanVariableReference(Token variableId) {
    // int bounds[] = getBounds(variableId);
    return newVariableReference(variableId, RutaTypeConstants.RUTA_TYPE_B);
  }

  public static BooleanLiteral createSimpleBooleanExpression(Token bToken) {
    int bounds[] = getBounds(bToken);
    boolean value = Boolean.valueOf(bToken.getText());
    return new BooleanLiteral(bounds[0], bounds[1], value);
  }

  // =====> TYPE-EXPRESSIONS <======
  public static Expression createTypeExpression(Expression e) {
    if (e != null) {
      return new RutaExpression(e.sourceStart(), e.sourceEnd(), e, RutaTypeConstants.RUTA_TYPE_AT);
    }
    return null;
  }

  public static Expression createEmptyTypeExpression(Token token) {
    int bounds[] = getBounds(token);
    return new RutaVariableReference(bounds[0], bounds[1], "", RutaTypeConstants.RUTA_TYPE_AT);
  }

  public static Expression createEmptyStringExpression(Token token) {
    int bounds[] = getBounds(token);
    return new RutaVariableReference(bounds[0], bounds[1], "", RutaTypeConstants.RUTA_TYPE_S);
  }

  public static Expression createEmptyNumberExpression(Token token) {
    int bounds[] = getBounds(token);
    return new RutaVariableReference(bounds[0], bounds[1], "", RutaTypeConstants.RUTA_TYPE_N);
  }

  public static Expression createEmptyBooleanExpression(Token token) {
    int bounds[] = getBounds(token);
    return new RutaVariableReference(bounds[0], bounds[1], "", RutaTypeConstants.RUTA_TYPE_B);
  }

  // public static Expression createSimpleTypeExpression(Token at, RutaBlock env) {
  // int bounds[] = getBounds(at);
  // return new RutaSimpleTypeExpression(bounds[0], bounds[1], at.getText());
  // }

  public static VariableReference createAnnotationTypeVariableReference(Token atRef) {
    return newVariableReference(atRef, RutaTypeConstants.RUTA_TYPE_AT);
  }

  public static VariableReference createAnnotationVariableReference(Token atRef) {
    return newVariableReference(atRef, RutaTypeConstants.RUTA_TYPE_UA);
  }

  public static VariableReference createAnnotationListVariableReference(Token atRef) {
    return newVariableReference(atRef, RutaTypeConstants.RUTA_TYPE_UAL);
  }

  public static Expression createAnnotationTypeConstantReference(Token atBasic) {
    int bounds[] = getBounds(atBasic);
    return new RutaVariableReference(bounds[0], bounds[1], atBasic.getText(),
            RutaTypeConstants.RUTA_TYPE_AT);
    // RutaBasicAnnotationType(atBasic.getText(),bounds[0],bounds[1],bounds[0],bounds[1]);
  }

  // =====> STRING-EXPRESSIONS <======
  public static RutaStringExpression createStringExpression(List<Expression> exprList) {
    List<ASTNode> l = new ArrayList<>();
    int start = 0;
    int end = 0;
    if (exprList != null) {
      l.addAll(exprList);
      if (!exprList.isEmpty()) {
        start = exprList.get(0).sourceStart();
        end = exprList.get(exprList.size() - 1).sourceEnd();
      }
    }
    return new RutaStringExpression(start, end, l);
  }

  public static StringLiteral createSimpleString(Token stringToken) {
    int bounds[] = getBounds(stringToken);
    return new StringLiteral(bounds[0], bounds[1], stringToken.getText());
  }

  // public static RessourceReference createRessourceReference

  public static VariableReference createStringVariableReference(Token variableId) {
    return newVariableReference(variableId, RutaTypeConstants.RUTA_TYPE_S);
  }

  // =====> NUMBER-EXPRESSIONS <======
  public static RutaExpression createNumberExpression(Expression e) {
    return new RutaExpression(e.sourceStart(), e.sourceEnd(), e, RutaTypeConstants.RUTA_TYPE_N);
  }

  public static NumericLiteral createDecimalLiteral(Token decLit, Token minus) {
    int bounds[] = getBounds(decLit);
    int value = Integer.valueOf(decLit.getText()); // .getInteger(decLit.getText());
    if (minus != null) {
      value = -value;
      bounds[0] = ((CommonToken) minus).getStartIndex();
    }
    return new NumericLiteral(bounds[0], bounds[1], value);
  }

  public static FloatNumericLiteral createFloatingPointLiteral(Token fpLit, Token minus) {
    int bounds[] = getBounds(fpLit);
    double value;
    try {
      value = Double.parseDouble(fpLit.getText());
    } catch (NumberFormatException e) {
      value = 0.0;
    }
    if (minus != null) {
      value = -value;
      bounds[0] = ((CommonToken) minus).getStartIndex();
    }
    return new FloatNumericLiteral(bounds[0], bounds[1], value);
  }

  public static VariableReference createNumberVariableReference(Token numVarRef) {
    return newVariableReference(numVarRef, RutaTypeConstants.RUTA_TYPE_N);
  }

  public static Expression createNegatedNumberExpression(Token minus, Expression expr) {
    int bounds[] = getSurroundingBounds(expr, (List<?>) null);
    if (minus != null) {
      bounds[0] = ((CommonToken) minus).getStartIndex();
    }
    return new RutaExpression(bounds[0], bounds[1], expr, RutaTypeConstants.RUTA_TYPE_N);
  }

  public static RutaBinaryArithmeticExpression createBinaryArithmeticExpr(Expression exprA,
          Expression exprB, Token op) {
    int bounds[] = getBounds(exprA, exprB);
    int lexerOpID = op.getType();
    int operatorID = 0;
    // convert lexer-opId to dltk-opId:
    switch (lexerOpID) {
      case RutaLexer.STAR:
        operatorID = ExpressionConstants.E_MULT;
        break;
      case RutaLexer.SLASH:
        operatorID = ExpressionConstants.E_DIV;
        break;
      case RutaLexer.PERCENT:
        operatorID = ExpressionConstants.E_MOD;
        break;
      case RutaLexer.PLUS:
        operatorID = ExpressionConstants.E_PLUS;
        break;
      case RutaLexer.MINUS:
        operatorID = ExpressionConstants.E_MINUS;
        break;
      case RutaLexer.POW:
        operatorID = ExpressionConstants.E_POWER;
        break;
      default:
        break;
    }
    return new RutaBinaryArithmeticExpression(bounds[0], bounds[1], exprA, exprB, operatorID);
  }

  public static Expression createUnaryArithmeticExpr(Expression expr, Token op) {
    int bounds[] = getBounds(op);
    if (expr != null) {
      bounds[1] = expr.sourceEnd();
    }
    int opID = convertOpToInt(op);
    return new RutaUnaryArithmeticExpression(bounds[0], bounds[1], expr, opID);
  }

  private static int convertOpToInt(Token opToken) {
    return RutaExpressionConstants.opIDs.get(opToken.getText());
  }

  // TODO
  public static Expression createBooleanFunction(Token op, Expression e1, Expression e2) {
    return new RutaExpression(e1.sourceStart(), e2.sourceEnd(), null,
            RutaTypeConstants.RUTA_TYPE_B);
  }

  public static Expression createListVariableReference(Token id) {
    return newVariableReference(id, RutaTypeConstants.RUTA_TYPE_WL);
  }

  public static Expression createTableVariableReference(Token id) {
    return newVariableReference(id, RutaTypeConstants.RUTA_TYPE_WT);
  }

  public static Expression createListVariableReference(Expression ref) {
    int bounds[] = getSurroundingBounds(ref);
    return new RutaVariableReference(bounds[0], bounds[1], "", RutaTypeConstants.RUTA_TYPE_WL);
  }

  public static Expression createTableVariableReference(Expression ref) {
    int bounds[] = getSurroundingBounds(ref);
    return new RutaVariableReference(bounds[0], bounds[1], "", RutaTypeConstants.RUTA_TYPE_WT);
  }

  public static Expression createRessourceReference(Token path) {
    int bounds[] = getBounds(path);
    String pathWithoutQuotes = path.getText();
    pathWithoutQuotes = pathWithoutQuotes.substring(1, pathWithoutQuotes.length() - 1);
    return new RutaRessourceReference(bounds[0], bounds[1], pathWithoutQuotes);
  }

  public static Expression createInnerListExpression(Token lBrak, List<String> inner, Token rBrak) {
    int boundsA[] = getBounds(lBrak, rBrak);
    StringBuffer s = new StringBuffer();
    s.append(lBrak);
    for (String el : inner) {
      s.append(el);
    }
    s.append(rBrak);
    return new RutaInnerListExpression(boundsA[0], boundsA[1], s.toString());
  }

  public static Expression createBooleanTypeExpression(Expression e1, Token op, Expression e2) {
    int lexerOpID = op.getType(); // Integer.valueOf(op.getText());
    int operatorID = 0;
    // convert lexer-opId to dltk-opId:
    switch (lexerOpID) {
      case RutaLexer.EQUAL:
        operatorID = E_EQUAL;
        break;
      case RutaLexer.NOTEQUAL:
        operatorID = E_NOT_EQUAL;
        break;
      default:
        break;
    }
    if (e1 != null && e2 != null) {
      return new RutaBooleanCompareExpression(e1.sourceStart(), e2.sourceEnd(), operatorID, e1, e2);
    }
    return null;
  }

  public static Expression createBooleanAnnotationExpression(Expression e1, Token op,
          Expression e2) {
    int lexerOpID = op.getType(); // Integer.valueOf(op.getText());
    int operatorID = 0;
    // convert lexer-opId to dltk-opId:
    switch (lexerOpID) {
      case RutaLexer.EQUAL:
        operatorID = E_EQUAL;
        break;
      case RutaLexer.NOTEQUAL:
        operatorID = E_NOT_EQUAL;
        break;
      default:
        break;
    }
    if (e1 != null && e2 != null) {
      return new RutaBooleanCompareExpression(e1.sourceStart(), e2.sourceEnd(), operatorID, e1, e2);
    }
    return null;
  }

  public static Expression createBooleanAnnotationListExpression(Expression e1, Token op,
          Expression e2) {
    int lexerOpID = op.getType(); // Integer.valueOf(op.getText());
    int operatorID = 0;
    // convert lexer-opId to dltk-opId:
    switch (lexerOpID) {
      case RutaLexer.EQUAL:
        operatorID = E_EQUAL;
        break;
      case RutaLexer.NOTEQUAL:
        operatorID = E_NOT_EQUAL;
        break;
      default:
        break;
    }
    if (e1 != null && e2 != null) {
      return new RutaBooleanCompareExpression(e1.sourceStart(), e2.sourceEnd(), operatorID, e1, e2);
    }
    return null;
  }

  public static Expression createBooleanStringExpression(Expression e1, Token op, Expression e2) {
    int lexerOpID = op.getType(); // Integer.valueOf(op.getText());
    int operatorID = 0;
    // convert lexer-opId to dltk-opId:
    switch (lexerOpID) {
      case RutaLexer.EQUAL:
        operatorID = E_EQUAL;
        break;
      case RutaLexer.NOTEQUAL:
        operatorID = E_NOT_EQUAL;
        break;
      default:
        break;
    }
    if (e1 != null && e2 != null) {
      return new RutaBooleanCompareExpression(e1.sourceStart(), e2.sourceEnd(), operatorID, e1, e2);
    }
    return null;
  }

  public static Expression createListExpression(List<Expression> exprList, int type) {
    List<ASTNode> l = new ArrayList<>();
    int start = 0;
    int end = 0;
    if (exprList != null) {
      l.addAll(exprList);
      if (!exprList.isEmpty()) {
        start = exprList.get(0).sourceStart();
        Expression expression = exprList.get(exprList.size() - 1);
        if (expression != null) {
          end = expression.sourceEnd();
        } else {
          end = exprList.get(0).sourceEnd();
        }
      }
    }
    return new RutaListExpression(start, end, l, type);
  }

  public static Expression createListExpression(Token var, int type) {
    return newVariableReference(var, type);
  }

  public static Expression createStringFunction(Token name, Expression var, List<Expression> list) {
    list.add(0, var);
    return createStringFunction(name, list);
  }

  public static Expression createFeatureMatch(Token feature, Token comp, Expression value) {
    int bounds[] = getBounds(feature);
    int end = bounds[1];
    if (value != null) {
      end = value.sourceEnd();
    }
    return new FeatureMatchExpression(bounds[0], end, feature, comp, value);
  }

  public static Expression createBooleanFunction(Token id, List<Expression> args) {
    return createFunction(id, args, RutaTypeConstants.RUTA_TYPE_B);
  }

  public static Expression createNumberFunction(Token id, List<Expression> args) {
    return createFunction(id, args, RutaTypeConstants.RUTA_TYPE_N);
  }

  public static Expression createStringFunction(Token id, List<Expression> args) {
    return createFunction(id, args, RutaTypeConstants.RUTA_TYPE_S);
  }

  public static Expression createTypeFunction(Token id, List<Expression> args) {
    return createFunction(id, args, RutaTypeConstants.RUTA_TYPE_AT);
  }

  public static RutaFunction createFunction(Token type, List<Expression> exprsRaw, int kind) {
    int bounds[] = getBounds(type);
    int nameStart = bounds[0];
    int nameEnd = bounds[1];
    List<ASTNode> exprs = new ArrayList<>();
    if (exprsRaw != null) {
      for (Object expressionObj : exprsRaw) {
        Expression expr = (Expression) expressionObj;
        if (expr != null) {
          exprs.add(expr);
        }
      }
      if (!exprs.isEmpty()) {
        Expression lastExpr = (Expression) exprs.get(exprs.size() - 1);
        bounds[1] = Math.max(bounds[1], lastExpr.sourceEnd());
      }
    }
    return new RutaFunction(bounds[0], bounds[1], exprs, kind, type.getText(), nameStart, nameEnd);
  }

  public static Expression createFeatureExpression(Token f) {
    return createFeatureMatch(f, null, null);
  }

  public static Expression createStringExpression(Expression fe) {
    List<Expression> list = new ArrayList<Expression>(1);
    list.add(fe);
    return createStringExpression(list);
  }

  public static Expression createNullExpression(Token t) {
    int bounds[] = getBounds(t);
    return new NullExpression(bounds[0], bounds[1]);
  }

  public static Expression createConditionedAnnotationTypeExpression(Token match, Token comp,
          Expression arg, List<RutaCondition> cs) {
    int bounds[] = getBounds(match);
    int end = bounds[1];
    if (cs != null && !cs.isEmpty()) {
      end = cs.get(cs.size() - 1).sourceEnd();
    } else if (arg != null) {
      end = arg.sourceEnd();
    }
    FeatureMatchExpression reference = new FeatureMatchExpression(bounds[0], end, match, comp, arg);
    return new ConditionedAnnotationTypeExpression(bounds[0], end, reference, arg, cs);
  }

}
