package com.alibaba.druid.sql.dialect.oracle.visitor;

import com.alibaba.druid.sql.SQLUtils;
import com.alibaba.druid.sql.ast.SQLExpr;
import com.alibaba.druid.sql.ast.SQLLimit;
import com.alibaba.druid.sql.ast.SQLObject;
import com.alibaba.druid.sql.ast.SQLOrderBy;
import com.alibaba.druid.sql.ast.expr.SQLAllColumnExpr;
import com.alibaba.druid.sql.ast.expr.SQLBetweenExpr;
import com.alibaba.druid.sql.ast.expr.SQLBinaryOpExpr;
import com.alibaba.druid.sql.ast.expr.SQLBinaryOperator;
import com.alibaba.druid.sql.ast.expr.SQLIdentifierExpr;
import com.alibaba.druid.sql.ast.expr.SQLIntegerExpr;
import com.alibaba.druid.sql.ast.expr.SQLPropertyExpr;
import com.alibaba.druid.sql.ast.statement.SQLSelect;
import com.alibaba.druid.sql.ast.statement.SQLSelectItem;
import com.alibaba.druid.sql.ast.statement.SQLSelectQueryBlock;
import com.alibaba.druid.sql.ast.statement.SQLSubqueryTableSource;
import com.alibaba.druid.sql.ast.statement.SQLTableSource;
import com.alibaba.druid.sql.ast.statement.SQLUnionOperator;
import com.alibaba.druid.sql.ast.statement.SQLUnionQuery;
import com.alibaba.druid.sql.dialect.oracle.ast.stmt.OracleSelectQueryBlock;
import com.alibaba.druid.util.FnvHash;
import java.util.List;

public class OracleRowNumToLimit extends OracleASTVisitorAdapter {
   private Context context;
   private boolean removeSelectListRownum = true;

   public boolean visit(SQLSelect x) {
      if (x.getWithSubQuery() != null) {
         x.getWithSubQuery().accept(this);
      }

      if (x.getQuery() != null) {
         x.getQuery().accept(this);
      }

      SQLSelectQueryBlock queryBlock = x.getQueryBlock();
      if (queryBlock != null && queryBlock.getLimit() != null) {
         SQLExpr rowCount = queryBlock.getLimit().getRowCount();
         if (rowCount instanceof SQLIntegerExpr && SQLIntegerExpr.isZero((SQLIntegerExpr)rowCount)) {
            x.setOrderBy((SQLOrderBy)null);
         }
      }

      return false;
   }

