/*
 * 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.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import org.antlr.runtime.Token;
import org.eclipse.dltk.ast.ASTNode;
import org.eclipse.dltk.ast.expressions.Expression;
import org.eclipse.dltk.ast.expressions.ExpressionConstants;

public class ActionFactory extends AbstractFactory {

  public static final String IMPLICIT = "Implicit";
  
  public static RutaAction createAction(Token type, List<Expression> exprsRaw) {
    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 RutaAction(bounds[0], bounds[1], exprs, ExpressionConstants.USER_EXPRESSION_START
            + type.getType(), type.getText(), nameStart, nameEnd);
  }

  public static RutaAction createAction(Token type, Expression... exprsArray) {
    List<Expression> listOfExpressions = new ArrayList<Expression>();
    if (exprsArray != null) {
      for (int i = 0; i < exprsArray.length; i++) {
        Expression expression = exprsArray[i];
        if (expression != null) {
          listOfExpressions.add(expression);
        }
      }
    }
    return createAction(type, listOfExpressions);
  }

  public static RutaAction createEmptyAction(Token token) {
    int bounds[] = getBounds(token);
    return new RutaAction(bounds[0], bounds[1], new ArrayList<ASTNode>(),
            RutaConditionConstants.CONSTANT_OFFSET, "", bounds[0], bounds[1]);
  }

  public static RutaAction createAction(Token type, Map<Expression, Expression> map,
          Expression... exprsArray) {
    List<Expression> listOfExpressions = new ArrayList<Expression>();
    // TODO add map
    if (exprsArray != null) {
      for (int i = 0; i < exprsArray.length; i++) {
        Expression expression = exprsArray[i];
        if (expression != null) {
          listOfExpressions.add(expression);
        }
      }
    }
    return createAction(type, listOfExpressions);
  }

  public static RutaAction createCallAction(Token callToken, ComponentReference ref) {
    return createAction(callToken, ref);
  }

  public static RutaAction createCallAction(Token callToken, ComponentReference ref, Expression list, Expression view) {
    return createAction(callToken, ref, list, view);
  }

  public static RutaAction createStructureAction(Token type, Expression structure,
          List<Expression> indexes, List<Expression> left, List<Expression> right) {
    List<Expression> args = new ArrayList<Expression>();
    if (indexes != null) {
      args.addAll(indexes);
    }
    return createStructureAction(type, args, left, right, structure);
  }

  /**
   * @param left
   * @param bounds
   * @param exprs
   */
  private static void filterNullObjsAndSetBounds(List left, int[] bounds, List<ASTNode> exprs) {
    if (left != null) {
      for (Object expressionObj : left) {
        Expression expr = (Expression) expressionObj;
        if (expr != null) {
          exprs.add(expr);
        }
      }
      if (!exprs.isEmpty()) {
        ASTNode lastExpr = exprs.get(exprs.size() - 1);
        bounds[1] = Math.max(bounds[1], lastExpr.sourceEnd());
      }
    }
  }

  public static RutaAction createLogAction(Token type, Expression logString, Token level) {
    int[] nameBounds = getBounds(type);
    int[] bounds;
    int levelBounds[] = new int[] { -1, -1 };
    if (level != null) {
      levelBounds = getBounds(level);
      bounds = getBounds(type, level);
    } else {
      bounds = getBounds(type);
    }
    if (logString != null) {
      bounds[1] = Math.max(bounds[1], logString.sourceEnd());
    }
    List exprs = new ArrayList();
    exprs.add(logString);
    return new RutaLogAction(bounds[0], bounds[1], type.getText(), nameBounds[0], nameBounds[1],
            exprs, levelBounds[0], levelBounds[1]);
  }

  public static RutaAction createStructureAction(Token type, Expression structure,
          Expression index, Expression table, List left, List right) {
    int bounds[] = getBounds(type);
    int nameStart = bounds[0];
    int nameEnd = bounds[1];
    List<ASTNode> numExprs = new ArrayList<>();
    Map<Expression, Expression> assignments = new LinkedHashMap<Expression, Expression>();
    List<Expression> indexes = new ArrayList<Expression>();
    indexes.add(table);
    indexes.add(index);
    indexes.add(structure);
    filterNullObjsAndSetBounds(indexes, bounds, numExprs);
    if (left != null && right != null) {
      Iterator<Expression> keysIt = left.iterator();
      Iterator<Expression> valsIt = right.iterator();
      Expression val = null;
      while (keysIt.hasNext()) {
        Expression key = keysIt.next();
        if (!valsIt.hasNext()) {
          break;
        }
        val = valsIt.next();
        assignments.put(key, val);
      }
      if (val != null) {
        bounds[1] = val.sourceEnd();
      }
    }
    return new RutaStructureAction(bounds[0], bounds[1], numExprs,
            ExpressionConstants.USER_EXPRESSION_START + type.getType(), type.getText(), nameStart,
            nameEnd, assignments, structure);
  }

  public static RutaAction createAction(Token name, Expression f, List<Expression> list) {
    List<Expression> list2 = new ArrayList<Expression>();
    list2.add(f);
    list2.addAll(list);
    return createAction(name, list2);
  }

  public static RutaAction createAction(Token name, Expression a1, Expression a2,
          List<Expression> list) {
    List<Expression> complete = new ArrayList<Expression>();
    complete.add(a1);
    complete.add(a2);
    complete.addAll(list);
    return createAction(name, complete);
  }

  public static RutaAction createConfigureAction(Token name, ComponentReference ns,
          List<Expression> left, List<Expression> right) {
    List<Expression> exprs = new ArrayList<Expression>();
    exprs.add(ns);
    if (left != null && right != null) {
      for (int i = 0; i < Math.min(left.size(), right.size()); i++) {
        exprs.add(left.get(i));
        exprs.add(right.get(i));
      }
    }
    return createAction(name, exprs);
  }

  public static RutaAction createStructureAction(Token name, List<Expression> args,
          List<Expression> left, List<Expression> right, Expression structure) {
    int bounds[] = getBounds(name);
    int nameStart = bounds[0];
    int nameEnd = bounds[1];
    List<ASTNode> numExprs = new ArrayList<>();
    Map<Expression, Expression> assignments = new LinkedHashMap<Expression, Expression>();
    filterNullObjsAndSetBounds(args, bounds, numExprs);
    if (left != null && right != null) {
      Iterator<Expression> keysIt = left.iterator();
      Iterator<Expression> valsIt = right.iterator();
      Expression val = null;
      while (keysIt.hasNext()) {
        Expression key = keysIt.next();
        if (!valsIt.hasNext()) {
          break;
        }
        val = valsIt.next();
        assignments.put(key, val);
      }
      if (val != null) {
        bounds[1] = val.sourceEnd();
      }
    }
    return new RutaStructureAction(bounds[0], bounds[1], numExprs,
            ExpressionConstants.USER_EXPRESSION_START + name.getType(), name.getText(), nameStart,
            nameEnd, assignments, structure);
  }

  public static RutaAction createAction(Expression... exprsArray) {
    List<ASTNode> exprL = new ArrayList<>();
    if (exprsArray != null) {
      for (int i = 0; i < exprsArray.length; i++) {
        Expression expression = exprsArray[i];
        if (expression != null) {
          exprL.add(expression);
        }
      }
    }
    int[] bounds = getBounds(exprL.get(0), exprL.get(exprL.size() - 1));
    return new RutaAction(bounds[0], bounds[1], exprL, ExpressionConstants.USER_EXPRESSION_START,
            IMPLICIT, bounds[0], bounds[1]);
  }

}
