/*
 * 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.hadoop.hive.ql.optimizer.calcite.translator;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;


import org.apache.calcite.adapter.druid.DruidQuery;
import org.apache.calcite.rel.RelFieldCollation;
import org.apache.calcite.rel.RelNode;
import org.apache.calcite.rel.RelVisitor;
import org.apache.calcite.rel.core.Aggregate;
import org.apache.calcite.rel.core.Aggregate.Group;
import org.apache.calcite.rel.core.AggregateCall;
import org.apache.calcite.rel.core.Filter;
import org.apache.calcite.rel.core.Join;
import org.apache.calcite.rel.core.JoinRelType;
import org.apache.calcite.rel.core.Project;
import org.apache.calcite.rel.core.SemiJoin;
import org.apache.calcite.rel.core.Sort;
import org.apache.calcite.rel.core.TableFunctionScan;
import org.apache.calcite.rel.core.TableScan;
import org.apache.calcite.rel.core.Union;
import org.apache.calcite.rel.type.RelDataTypeField;
import org.apache.calcite.rex.RexBuilder;
import org.apache.calcite.rex.RexCall;
import org.apache.calcite.rex.RexFieldAccess;
import org.apache.calcite.rex.RexFieldCollation;
import org.apache.calcite.rex.RexInputRef;
import org.apache.calcite.rex.RexLiteral;
import org.apache.calcite.rex.RexNode;
import org.apache.calcite.rex.RexOver;
import org.apache.calcite.rex.RexUtil;
import org.apache.calcite.rex.RexVisitorImpl;
import org.apache.calcite.rex.RexWindow;
import org.apache.calcite.rex.RexWindowBound;
import org.apache.calcite.sql.SqlKind;
import org.apache.calcite.sql.SqlOperator;
import org.apache.calcite.sql.type.SqlTypeName;
import org.apache.calcite.util.ImmutableBitSet;
import org.apache.hadoop.hive.metastore.api.FieldSchema;
import org.apache.hadoop.hive.ql.metadata.VirtualColumn;
import org.apache.hadoop.hive.ql.optimizer.calcite.CalciteSemanticException;
import org.apache.hadoop.hive.ql.optimizer.calcite.reloperators.HiveAggregate;
import org.apache.hadoop.hive.ql.optimizer.calcite.reloperators.HiveGroupingID;
import org.apache.hadoop.hive.ql.optimizer.calcite.reloperators.jdbc.HiveJdbcConverter;
import org.apache.hadoop.hive.ql.optimizer.calcite.reloperators.HiveSortLimit;
import org.apache.hadoop.hive.ql.optimizer.calcite.reloperators.HiveTableFunctionScan;
import org.apache.hadoop.hive.ql.optimizer.calcite.reloperators.HiveTableScan;
import org.apache.hadoop.hive.ql.optimizer.calcite.reloperators.jdbc.JdbcHiveTableScan;
import org.apache.hadoop.hive.ql.optimizer.calcite.translator.SqlFunctionConverter.HiveToken;
import org.apache.hadoop.hive.ql.parse.ASTNode;
import org.apache.hadoop.hive.ql.parse.HiveParser;
import org.apache.hadoop.hive.ql.parse.ParseDriver;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.common.collect.Iterables;

public class ASTConverter {
  private static final Logger LOG = LoggerFactory.getLogger(ASTConverter.class);

  private final RelNode          root;
  private final HiveAST          hiveAST;
  private RelNode          from;
  private Filter           where;
  private Aggregate        groupBy;
  private Filter           having;
  private RelNode          select;
  private Sort             orderLimit;

  private Schema           schema;

  private long             derivedTableCount;

  ASTConverter(RelNode root, long dtCounterInitVal) {
    this.root = root;
    hiveAST = new HiveAST();
    this.derivedTableCount = dtCounterInitVal;
  }

  public static ASTNode convert(final RelNode relNode, List<FieldSchema> resultSchema, boolean alignColumns)
      throws CalciteSemanticException {
    RelNode root = PlanModifierForASTConv.convertOpTree(relNode, resultSchema, alignColumns);
    ASTConverter c = new ASTConverter(root, 0);
    return c.convert();
  }

  private ASTNode convert() throws CalciteSemanticException {
    /*
     * 1. Walk RelNode Graph; note from, where, gBy.. nodes.
     */
    new QBVisitor().go(root);

    /*
     * 2. convert from node.
     */
    QueryBlockInfo qb = convertSource(from);
    schema = qb.schema;
    hiveAST.from = ASTBuilder.construct(HiveParser.TOK_FROM, "TOK_FROM").add(qb.ast).node();

    /*
     * 3. convert filterNode
     */
    if (where != null) {
      ASTNode cond = where.getCondition().accept(new RexVisitor(schema, false, root.getCluster().getRexBuilder()));
      hiveAST.where = ASTBuilder.where(cond);
    }

    /*
     * 4. GBy
     */
    if (groupBy != null) {
      ASTBuilder b;
      boolean groupingSetsExpression = false;
      Group aggregateType = groupBy.getGroupType();
      switch (aggregateType) {
        case SIMPLE:
          b = ASTBuilder.construct(HiveParser.TOK_GROUPBY, "TOK_GROUPBY");
          break;
        case ROLLUP:
          b = ASTBuilder.construct(HiveParser.TOK_ROLLUP_GROUPBY, "TOK_ROLLUP_GROUPBY");
          break;
        case CUBE:
          b = ASTBuilder.construct(HiveParser.TOK_CUBE_GROUPBY, "TOK_CUBE_GROUPBY");
          break;
        case OTHER:
          b = ASTBuilder.construct(HiveParser.TOK_GROUPING_SETS, "TOK_GROUPING_SETS");
          groupingSetsExpression = true;
          break;
        default:
          throw new CalciteSemanticException("Group type not recognized");
      }

      HiveAggregate hiveAgg = (HiveAggregate) groupBy;
      for (int pos : hiveAgg.getAggregateColumnsOrder()) {
        RexInputRef iRef = new RexInputRef(groupBy.getGroupSet().nth(pos),
            groupBy.getCluster().getTypeFactory().createSqlType(SqlTypeName.ANY));
        b.add(iRef.accept(new RexVisitor(schema, false, root.getCluster().getRexBuilder())));
      }
      for (int pos = 0; pos < groupBy.getGroupCount(); pos++) {
        if (!hiveAgg.getAggregateColumnsOrder().contains(pos)) {
          RexInputRef iRef = new RexInputRef(groupBy.getGroupSet().nth(pos),
              groupBy.getCluster().getTypeFactory().createSqlType(SqlTypeName.ANY));
          b.add(iRef.accept(new RexVisitor(schema, false, root.getCluster().getRexBuilder())));
        }
      }

      //Grouping sets expressions
      if(groupingSetsExpression) {
        for(ImmutableBitSet groupSet: groupBy.getGroupSets()) {
          ASTBuilder expression = ASTBuilder.construct(
                  HiveParser.TOK_GROUPING_SETS_EXPRESSION, "TOK_GROUPING_SETS_EXPRESSION");
          for (int i : groupSet) {
            RexInputRef iRef = new RexInputRef(i, groupBy.getCluster().getTypeFactory()
                .createSqlType(SqlTypeName.ANY));
            expression.add(iRef.accept(new RexVisitor(schema, false, root.getCluster().getRexBuilder())));
          }
          b.add(expression);
        }
      }

      if (!groupBy.getGroupSet().isEmpty()) {
        hiveAST.groupBy = b.node();
      }

      schema = new Schema(schema, groupBy);
    }

    /*
     * 5. Having
     */
    if (having != null) {
      ASTNode cond = having.getCondition().accept(new RexVisitor(schema, false, root.getCluster().getRexBuilder()));
      hiveAST.having = ASTBuilder.having(cond);
    }

    /*
     * 6. Project
     */
    ASTBuilder b = ASTBuilder.construct(HiveParser.TOK_SELECT, "TOK_SELECT");

    if (select instanceof Project) {
      List<RexNode> childExps = ((Project) select).getChildExps();
      if (childExps.isEmpty()) {
        RexLiteral r = select.getCluster().getRexBuilder().makeExactLiteral(new BigDecimal(1));
        ASTNode selectExpr = ASTBuilder.selectExpr(ASTBuilder.literal(r), "1");
        b.add(selectExpr);
      } else {
        int i = 0;

        for (RexNode r : childExps) {
          ASTNode expr = r.accept(new RexVisitor(schema, r instanceof RexLiteral,
              select.getCluster().getRexBuilder()));
          String alias = select.getRowType().getFieldNames().get(i++);
          ASTNode selectExpr = ASTBuilder.selectExpr(expr, alias);
          b.add(selectExpr);
        }
      }
      hiveAST.select = b.node();
    } else {
      // select is UDTF
      HiveTableFunctionScan udtf = (HiveTableFunctionScan) select;
      List<ASTNode> children = new ArrayList<>();
      RexCall call = (RexCall) udtf.getCall();
      for (RexNode r : call.getOperands()) {
        ASTNode expr = r.accept(new RexVisitor(schema, r instanceof RexLiteral,
            select.getCluster().getRexBuilder()));
        children.add(expr);
      }
      ASTBuilder sel = ASTBuilder.construct(HiveParser.TOK_SELEXPR, "TOK_SELEXPR");
      ASTNode function = buildUDTFAST(call.getOperator().getName(), children);
      sel.add(function);
      for (String alias : udtf.getRowType().getFieldNames()) {
        sel.add(HiveParser.Identifier, alias);
      }
      b.add(sel);
      hiveAST.select = b.node();
    }

    /*
     * 7. Order Use in Order By from the block above. RelNode has no pointer to
     * parent hence we need to go top down; but OB at each block really belong
     * to its src/from. Hence the need to pass in sort for each block from
     * its parent.
     * 8. Limit
     */
    convertOrderLimitToASTNode((HiveSortLimit) orderLimit);

    return hiveAST.getAST();
  }

  private ASTNode buildUDTFAST(String functionName, List<ASTNode> children) {
    ASTNode node = (ASTNode) ParseDriver.adaptor.create(HiveParser.TOK_FUNCTION, "TOK_FUNCTION");
    node.addChild((ASTNode) ParseDriver.adaptor.create(HiveParser.Identifier, functionName));
    for (ASTNode c : children) {
      ParseDriver.adaptor.addChild(node, c);
    }
    return node;
  }
  private void convertOrderLimitToASTNode(HiveSortLimit order) {
    if (order != null) {
      HiveSortLimit hiveSortLimit = order;
      if (!hiveSortLimit.getCollation().getFieldCollations().isEmpty()) {
        // 1 Add order by token
        ASTNode orderAst = ASTBuilder.createAST(HiveParser.TOK_ORDERBY, "TOK_ORDERBY");

        schema = new Schema(hiveSortLimit);
        Map<Integer, RexNode> obRefToCallMap = hiveSortLimit.getInputRefToCallMap();
        RexNode obExpr;
        ASTNode astCol;
        for (RelFieldCollation c : hiveSortLimit.getCollation().getFieldCollations()) {

          // 2 Add Direction token
          ASTNode directionAST = c.getDirection() == RelFieldCollation.Direction.ASCENDING ? ASTBuilder
              .createAST(HiveParser.TOK_TABSORTCOLNAMEASC, "TOK_TABSORTCOLNAMEASC") : ASTBuilder
              .createAST(HiveParser.TOK_TABSORTCOLNAMEDESC, "TOK_TABSORTCOLNAMEDESC");
          ASTNode nullDirectionAST;
          // Null direction
          if (c.nullDirection == RelFieldCollation.NullDirection.FIRST) {
            nullDirectionAST = ASTBuilder.createAST(HiveParser.TOK_NULLS_FIRST, "TOK_NULLS_FIRST");
            directionAST.addChild(nullDirectionAST);
          } else if (c.nullDirection == RelFieldCollation.NullDirection.LAST) {
            nullDirectionAST = ASTBuilder.createAST(HiveParser.TOK_NULLS_LAST, "TOK_NULLS_LAST");
            directionAST.addChild(nullDirectionAST);
          } else {
            // Default
            if (c.getDirection() == RelFieldCollation.Direction.ASCENDING) {
              nullDirectionAST = ASTBuilder.createAST(HiveParser.TOK_NULLS_FIRST, "TOK_NULLS_FIRST");
              directionAST.addChild(nullDirectionAST);
            } else {
              nullDirectionAST = ASTBuilder.createAST(HiveParser.TOK_NULLS_LAST, "TOK_NULLS_LAST");
              directionAST.addChild(nullDirectionAST);
            }
          }

          // 3 Convert OB expr (OB Expr is usually an input ref except for top
          // level OB; top level OB will have RexCall kept in a map.)
          obExpr = null;
          if (obRefToCallMap != null)
            obExpr = obRefToCallMap.get(c.getFieldIndex());

          if (obExpr != null) {
            astCol = obExpr.accept(new RexVisitor(schema, false, order.getCluster().getRexBuilder()));
          } else {
            ColumnInfo cI = schema.get(c.getFieldIndex());
            /*
             * The RowResolver setup for Select drops Table associations. So
             * setup ASTNode on unqualified name.
             */
            astCol = ASTBuilder.unqualifiedName(cI.column);
          }

          // 4 buildup the ob expr AST
          nullDirectionAST.addChild(astCol);
          orderAst.addChild(directionAST);
        }
        hiveAST.order = orderAst;
      }

      RexNode offsetExpr = hiveSortLimit.getOffsetExpr();
      RexNode fetchExpr = hiveSortLimit.getFetchExpr();
      if (fetchExpr != null) {
        Object offset = (offsetExpr == null) ?
            new Integer(0) : ((RexLiteral) offsetExpr).getValue2();
        Object fetch = ((RexLiteral) fetchExpr).getValue2();
        hiveAST.limit = ASTBuilder.limit(offset, fetch);
      }
    }
  }

  private Schema getRowSchema(String tblAlias) {
    if (select instanceof Project) {
      return new Schema((Project) select, tblAlias);
    } else {
      return new Schema((TableFunctionScan) select, tblAlias);
    }
  }

  private QueryBlockInfo convertSource(RelNode r) throws CalciteSemanticException {
    Schema s = null;
    ASTNode ast = null;

    if (r instanceof TableScan) {
      TableScan f = (TableScan) r;
      s = new Schema(f);
      ast = ASTBuilder.table(f);
    } else if (r instanceof HiveJdbcConverter) {
      HiveJdbcConverter f = (HiveJdbcConverter) r;
      s = new Schema(f);
      ast = ASTBuilder.table(f);
    } else if (r instanceof DruidQuery) {
      DruidQuery f = (DruidQuery) r;
      s = new Schema(f);
      ast = ASTBuilder.table(f);
    } else if (r instanceof Join) {
      Join join = (Join) r;
      QueryBlockInfo left = convertSource(join.getLeft());
      QueryBlockInfo right = convertSource(join.getRight());
      s = new Schema(left.schema, right.schema);
      ASTNode cond = join.getCondition().accept(new RexVisitor(s, false, r.getCluster().getRexBuilder()));
      boolean semiJoin = join instanceof SemiJoin;
      if (join.getRight() instanceof Join && !semiJoin) {
          // should not be done for semijoin since it will change the semantics
        // Invert join inputs; this is done because otherwise the SemanticAnalyzer
        // methods to merge joins will not kick in
        JoinRelType type;
        if (join.getJoinType() == JoinRelType.LEFT) {
          type = JoinRelType.RIGHT;
        } else if (join.getJoinType() == JoinRelType.RIGHT) {
          type = JoinRelType.LEFT;
        } else {
          type = join.getJoinType();
        }
        ast = ASTBuilder.join(right.ast, left.ast, type, cond, semiJoin);
      } else {
        ast = ASTBuilder.join(left.ast, right.ast, join.getJoinType(), cond, semiJoin);
      }
      if (semiJoin) {
        s = left.schema;
      }
    } else if (r instanceof Union) {
      Union u = ((Union) r);
      ASTNode left = new ASTConverter(((Union) r).getInput(0), this.derivedTableCount).convert();
      for (int ind = 1; ind < u.getInputs().size(); ind++) {
        left = getUnionAllAST(left, new ASTConverter(((Union) r).getInput(ind),
            this.derivedTableCount).convert());
        String sqAlias = nextAlias();
        ast = ASTBuilder.subQuery(left, sqAlias);
        s = new Schema((Union) r, sqAlias);
      }
    } else {
      ASTConverter src = new ASTConverter(r, this.derivedTableCount);
      ASTNode srcAST = src.convert();
      String sqAlias = nextAlias();
      s = src.getRowSchema(sqAlias);
      ast = ASTBuilder.subQuery(srcAST, sqAlias);
    }
    return new QueryBlockInfo(s, ast);
  }

  class QBVisitor extends RelVisitor {

    public void handle(Filter filter) {
      RelNode child = filter.getInput();
      if (child instanceof Aggregate && !((Aggregate) child).getGroupSet().isEmpty()) {
        ASTConverter.this.having = filter;
      } else {
        ASTConverter.this.where = filter;
      }
    }

    public void handle(Project project) {
      if (ASTConverter.this.select == null) {
        ASTConverter.this.select = project;
      } else {
        ASTConverter.this.from = project;
      }
    }

    public void handle(TableFunctionScan tableFunctionScan) {
      if (ASTConverter.this.select == null) {
        ASTConverter.this.select = tableFunctionScan;
      } else {
        ASTConverter.this.from = tableFunctionScan;
      }
    }

    @Override
    public void visit(RelNode node, int ordinal, RelNode parent) {

      if (node instanceof TableScan ||
          node instanceof DruidQuery ||
          node instanceof HiveJdbcConverter) {
        ASTConverter.this.from = node;
      } else if (node instanceof Filter) {
        handle((Filter) node);
      } else if (node instanceof Project) {
        handle((Project) node);
      } else if (node instanceof TableFunctionScan) {
        handle((TableFunctionScan) node);
      } else if (node instanceof Join) {
        ASTConverter.this.from = node;
      } else if (node instanceof Union) {
        ASTConverter.this.from = node;
      } else if (node instanceof Aggregate) {
        ASTConverter.this.groupBy = (Aggregate) node;
      } else if (node instanceof Sort) {
        if (ASTConverter.this.select != null) {
          ASTConverter.this.from = node;
        } else {
          ASTConverter.this.orderLimit = (Sort) node;
        }
      }
      /*
       * once the source node is reached; stop traversal for this QB
       */
      if (ASTConverter.this.from == null) {
        node.childrenAccept(this);
      }
    }

  }


  static class RexVisitor extends RexVisitorImpl<ASTNode> {

    private final Schema schema;
    private final boolean useTypeQualInLiteral;
    private final RexBuilder rexBuilder;
    // this is to keep track of null literal which already has been visited
    private Map<RexLiteral, Boolean> nullLiteralMap ;


    protected RexVisitor(Schema schema, boolean useTypeQualInLiteral) {
      this(schema, useTypeQualInLiteral, null);

    }
    protected RexVisitor(Schema schema) {
      this(schema, false);
    }

    protected RexVisitor(Schema schema, boolean useTypeQualInLiteral, RexBuilder rexBuilder) {
      super(true);
      this.schema = schema;
      this.useTypeQualInLiteral = useTypeQualInLiteral;
      this.rexBuilder = rexBuilder;

      this.nullLiteralMap =
          new TreeMap<>(new Comparator<RexLiteral>(){
            // RexLiteral's equal only consider value and type which isn't sufficient
            // so providing custom comparator which distinguishes b/w objects irrespective
            // of value/type
            @Override
            public int compare(RexLiteral o1, RexLiteral o2) {
              if(o1 == o2) return 0;
              else return 1;
            }
          });
    }

    @Override
    public ASTNode visitFieldAccess(RexFieldAccess fieldAccess) {
      return ASTBuilder.construct(HiveParser.DOT, ".").add(super.visitFieldAccess(fieldAccess))
          .add(HiveParser.Identifier, fieldAccess.getField().getName()).node();
    }

    @Override
    public ASTNode visitInputRef(RexInputRef inputRef) {
      ColumnInfo cI = schema.get(inputRef.getIndex());
      if (cI.agg != null) {
        return (ASTNode) ParseDriver.adaptor.dupTree(cI.agg);
      }

      if (cI.table == null || cI.table.isEmpty())
        return ASTBuilder.unqualifiedName(cI.column);
      else
        return ASTBuilder.qualifiedName(cI.table, cI.column);

    }

    @Override
    public ASTNode visitLiteral(RexLiteral literal) {

      if (RexUtil.isNull(literal) && literal.getType().getSqlTypeName() != SqlTypeName.NULL
          && rexBuilder != null) {
        // It is NULL value with different type, we need to introduce a CAST
        // to keep it
        if(nullLiteralMap.containsKey(literal)) {
          return ASTBuilder.literal(literal, useTypeQualInLiteral);
        }
        nullLiteralMap.put(literal, true);
        RexNode r = rexBuilder.makeAbstractCast(literal.getType(), literal);

        return r.accept(this);
      }
      return ASTBuilder.literal(literal, useTypeQualInLiteral);
    }

    private ASTNode getPSpecAST(RexWindow window) {
      ASTNode pSpecAst = null;

      ASTNode dByAst = null;
      if (window.partitionKeys != null && !window.partitionKeys.isEmpty()) {
        dByAst = ASTBuilder.createAST(HiveParser.TOK_DISTRIBUTEBY, "TOK_DISTRIBUTEBY");
        for (RexNode pk : window.partitionKeys) {
          ASTNode astCol = pk.accept(this);
          dByAst.addChild(astCol);
        }
      }

      ASTNode oByAst = null;
      if (window.orderKeys != null && !window.orderKeys.isEmpty()) {
        oByAst = ASTBuilder.createAST(HiveParser.TOK_ORDERBY, "TOK_ORDERBY");
        for (RexFieldCollation ok : window.orderKeys) {
          ASTNode directionAST = ok.getDirection() == RelFieldCollation.Direction.ASCENDING ? ASTBuilder
              .createAST(HiveParser.TOK_TABSORTCOLNAMEASC, "TOK_TABSORTCOLNAMEASC") : ASTBuilder
              .createAST(HiveParser.TOK_TABSORTCOLNAMEDESC, "TOK_TABSORTCOLNAMEDESC");
          ASTNode nullDirectionAST;
          // Null direction
          if (ok.right.contains(SqlKind.NULLS_FIRST)) {
            nullDirectionAST = ASTBuilder.createAST(HiveParser.TOK_NULLS_FIRST, "TOK_NULLS_FIRST");
            directionAST.addChild(nullDirectionAST);
          } else if (ok.right.contains(SqlKind.NULLS_LAST)) {
            nullDirectionAST = ASTBuilder.createAST(HiveParser.TOK_NULLS_LAST, "TOK_NULLS_LAST");
            directionAST.addChild(nullDirectionAST);
          } else {
            // Default
            if (ok.getDirection() == RelFieldCollation.Direction.ASCENDING) {
              nullDirectionAST = ASTBuilder.createAST(HiveParser.TOK_NULLS_FIRST, "TOK_NULLS_FIRST");
              directionAST.addChild(nullDirectionAST);
            } else {
              nullDirectionAST = ASTBuilder.createAST(HiveParser.TOK_NULLS_LAST, "TOK_NULLS_LAST");
              directionAST.addChild(nullDirectionAST);
            }
          }
          ASTNode astCol = ok.left.accept(this);
          
          nullDirectionAST.addChild(astCol);
          oByAst.addChild(directionAST);
        }
      }

      if (dByAst != null || oByAst != null) {
        pSpecAst = ASTBuilder.createAST(HiveParser.TOK_PARTITIONINGSPEC, "TOK_PARTITIONINGSPEC");
        if (dByAst != null)
          pSpecAst.addChild(dByAst);
        if (oByAst != null)
          pSpecAst.addChild(oByAst);
      }

      return pSpecAst;
    }

    private ASTNode getWindowBound(RexWindowBound wb) {
      ASTNode wbAST = null;

      if (wb.isCurrentRow()) {
        wbAST = ASTBuilder.createAST(HiveParser.KW_CURRENT, "CURRENT");
      } else {
        if (wb.isPreceding())
          wbAST = ASTBuilder.createAST(HiveParser.KW_PRECEDING, "PRECEDING");
        else
          wbAST = ASTBuilder.createAST(HiveParser.KW_FOLLOWING, "FOLLOWING");
        if (wb.isUnbounded()) {
          wbAST.addChild(ASTBuilder.createAST(HiveParser.KW_UNBOUNDED, "UNBOUNDED"));
        } else {
          ASTNode offset = wb.getOffset().accept(this);
          wbAST.addChild(offset);
        }
      }

      return wbAST;
    }

    private ASTNode getWindowRangeAST(RexWindow window) {
      ASTNode wRangeAst = null;

      ASTNode startAST = null;
      RexWindowBound ub = window.getUpperBound();
      if (ub != null) {
        startAST = getWindowBound(ub);
      }

      ASTNode endAST = null;
      RexWindowBound lb = window.getLowerBound();
      if (lb != null) {
        endAST = getWindowBound(lb);
      }

      if (startAST != null || endAST != null) {
        // NOTE: in Hive AST Rows->Range(Physical) & Range -> Values (logical)
        if (window.isRows())
          wRangeAst = ASTBuilder.createAST(HiveParser.TOK_WINDOWRANGE, "TOK_WINDOWRANGE");
        else
          wRangeAst = ASTBuilder.createAST(HiveParser.TOK_WINDOWVALUES, "TOK_WINDOWVALUES");
        if (startAST != null)
          wRangeAst.addChild(startAST);
        if (endAST != null)
          wRangeAst.addChild(endAST);
      }

      return wRangeAst;
    }

    @Override
    public ASTNode visitOver(RexOver over) {
      if (!deep) {
        return null;
      }

      // 1. Translate the UDAF
      final ASTNode wUDAFAst = visitCall(over);

      // 2. Add TOK_WINDOW as child of UDAF
      ASTNode wSpec = ASTBuilder.createAST(HiveParser.TOK_WINDOWSPEC, "TOK_WINDOWSPEC");
      wUDAFAst.addChild(wSpec);

      // 3. Add Part Spec & Range Spec as child of TOK_WINDOW
      final RexWindow window = over.getWindow();
      final ASTNode wPSpecAst = getPSpecAST(window);
      final ASTNode wRangeAst = getWindowRangeAST(window);
      if (wPSpecAst != null)
        wSpec.addChild(wPSpecAst);
      if (wRangeAst != null)
        wSpec.addChild(wRangeAst);

      return wUDAFAst;
    }

    @Override
    public ASTNode visitCall(RexCall call) {
      if (!deep) {
        return null;
      }

      SqlOperator op = call.getOperator();
      List<ASTNode> astNodeLst = new LinkedList<ASTNode>();
      switch (op.kind) {
      case EQUALS:
      case NOT_EQUALS:
      case LESS_THAN:
      case GREATER_THAN:
      case LESS_THAN_OR_EQUAL:
      case GREATER_THAN_OR_EQUAL:
        if (rexBuilder != null && RexUtil.isReferenceOrAccess(call.operands.get(1), true) &&
            RexUtil.isLiteral(call.operands.get(0), true)) {
          // Swap to get reference on the left side
          return visitCall((RexCall) RexUtil.invert(rexBuilder, call));
        } else {
          for (RexNode operand : call.operands) {
            astNodeLst.add(operand.accept(this));
          }
        }
        break;
      case CAST:
        HiveToken ht = TypeConverter.hiveToken(call.getType());
        ASTBuilder astBldr = ASTBuilder.construct(ht.type, ht.text);
        if (ht.args != null) {
          for (String castArg : ht.args) {
            astBldr.add(HiveParser.Identifier, castArg);
          }
        }
        astNodeLst.add(astBldr.node());
        for (RexNode operand : call.operands) {
          astNodeLst.add(operand.accept(this));
        }
        break;
      case EXTRACT:
        // Extract on date: special handling since function in Hive does
        // include <time_unit>. Observe that <time_unit> information
        // is implicit in the function name, thus translation will
        // proceed correctly if we just ignore the <time_unit>
        astNodeLst.add(call.operands.get(1).accept(this));
        break;
      case FLOOR:
        if (call.operands.size() == 2) {
          // Floor on date: special handling since function in Hive does
          // include <time_unit>. Observe that <time_unit> information
          // is implicit in the function name, thus translation will
          // proceed correctly if we just ignore the <time_unit>
          astNodeLst.add(call.operands.get(0).accept(this));
          break;
        }
        // fall-through
      default:
        for (RexNode operand : call.operands) {
          astNodeLst.add(operand.accept(this));
        }
      }

      if (isFlat(call)) {
        return SqlFunctionConverter.buildAST(op, astNodeLst, 0);
      } else {
        return SqlFunctionConverter.buildAST(op, astNodeLst);
      }
    }
  }

  static class QueryBlockInfo {
    Schema  schema;
    ASTNode ast;

    public QueryBlockInfo(Schema schema, ASTNode ast) {
      super();
      this.schema = schema;
      this.ast = ast;
    }
  }

  /*
   * represents the schema exposed by a QueryBlock.
   */
  static class Schema extends ArrayList<ColumnInfo> {

    private static final long serialVersionUID = 1L;

    Schema(TableScan scan) {
      HiveTableScan hts = (HiveTableScan) scan;
      String tabName = hts.getTableAlias();
      for (RelDataTypeField field : scan.getRowType().getFieldList()) {
        add(new ColumnInfo(tabName, field.getName()));
      }
    }

    Schema(DruidQuery dq) {
      HiveTableScan hts = (HiveTableScan) ((DruidQuery)dq).getTableScan();
      String tabName = hts.getTableAlias();
      for (RelDataTypeField field : dq.getRowType().getFieldList()) {
        add(new ColumnInfo(tabName, field.getName()));
      }
    }

    Schema(HiveJdbcConverter scan) {
      HiveJdbcConverter jdbcHiveCoverter = scan;
      final JdbcHiveTableScan jdbcTableScan = jdbcHiveCoverter.getTableScan();
      String tabName = jdbcTableScan.getHiveTableScan().getTableAlias();
      for (RelDataTypeField field : jdbcHiveCoverter.getRowType().getFieldList()) {
        add(new ColumnInfo(tabName, field.getName()));
      }
    }

    Schema(Project select, String alias) {
      for (RelDataTypeField field : select.getRowType().getFieldList()) {
        add(new ColumnInfo(alias, field.getName()));
      }
    }

    Schema(TableFunctionScan select, String alias) {
      for (RelDataTypeField field : select.getRowType().getFieldList()) {
        add(new ColumnInfo(alias, field.getName()));
      }
    }

    Schema(Union unionRel, String alias) {
      for (RelDataTypeField field : unionRel.getRowType().getFieldList()) {
        add(new ColumnInfo(alias, field.getName()));
      }
    }

    Schema(Schema left, Schema right) {
      for (ColumnInfo cI : Iterables.concat(left, right)) {
        add(cI);
      }
    }

    Schema(Schema src, Aggregate gBy) {
      for (int i : gBy.getGroupSet()) {
        ColumnInfo cI = src.get(i);
        add(cI);
      }
      List<AggregateCall> aggs = gBy.getAggCallList();
      for (AggregateCall agg : aggs) {
        if (agg.getAggregation() == HiveGroupingID.INSTANCE) {
          add(new ColumnInfo(null,VirtualColumn.GROUPINGID.getName()));
          continue;
        }
        int argCount = agg.getArgList().size();
        ASTBuilder b = agg.isDistinct() ? ASTBuilder.construct(HiveParser.TOK_FUNCTIONDI,
            "TOK_FUNCTIONDI") : argCount == 0 ? ASTBuilder.construct(HiveParser.TOK_FUNCTIONSTAR,
            "TOK_FUNCTIONSTAR") : ASTBuilder.construct(HiveParser.TOK_FUNCTION, "TOK_FUNCTION");
        b.add(HiveParser.Identifier, agg.getAggregation().getName());
        for (int i : agg.getArgList()) {
          RexInputRef iRef = new RexInputRef(i, gBy.getCluster().getTypeFactory()
              .createSqlType(SqlTypeName.ANY));
          b.add(iRef.accept(new RexVisitor(src, false, gBy.getCluster().getRexBuilder())));
        }
        add(new ColumnInfo(null, b.node()));
      }
    }

    /**
     * Assumption:<br>
     * 1. Project will always be child of Sort.<br>
     * 2. In Calcite every projection in Project is uniquely named
     * (unambigous) without using table qualifier (table name).<br>
     *
     * @param order
     *          Hive Sort Node
     * @return Schema
     */
    public Schema(HiveSortLimit order) {
      Project select = (Project) order.getInput();
      for (String projName : select.getRowType().getFieldNames()) {
        add(new ColumnInfo(null, projName));
      }
    }

    public Schema(String tabAlias, List<RelDataTypeField> fieldList) {
      for (RelDataTypeField field : fieldList) {
        add(new ColumnInfo(tabAlias, field.getName()));
      }
    }
  }

  /*
   * represents Column information exposed by a QueryBlock.
   */
  static class ColumnInfo {
    String  table;
    String  column;
    ASTNode agg;

    ColumnInfo(String table, String column) {
      super();
      this.table = table;
      this.column = column;
    }

    ColumnInfo(String table, ASTNode agg) {
      super();
      this.table = table;
      this.agg = agg;
    }

    ColumnInfo(String alias, ColumnInfo srcCol) {
      this.table = alias;
      this.column = srcCol.column;
      this.agg = srcCol.agg;
    }
  }

  private String nextAlias() {
    String tabAlias = String.format("$hdt$_%d", derivedTableCount);
    derivedTableCount++;
    return tabAlias;
  }

  static class HiveAST {

    ASTNode from;
    ASTNode where;
    ASTNode groupBy;
    ASTNode having;
    ASTNode select;
    ASTNode order;
    ASTNode limit;

    public ASTNode getAST() {
      ASTBuilder b = ASTBuilder
          .construct(HiveParser.TOK_QUERY, "TOK_QUERY")
          .add(from)
          .add(
              ASTBuilder.construct(HiveParser.TOK_INSERT, "TOK_INSERT").add(ASTBuilder.destNode())
                  .add(select).add(where).add(groupBy).add(having).add(order).add(limit));
      return b.node();
    }
  }

  public ASTNode getUnionAllAST(ASTNode leftAST, ASTNode rightAST) {

    ASTNode unionTokAST = ASTBuilder.construct(HiveParser.TOK_UNIONALL, "TOK_UNIONALL").add(leftAST)
        .add(rightAST).node();

    return unionTokAST;
  }

  public static boolean isFlat(RexCall call) {
    boolean flat = false;
    if (call.operands != null && call.operands.size() > 2) {
      SqlOperator op = call.getOperator();
      if (op.getKind() == SqlKind.AND || op.getKind() == SqlKind.OR) {
        flat = true;
      }
    }

    return flat;
  }

}