   public boolean visit(OracleSelectQueryBlock x) {
      this.context = new Context(this.context);
      this.context.queryBlock = x;
      SQLExpr where = x.getWhere();
      if (where != null) {
         where.accept(this);
      }

      SQLTableSource from = x.getFrom();
      if (from != null) {
         from.accept(this);
      }

      this.removeSelectListRowNum(x);
      List<SQLSelectItem> selectList = x.getSelectList();

      for(SQLSelectItem selectItem : selectList) {
         selectItem.accept(this);
      }

      SQLExpr startWith = x.getStartWith();
      if (startWith != null) {
         startWith.accept(this);
      }

      boolean allColumn = false;
      if (selectList.size() == 1) {
         SQLExpr expr = ((SQLSelectItem)selectList.get(0)).getExpr();
         if (expr instanceof SQLAllColumnExpr) {
            allColumn = true;
         } else if (expr instanceof SQLPropertyExpr && ((SQLPropertyExpr)expr).getName().equals("*")) {
            allColumn = true;
         }
      }

      if (!allColumn && x.getFrom() instanceof SQLSubqueryTableSource && ((SQLSubqueryTableSource)x.getFrom()).getSelect().getQuery() instanceof SQLSelectQueryBlock) {
         SQLSelectQueryBlock subQuery = ((SQLSubqueryTableSource)x.getFrom()).getSelect().getQueryBlock();
         List<SQLSelectItem> subSelectList = subQuery.getSelectList();
         if (subSelectList.size() >= selectList.size()) {
            boolean match = true;

            for(int i = 0; i < selectList.size(); ++i) {
               if (!((SQLSelectItem)selectList.get(i)).equals(subSelectList.get(i))) {
                  match = false;
                  break;
               }
            }

            if (match) {
               allColumn = true;
            }
         }
      }

      if (x.getParent() instanceof SQLSelect && x.getWhere() == null && x.getOrderBy() == null && allColumn && x.getLimit() != null && x.getFrom() instanceof SQLSubqueryTableSource && ((SQLSubqueryTableSource)x.getFrom()).getSelect().getQuery() instanceof SQLSelectQueryBlock) {
         SQLSelect select = (SQLSelect)x.getParent();
         SQLSelectQueryBlock subQuery = ((SQLSubqueryTableSource)x.getFrom()).getSelect().getQueryBlock();
         subQuery.mergeLimit(x.getLimit());
         x.setLimit((SQLLimit)null);
         select.setQuery(subQuery);
         this.context.queryBlock = subQuery;
         this.context.fixLimit();
         subQuery.accept(this);
      }

      if (x.getParent() instanceof SQLUnionQuery && x.getWhere() == null && x.getOrderBy() == null && allColumn && x.getLimit() != null && x.getFrom() instanceof SQLSubqueryTableSource && ((SQLSubqueryTableSource)x.getFrom()).getSelect().getQuery() instanceof SQLSelectQueryBlock) {
         SQLUnionQuery union = (SQLUnionQuery)x.getParent();
         SQLSelectQueryBlock subQuery = ((SQLSubqueryTableSource)x.getFrom()).getSelect().getQueryBlock();
         subQuery.mergeLimit(x.getLimit());
         x.setLimit((SQLLimit)null);
         if (union.getLeft() == x) {
            union.setLeft(subQuery);
         } else {
            union.setRight(subQuery);
         }

         this.context.queryBlock = subQuery;
         this.context.fixLimit();
         subQuery.accept(this);
      }

      this.context = this.context.parent;
      return false;
   }

   public boolean visit(SQLUnionQuery x) {
      if (x.getLeft() != null) {
         x.getLeft().accept(this);
      }

      if (x.getRight() != null) {
         x.getRight().accept(this);
      }

      if (x.getLeft() instanceof SQLSelectQueryBlock && x.getRight() instanceof SQLSelectQueryBlock) {
         if (x.getOperator() == SQLUnionOperator.MINUS) {
            SQLSelectQueryBlock left = (SQLSelectQueryBlock)x.getLeft().clone();
            SQLSelectQueryBlock right = (SQLSelectQueryBlock)x.getRight().clone();
            left.setLimit((SQLLimit)null);
            right.setLimit((SQLLimit)null);
            boolean eqNonLimit = left.toString().equals(right.toString());
            if (eqNonLimit) {
               left = (SQLSelectQueryBlock)x.getLeft().clone();
               right = (SQLSelectQueryBlock)x.getRight();
               SQLLimit leftLimit = left.getLimit();
               SQLLimit rightLimit = right.getLimit();
               if ((leftLimit != null || rightLimit != null) && (leftLimit == null || !leftLimit.equals(rightLimit))) {
                  if (leftLimit == null) {
                     SQLExpr rightOffset = rightLimit.getOffset();
                     if (rightOffset != null && !SQLIntegerExpr.isZero(rightOffset)) {
                        return false;
                     }

                     SQLLimit limit = new SQLLimit();
                     limit.setOffset(rightLimit.getRowCount());
                     left.setLimit(limit);
                  } else {
                     SQLExpr rightOffset = rightLimit.getOffset();
                     if (rightOffset != null && !SQLIntegerExpr.isZero(rightOffset)) {
                        return false;
                     }

                     SQLExpr leftOffset = leftLimit.getOffset();
                     if (leftOffset != null && !SQLIntegerExpr.isZero(leftOffset)) {
                        return false;
                     }

                     SQLExpr rightRowCount = rightLimit.getRowCount();
                     SQLExpr leftRowCount = leftLimit.getRowCount();
                     SQLLimit limit = new SQLLimit();
                     limit.setOffset(rightRowCount);
                     limit.setRowCount(substract(leftRowCount, rightRowCount));
                     if (SQLIntegerExpr.isZero(limit.getRowCount())) {
                        limit.setRowCount(0);
                        limit.setOffset((SQLExpr)null);
                        if (left.getOrderBy() != null) {
                           left.setOrderBy((SQLOrderBy)null);
                        }
                     }

                     left.setLimit(limit);
                  }
               } else {
                  left.setLimit(new SQLLimit(0));
               }

               SQLObject parent = x.getParent();
               if (parent instanceof SQLSelect) {
                  SQLSelect select = (SQLSelect)parent;
                  select.setQuery(left);
               } else if (parent instanceof SQLUnionQuery) {
                  SQLUnionQuery union = (SQLUnionQuery)parent;
                  if (union.getLeft() == x) {
                     union.setLeft(left);
                  } else {
                     union.setRight(left);
                  }
               }
            }
         } else if (x.getOperator() == SQLUnionOperator.INTERSECT) {
            SQLSelectQueryBlock left = (SQLSelectQueryBlock)x.getLeft().clone();
            SQLSelectQueryBlock right = (SQLSelectQueryBlock)x.getRight().clone();
            left.setLimit((SQLLimit)null);
            right.setLimit((SQLLimit)null);
            boolean eqNonLimit = left.toString().equals(right.toString());
            if (eqNonLimit) {
               left = (SQLSelectQueryBlock)x.getLeft().clone();
               right = (SQLSelectQueryBlock)x.getRight();
               SQLLimit leftLimit = left.getLimit();
               SQLLimit rightLimit = right.getLimit();
               if (rightLimit != null && !rightLimit.equals(leftLimit)) {
                  if (leftLimit == null) {
                     left.setLimit(rightLimit.clone());
                  } else {
                     SQLLimit limit = new SQLLimit();
                     SQLExpr rightOffset = rightLimit.getOffset();
                     SQLExpr leftOffset = leftLimit.getOffset();
                     if (leftOffset == null) {
                        limit.setOffset(rightOffset);
                     } else if (rightOffset == null) {
                        limit.setOffset(leftOffset);
                     } else if (rightOffset.equals(leftOffset)) {
                        limit.setOffset(leftOffset);
                     } else {
                        if (!(leftOffset instanceof SQLIntegerExpr) || !(rightOffset instanceof SQLIntegerExpr)) {
                           return false;
                        }

                        limit.setOffset(SQLIntegerExpr.greatst((SQLIntegerExpr)leftOffset, (SQLIntegerExpr)rightOffset));
                     }

                     SQLExpr rightRowCount = rightLimit.getRowCount();
                     SQLExpr leftRowCount = leftLimit.getRowCount();
                     SQLExpr leftEnd = leftOffset == null ? leftRowCount : substract(leftRowCount, leftOffset);
                     SQLExpr rightEnd = rightOffset == null ? rightRowCount : substract(rightRowCount, rightOffset);
                     if (leftEnd != null && !(leftEnd instanceof SQLIntegerExpr) || rightEnd != null && !(rightEnd instanceof SQLIntegerExpr)) {
                        return false;
                     }

                     SQLIntegerExpr end = SQLIntegerExpr.least((SQLIntegerExpr)leftEnd, (SQLIntegerExpr)rightEnd);
                     if (limit.getOffset() == null) {
                        limit.setRowCount(end);
                     } else {
                        limit.setRowCount(substract(end, limit.getOffset()));
                     }

                     left.setLimit(limit);
                  }
               }

               SQLObject parent = x.getParent();
               if (parent instanceof SQLSelect) {
                  SQLSelect select = (SQLSelect)parent;
                  select.setQuery(left);
               } else if (parent instanceof SQLUnionQuery) {
                  SQLUnionQuery union = (SQLUnionQuery)parent;
                  if (union.getLeft() == x) {
                     union.setLeft(left);
                  } else {
                     union.setRight(left);
                  }
               }
            }
         }
      }

      return false;
   }

   private void removeSelectListRowNum(SQLSelectQueryBlock x) {
      SQLTableSource from = x.getFrom();
      SQLLimit limit = x.getLimit();
      if (limit == null && from instanceof SQLSubqueryTableSource && ((SQLSubqueryTableSource)from).getSelect().getQuery() instanceof SQLSelectQueryBlock) {
         limit = ((SQLSubqueryTableSource)from).getSelect().getQueryBlock().getLimit();
      }

      if (this.removeSelectListRownum) {
         List<SQLSelectItem> selectList = x.getSelectList();

         for(int i = selectList.size() - 1; i >= 0; --i) {
            SQLSelectItem selectItem = (SQLSelectItem)selectList.get(i);
            SQLExpr expr = selectItem.getExpr();
            if (this.isRowNum(expr) && limit != null) {
               selectList.remove(i);
            }
         }

      }
   }

   public boolean visit(SQLBinaryOpExpr x) {
      SQLExpr left = x.getLeft();
      SQLExpr right = x.getRight();
      SQLBinaryOperator op = x.getOperator();
      if (this.context != null && this.context.queryBlock != null) {
         boolean isRowNum = this.isRowNum(left);
         if (isRowNum) {
            if (op == SQLBinaryOperator.LessThan) {
               if (SQLUtils.replaceInParent((SQLExpr)x, (SQLExpr)null)) {
                  this.context.setLimit(decrement(right));
                  this.context.fixLimit();
               }

               return false;
            }

            if (op == SQLBinaryOperator.LessThanOrEqual) {
               if (SQLUtils.replaceInParent((SQLExpr)x, (SQLExpr)null)) {
                  this.context.setLimit(right);
                  this.context.fixLimit();
               }

               return false;
            }

            if (op == SQLBinaryOperator.Equality) {
               if (SQLUtils.replaceInParent((SQLExpr)x, (SQLExpr)null)) {
                  this.context.setLimit(right);
                  this.context.fixLimit();
               }

               return false;
            }

            if (op == SQLBinaryOperator.GreaterThanOrEqual) {
               if (SQLUtils.replaceInParent((SQLExpr)x, (SQLExpr)null)) {
                  this.context.setOffset(decrement(right));
                  this.context.fixLimit();
               }

               return false;
            }

            if (op == SQLBinaryOperator.GreaterThan) {
               if (SQLUtils.replaceInParent((SQLExpr)x, (SQLExpr)null)) {
                  this.context.setOffset(right);
                  this.context.fixLimit();
               }

               return false;
            }
         }

         return true;
      } else {
         return false;
      }
   }

   public boolean visit(SQLBetweenExpr x) {
      if (!this.isRowNum(x.getTestExpr())) {
         return true;
      } else {
         if (SQLUtils.replaceInParent((SQLExpr)x, (SQLExpr)null)) {
            SQLExpr offset = decrement(x.getBeginExpr());
            this.context.setOffset(offset);
            if (offset instanceof SQLIntegerExpr) {
               int val = ((SQLIntegerExpr)offset).getNumber().intValue();
               if (val < 0) {
                  offset = new SQLIntegerExpr(0);
               }
            }

            this.context.setLimit(substract(x.getEndExpr(), offset));
            SQLLimit limit = this.context.queryBlock.getLimit();
            if (limit != null) {
               limit.putAttribute("oracle.isFixLimit", Boolean.TRUE);
            }
         }

         return false;
      }
   }

   public boolean isRowNum(SQLExpr x) {
      if (x instanceof SQLIdentifierExpr) {
         SQLIdentifierExpr identifierExpr = (SQLIdentifierExpr)x;
         long nameHashCode64 = identifierExpr.nameHashCode64();
         if (nameHashCode64 == FnvHash.Constants.ROWNUM) {
            return true;
         }

         if (this.context != null && this.context.queryBlock != null && this.context.queryBlock.getFrom() instanceof SQLSubqueryTableSource && ((SQLSubqueryTableSource)this.context.queryBlock.getFrom()).getSelect().getQuery() instanceof SQLSelectQueryBlock) {
            SQLSelectQueryBlock subQueryBlock = ((SQLSubqueryTableSource)this.context.queryBlock.getFrom()).getSelect().getQueryBlock();
            SQLSelectItem selectItem = subQueryBlock.findSelectItem(nameHashCode64);
            this.context = new Context(this.context);
            this.context.queryBlock = subQueryBlock;

            boolean var7;
            try {
               if (selectItem == null || !this.isRowNum(selectItem.getExpr())) {
                  return false;
               }

               var7 = true;
            } finally {
               this.context = this.context.parent;
            }

            return var7;
         }
      }

      return false;
   }

   public static SQLExpr decrement(SQLExpr x) {
      if (x instanceof SQLIntegerExpr) {
         int val = ((SQLIntegerExpr)x).getNumber().intValue() - 1;
         return new SQLIntegerExpr(val);
      } else {
         return new SQLBinaryOpExpr(x.clone(), SQLBinaryOperator.Subtract, new SQLIntegerExpr(1));
      }
   }

   public static SQLExpr substract(SQLExpr left, SQLExpr right) {
      if (left == null && right == null) {
         return null;
      } else if (left == null) {
         return null;
      } else if (left instanceof SQLIntegerExpr && right instanceof SQLIntegerExpr) {
         int rightVal = Math.max(0, ((SQLIntegerExpr)right).getNumber().intValue());
         int leftVal = ((SQLIntegerExpr)left).getNumber().intValue();
         int val = leftVal - rightVal;
         if (val < 0) {
            val = 0;
         }

         return new SQLIntegerExpr(val);
      } else {
         return new SQLBinaryOpExpr(left, SQLBinaryOperator.Subtract, right);
      }
   }

   public static SQLExpr increment(SQLExpr x) {
      if (x instanceof SQLIntegerExpr) {
         int val = ((SQLIntegerExpr)x).getNumber().intValue() + 1;
         return new SQLIntegerExpr(val);
      } else {
         return new SQLBinaryOpExpr(x.clone(), SQLBinaryOperator.Add, new SQLIntegerExpr(1));
      }
   }

   public static class Context {
      public final Context parent;
      public SQLSelectQueryBlock queryBlock;

      public Context(Context parent) {
         this.parent = parent;
      }

      void setLimit(SQLExpr x) {
         if (x instanceof SQLIntegerExpr) {
            int val = ((SQLIntegerExpr)x).getNumber().intValue();
            if (val < 0) {
               x = new SQLIntegerExpr(0);
            }
         }

         SQLLimit limit = this.queryBlock.getLimit();
         if (limit == null) {
            limit = new SQLLimit();
            this.queryBlock.setLimit(limit);
         }

         limit.setRowCount(x);
      }

      void fixLimit() {
         SQLLimit limit = this.queryBlock.getLimit();
         if (limit != null) {
            if (limit.getAttribute("oracle.isFixLimit") != Boolean.TRUE) {
               if (limit.getRowCount() != null && limit.getOffset() != null) {
                  if (limit.getRowCount() instanceof SQLIntegerExpr && limit.getOffset() instanceof SQLIntegerExpr) {
                     SQLIntegerExpr rowCountExpr = SQLIntegerExpr.substract((SQLIntegerExpr)limit.getRowCount(), (SQLIntegerExpr)limit.getOffset());
                     limit.setRowCount(rowCountExpr);
                  } else {
                     limit.setRowCount(OracleRowNumToLimit.substract(limit.getRowCount(), limit.getOffset()));
                  }

                  limit.putAttribute("oracle.isFixLimit", Boolean.TRUE);
               }

            }
         }
      }

      void setOffset(SQLExpr x) {
         if (x instanceof SQLIntegerExpr) {
            int val = ((SQLIntegerExpr)x).getNumber().intValue();
            if (val < 0) {
               x = new SQLIntegerExpr(0);
            }
         }

         SQLLimit limit = this.queryBlock.getLimit();
         if (limit == null) {
            limit = new SQLLimit();
            this.queryBlock.setLimit(limit);
         }

         limit.setOffset(x);
      }
   }
}
