package util.sqlparse.visitor.sqlserver.visitor;

import com.alibaba.druid.sql.ast.SQLExpr;
import com.alibaba.druid.sql.ast.SQLLimit;
import com.alibaba.druid.sql.ast.SQLName;
import com.alibaba.druid.sql.ast.SQLObject;
import com.alibaba.druid.sql.ast.SQLStatement;
import com.alibaba.druid.sql.ast.expr.SQLBetweenExpr;
import com.alibaba.druid.sql.ast.expr.SQLBigIntExpr;
import com.alibaba.druid.sql.ast.expr.SQLBinaryExpr;
import com.alibaba.druid.sql.ast.expr.SQLBinaryOpExpr;
import com.alibaba.druid.sql.ast.expr.SQLBooleanExpr;
import com.alibaba.druid.sql.ast.expr.SQLCaseExpr;
import com.alibaba.druid.sql.ast.expr.SQLCharExpr;
import com.alibaba.druid.sql.ast.expr.SQLDateExpr;
import com.alibaba.druid.sql.ast.expr.SQLDateTimeExpr;
import com.alibaba.druid.sql.ast.expr.SQLDecimalExpr;
import com.alibaba.druid.sql.ast.expr.SQLDoubleExpr;
import com.alibaba.druid.sql.ast.expr.SQLExistsExpr;
import com.alibaba.druid.sql.ast.expr.SQLFloatExpr;
import com.alibaba.druid.sql.ast.expr.SQLHexExpr;
import com.alibaba.druid.sql.ast.expr.SQLInListExpr;
import com.alibaba.druid.sql.ast.expr.SQLInSubQueryExpr;
import com.alibaba.druid.sql.ast.expr.SQLIntegerExpr;
import com.alibaba.druid.sql.ast.expr.SQLJSONExpr;
import com.alibaba.druid.sql.ast.expr.SQLListExpr;
import com.alibaba.druid.sql.ast.expr.SQLMethodInvokeExpr;
import com.alibaba.druid.sql.ast.expr.SQLNCharExpr;
import com.alibaba.druid.sql.ast.expr.SQLNullExpr;
import com.alibaba.druid.sql.ast.expr.SQLNumberExpr;
import com.alibaba.druid.sql.ast.expr.SQLRealExpr;
import com.alibaba.druid.sql.ast.expr.SQLSmallIntExpr;
import com.alibaba.druid.sql.ast.expr.SQLTimeExpr;
import com.alibaba.druid.sql.ast.expr.SQLTimestampExpr;
import com.alibaba.druid.sql.ast.expr.SQLTinyIntExpr;
import com.alibaba.druid.sql.ast.expr.SQLUnaryExpr;
import com.alibaba.druid.sql.ast.expr.SQLValuableExpr;
import com.alibaba.druid.sql.ast.expr.SQLVariantRefExpr;
import com.alibaba.druid.sql.ast.statement.SQLAssignItem;
import com.alibaba.druid.sql.ast.statement.SQLInsertStatement;
import com.alibaba.druid.sql.ast.statement.SQLReplaceStatement;
import com.alibaba.druid.sql.ast.statement.SQLSelect;
import com.alibaba.druid.sql.ast.statement.SQLSelectItem;
import com.alibaba.druid.sql.ast.statement.SQLUpdateSetItem;
import com.alibaba.druid.sql.ast.statement.SQLValuesTableSource;
import com.alibaba.druid.sql.dialect.sqlserver.ast.stmt.SQLServerMergeStatement;
import java.util.ArrayList;
import java.util.List;
import util.sqlparse.visitor.common.memo.FieldMemo;
import util.sqlparse.visitor.common.memo.ValueMemo;
import util.sqlparse.visitor.common.scope.Scope;

public class FieldVisitor extends ParseVisitor {
   private static final String QUESTION_MARK = "?";
   private static final String FUNC_IFNULL = "ifnull";
   private static final String FUNC_NVL = "nvl";
   private final util.sqlparse.visitor.common.utils.Alias alias = new util.sqlparse.visitor.common.utils.Alias();

   public FieldVisitor(Scope scope) {
      super(scope);
   }

   public boolean visit(SQLVariantRefExpr x) {
      String value = x.toString();
      if (value == null || !value.startsWith("@") && !value.startsWith("#")) {
         this.setValue(x);
         return true;
      } else {
         return true;
      }
   }

   private void setValue(SQLExpr valueExpr) {
      SQLObject parent = valueExpr.getParent();
      if (parent != null) {
         if (parent instanceof SQLUnaryExpr) {
            this.setUnary(valueExpr, parent);
         } else if (parent instanceof SQLBinaryOpExpr) {
            this.setBinary(valueExpr, parent);
         } else if (parent instanceof SQLCaseExpr) {
            this.setCase(valueExpr, parent);
         } else if (parent instanceof SQLCaseExpr.Item) {
            this.setCase(valueExpr, parent);
         } else if (parent instanceof SQLMethodInvokeExpr) {
            this.setMethod(valueExpr, parent);
         } else if (parent instanceof SQLExistsExpr) {
            this.setExists(valueExpr, parent);
         } else if (parent instanceof SQLInListExpr) {
            this.setInList(valueExpr, parent);
         } else if (parent instanceof SQLInsertStatement) {
            this.setInsertOrReplaceItem(valueExpr, parent);
         } else if (parent instanceof SQLInsertStatement.ValuesClause) {
            this.setInsertOrReplaceItem(valueExpr, parent);
         } else if (parent instanceof SQLServerMergeStatement.SQLServerMergeInsertClause) {
            this.setMergeInsertItem(valueExpr, parent);
         } else if (parent instanceof SQLServerMergeStatement.SQLServerMergeUpdateClause) {
            this.setMergeUpdateItem(valueExpr, parent);
         } else if (parent instanceof SQLUpdateSetItem) {
            this.setUpdateItem(valueExpr, parent);
         } else if (parent instanceof SQLInSubQueryExpr) {
            this.setInSubQuery(valueExpr, parent);
         } else if (parent instanceof SQLLimit) {
            this.setLimit(valueExpr, parent);
         } else if (parent instanceof SQLBetweenExpr) {
            this.setBetween(valueExpr, parent);
         } else if (parent instanceof SQLSelectItem) {
            this.setSelectItem(valueExpr, parent);
         } else if (parent instanceof SQLListExpr) {
            this.setListExpr(valueExpr, parent);
         } else {
            this.setOther(valueExpr, parent);
         }
      }

   }

   private void setMergeInsertItem(SQLExpr valueExpr, SQLObject parent) {
      SQLServerMergeStatement.SQLServerMergeInsertClause clause = (SQLServerMergeStatement.SQLServerMergeInsertClause)parent;
      int index = 0;

      for(int i = 0; i < clause.getValues().size(); ++i) {
         if (clause.getValues().get(i) == valueExpr) {
            index = i;
            break;
         }
      }

      List<SQLExpr> columns = clause.getColumns();
      if (columns != null && columns.size() > 0) {
         int size = columns.size();
         index %= size;
         SQLExpr columnExpr = (SQLExpr)columns.get(index);
         ValueMemo value = this.getValueMemo(columnExpr, valueExpr);
         value.ref = valueExpr;
         value.expr = parent;
         value.value = this.getValue(valueExpr);
         value.isPreDefined = value.toString().equals("?");
         value.operator = "SET";
         this.scope.getValues().add(value);
      }

   }

   private void setMergeUpdateItem(SQLExpr valueExpr, SQLObject parent) {
      SQLServerMergeStatement.SQLServerMergeUpdateClause clause = (SQLServerMergeStatement.SQLServerMergeUpdateClause)parent;
      SQLUpdateSetItem item = (SQLUpdateSetItem)clause.getItems().get(0);

      for(int i = 0; i < clause.getItems().size(); ++i) {
         if (((SQLUpdateSetItem)clause.getItems().get(i)).getValue() == valueExpr) {
            item = (SQLUpdateSetItem)clause.getItems().get(i);
            break;
         }
      }

      ValueMemo value = this.getValueMemo(item.getColumn(), valueExpr);
      value.ref = valueExpr;
      value.expr = parent;
      value.value = this.getValue(valueExpr);
      value.isPreDefined = value.toString().equals("?");
      value.operator = "SET";
      this.scope.getValues().add(value);
   }

   private void setUnary(SQLExpr valueExpr, SQLObject parent) {
      ValueMemo value = this.getValueMemo((SQLExpr)null, valueExpr);
      value.ref = valueExpr;
      value.expr = parent;
      value.value = this.getValue(valueExpr);
      if (value.operator == null) {
         SQLUnaryExpr unary = (SQLUnaryExpr)parent;
         value.operator = unary.getOperator().name;
      } else {
         value.operator = value.operator.toUpperCase();
      }

      value.isPreDefined = value.toString().equals("?");
      this.scope.getValues().add(value);
   }

   private void setBinary(SQLExpr valueExpr, SQLObject parent) {
      SQLBinaryOpExpr opExpr = (SQLBinaryOpExpr)parent;
      SQLExpr leftExpr = opExpr.getLeft();
      SQLExpr rightExpr = opExpr.getRight();
      SQLExpr varExpr;
      if (leftExpr.equals(valueExpr)) {
         varExpr = rightExpr;
      } else {
         varExpr = leftExpr;
      }

      ValueMemo value = this.getValueMemo(varExpr, valueExpr);
      value.ref = valueExpr;
      value.expr = parent;
      value.value = this.getValue(valueExpr);
      value.operator = opExpr.getOperator().getName().toUpperCase();
      value.isPreDefined = value.toString().equals("?");
      this.scope.getValues().add(value);
   }

   private void setCase(SQLExpr valueExpr, SQLObject parent) {
      if (parent instanceof SQLCaseExpr) {
         SQLCaseExpr caseExpr = (SQLCaseExpr)parent;
         ValueMemo value = this.getValueMemo(caseExpr.getValueExpr(), valueExpr);
         value.ref = valueExpr;
         value.expr = caseExpr.getElseExpr();
         value.value = this.getValue(valueExpr);
         value.operator = "CASE";
         value.isPreDefined = value.toString().equals("?");
         this.scope.getValues().add(value);
      } else {
         SQLCaseExpr.Item itemExpr = (SQLCaseExpr.Item)parent;
         SQLCaseExpr caseExpr = (SQLCaseExpr)itemExpr.getParent();
         ValueMemo value = this.getValueMemo(caseExpr.getValueExpr(), valueExpr);
         value.ref = valueExpr;
         value.expr = itemExpr;
         value.value = this.getValue(valueExpr);
         value.operator = "CASE";
         value.isPreDefined = value.toString().equals("?");
         this.scope.getValues().add(value);
      }
   }

   private void setMethod(SQLExpr valueExpr, SQLObject parent) {
      if (!valueExpr.toString().equals("'%'")) {
         SQLMethodInvokeExpr methodExpr = (SQLMethodInvokeExpr)parent;
         String method = methodExpr.getMethodName();
         boolean done = false;
         if (method.equalsIgnoreCase("ifnull") || method.equalsIgnoreCase("nvl")) {
            SQLExpr expr = (SQLExpr)methodExpr.getArguments().get(0);
            ValueMemo value = this.getValueMemo(expr, valueExpr, true);
            if (value != null && value.field != null) {
               value.ref = valueExpr;
               value.expr = parent;
               value.value = this.getValue(valueExpr);
               value.operator = method.toUpperCase();
               value.isPreDefined = value.toString().equals("?");
               this.scope.getValues().add(value);
               done = true;
            }
         }

         if (!done) {
            List<String> operators = new ArrayList();
            SQLExpr expr = this.getSqlExpr(valueExpr.getParent(), valueExpr, operators);
            ValueMemo value = this.getValueMemo(expr, valueExpr);
            value.ref = valueExpr;
            value.expr = parent;
            value.value = this.getValue(valueExpr);
            if (value.operator == null) {
               if (operators.size() > 0) {
                  value.operator = (String)operators.get(0);
               } else {
                  value.operator = "OTHER";
               }
            }

            value.isPreDefined = value.toString().equals("?");
            this.scope.getValues().add(value);
         }

      }
   }

   private SQLExpr getSqlExpr(SQLObject x, SQLObject y, List<String> operators) {
      if (!(x instanceof SQLName) && !(x instanceof SQLValuableExpr)) {
         if (x instanceof SQLSelectItem) {
            return ((SQLSelectItem)x).getExpr();
         } else if (x == null) {
            return null;
         } else if (x instanceof SQLStatement) {
            return null;
         } else if (x instanceof SQLSelect) {
            return null;
         } else {
            if (x instanceof SQLBinaryOpExpr) {
               SQLBinaryOpExpr bin = (SQLBinaryOpExpr)x;
               if (bin.getOperator() != null) {
                  switch (bin.getOperator()) {
                     case Like:
                     case Like2:
                     case Like4:
                     case Likec:
                        SQLExpr cmp = bin.getLeft().equals(y) ? bin.getRight() : bin.getLeft();
                        operators.add("LIKE");
                        return cmp;
                     case Equality:
                     case GreaterThan:
                     case GreaterThanOrEqual:
                     case LessThan:
                     case LessThanOrEqual:
                        SQLExpr cmp2 = bin.getLeft().equals(y) ? bin.getRight() : bin.getLeft();
                        operators.add(bin.getOperator().getName().toUpperCase());
                        return cmp2;
                     case BooleanAnd:
                     case BooleanOr:
                        if (y instanceof SQLExpr) {
                           operators.add("OTHER");
                           return (SQLExpr)y;
                        }
                  }
               }
            }

            if (x instanceof SQLInListExpr) {
               operators.add("IN");
               SQLInListExpr inList = (SQLInListExpr)x;
               int idx = inList.getTargetList().indexOf(x);
               return idx != -1 ? (SQLExpr)inList.getTargetList().get(idx) : inList.getExpr();
            } else {
               SQLObject parent = x.getParent();
               return !(parent instanceof SQLExpr) ? null : this.getSqlExpr(parent, x, operators);
            }
         }
      } else {
         return (SQLExpr)x;
      }
   }

   private void setExists(SQLExpr valueExpr, SQLObject parent) {
      ValueMemo value = this.getValueMemo((SQLExpr)null, valueExpr);
      value.ref = valueExpr;
      value.expr = parent;
      value.value = this.getValue(valueExpr);
      value.isPreDefined = value.toString().equals("?");
      this.scope.getValues().add(value);
   }

   private void setInList(SQLExpr valueExpr, SQLObject parent) {
      SQLInListExpr inListExpr = (SQLInListExpr)parent;
      ValueMemo value = this.getValueMemo(inListExpr.getExpr(), valueExpr);
      value.ref = valueExpr;
      value.expr = inListExpr;
      value.value = this.getValue(valueExpr);
      value.isPreDefined = value.toString().equals("?");
      value.operator = "IN";
      this.scope.getValues().add(value);
   }

   private void setInsertOrReplaceItem(SQLExpr valueExpr, SQLObject parent) {
      SQLInsertStatement.ValuesClause clause = null;
      if (parent instanceof SQLInsertStatement) {
         SQLInsertStatement stmt = (SQLInsertStatement)parent;
         clause = stmt.getValues();
      } else {
         clause = (SQLInsertStatement.ValuesClause)parent;
      }

      int index = 0;

      for(int i = 0; i < clause.getValues().size(); ++i) {
         if (clause.getValues().get(i) == valueExpr) {
            index = i;
            break;
         }
      }

      List<SQLExpr> columns = null;
      if (clause.getParent() instanceof SQLInsertStatement) {
         SQLInsertStatement insert = (SQLInsertStatement)clause.getParent();
         columns = insert.getColumns();
      } else if (clause.getParent() instanceof SQLReplaceStatement) {
         SQLReplaceStatement replace = (SQLReplaceStatement)clause.getParent();
         columns = replace.getColumns();
      }

      if (columns != null && columns.size() > 0) {
         int size = columns.size();
         index %= size;
         SQLExpr columnExpr = (SQLExpr)columns.get(index);
         ValueMemo value = this.getValueMemo(columnExpr, valueExpr);
         value.ref = valueExpr;
         value.expr = parent;
         value.value = this.getValue(valueExpr);
         value.isPreDefined = value.toString().equals("?");
         value.operator = "SET";
         this.scope.getValues().add(value);
      } else {
         this.setOther(valueExpr, parent);
      }

   }

   private void setListExpr(SQLExpr valueExpr, SQLObject parent) {
      SQLObject pa = parent.getParent();
      if (pa instanceof SQLUpdateSetItem) {
         SQLUpdateSetItem setItem = (SQLUpdateSetItem)pa;
         SQLListExpr columns = (SQLListExpr)setItem.getColumn();
         int index = 0;
         SQLListExpr item = (SQLListExpr)parent;
         List<SQLExpr> items = item.getItems();

         for(int i = 0; i < items.size(); ++i) {
            if (items.get(i) == valueExpr) {
               index = i;
               break;
            }
         }

         ValueMemo value = this.getValueMemo((SQLExpr)columns.getItems().get(index), valueExpr);
         value.ref = valueExpr;
         value.expr = parent;
         value.value = this.getValue(valueExpr);
         value.isPreDefined = value.toString().equals("?");
         value.operator = "SET";
         this.scope.getValues().add(value);
      } else if (pa instanceof SQLAssignItem) {
         SQLAssignItem assignItem = (SQLAssignItem)pa;
         SQLListExpr target = (SQLListExpr)assignItem.getTarget();
         SQLListExpr value = (SQLListExpr)assignItem.getValue();
         List<SQLExpr> targetItems = target.getItems();
         List<SQLExpr> valueItems = value.getItems();
         int index = 0;

         for(int i = 0; i < valueItems.size(); ++i) {
            if (valueItems.get(i) == valueExpr) {
               index = i;
               break;
            }
         }

         ValueMemo val = this.getValueMemo((SQLExpr)targetItems.get(index), valueExpr);
         val.ref = valueExpr;
         val.expr = parent;
         val.value = this.getValue(valueExpr);
         val.isPreDefined = value.toString().equals("?");
         val.operator = "SET";
         this.scope.getValues().add(val);
      } else if (pa instanceof SQLValuesTableSource) {
         SQLValuesTableSource tableSource = (SQLValuesTableSource)pa;
         int index = 0;

         for(int i = 0; i < ((SQLListExpr)parent).getItems().size(); ++i) {
            if (((SQLListExpr)parent).getItems().get(i) == valueExpr) {
               index = i;
               break;
            }
         }

         List<SQLName> columns = tableSource.getColumns();
         if (columns != null && columns.size() > 0) {
            int size = columns.size();
            index %= size;
            SQLExpr columnExpr = (SQLExpr)columns.get(index);
            ValueMemo value = this.getValueMemo(columnExpr, valueExpr);
            value.ref = valueExpr;
            value.expr = parent;
            value.value = this.getValue(valueExpr);
            value.isPreDefined = value.toString().equals("?");
            value.operator = "SET";
            this.scope.getValues().add(value);
         }
      } else {
         this.setOther(valueExpr, parent);
      }

   }

   private void setUpdateItem(SQLExpr valueExpr, SQLObject parent) {
      SQLUpdateSetItem item = (SQLUpdateSetItem)parent;
      ValueMemo value = this.getValueMemo(item.getColumn(), valueExpr);
      value.ref = valueExpr;
      value.expr = parent;
      value.value = this.getValue(valueExpr);
      value.isPreDefined = value.toString().equals("?");
      value.operator = "SET";
      this.scope.getValues().add(value);
   }

   private void setInSubQuery(SQLExpr valueExpr, SQLObject parent) {
      ValueMemo value = this.getValueMemo((SQLExpr)null, valueExpr);
      value.ref = valueExpr;
      value.expr = parent;
      value.value = this.getValue(valueExpr);
      value.operator = "IN";
      this.scope.getValues().add(value);
   }

   private void setLimit(SQLExpr valueExpr, SQLObject parent) {
      SQLLimit limit = (SQLLimit)parent;
      if (limit.getOffset() != null && limit.getOffset() == valueExpr) {
         ValueMemo value = new ValueMemo();
         value.ref = limit.getOffset();
         value.expr = parent;
         value.value = this.getValue(valueExpr);
         value.isPreDefined = valueExpr.toString().equals("?");
         value.operator = "LIMIT";
         FieldMemo field = new FieldMemo();
         field.name = parent + "limit_offset";
         value.field = field;
         this.scope.getValues().add(value);
      }

      if (limit.getRowCount() != null && limit.getRowCount() == valueExpr) {
         ValueMemo value = new ValueMemo();
         value.ref = limit.getRowCount();
         value.expr = parent;
         value.value = this.getValue(valueExpr);
         value.isPreDefined = valueExpr.toString().equals("?");
         value.operator = "LIMIT";
         FieldMemo field = new FieldMemo();
         field.name = parent + "limit_rowcount";
         value.field = field;
         this.scope.getValues().add(value);
      }

   }

   private void setBetween(SQLExpr valueExpr, SQLObject parent) {
      SQLBetweenExpr between = (SQLBetweenExpr)parent;
      SQLExpr expr = valueExpr == between.getBeginExpr() ? between.getBeginExpr() : between.getEndExpr();
      ValueMemo value = this.getValueMemo(expr, valueExpr);
      value.ref = valueExpr;
      value.expr = parent;
      value.value = this.getValue(valueExpr);
      value.isPreDefined = valueExpr.toString().equals("?");
      value.operator = "BETWEEN";
      this.scope.getValues().add(value);
   }

   private ValueMemo getValueMemo(SQLExpr x, SQLExpr valueExpr) {
      return this.getValueMemo(x, valueExpr, false);
   }

   private ValueMemo getValueMemo(SQLExpr x, SQLExpr valueExpr, boolean nullIfNoField) {
      ValueMemo value = this.scope.getCurrent().getFieldValueInfo(x);
      if (value == null && !nullIfNoField) {
         value = new ValueMemo(valueExpr);
         FieldMemo field = new FieldMemo();
         field.name = this.alias.aliasField();
         value.field = field;
      }

      return value;
   }

   private void setSelectItem(SQLExpr valueExpr, SQLObject parent) {
      ValueMemo value = this.getValueMemo(valueExpr, valueExpr);
      value.ref = valueExpr;
      value.expr = parent;
      value.value = this.getValue(valueExpr);
      value.operator = "SELECT_ITEM";
      this.scope.getValues().add(value);
   }

   private void setOther(SQLExpr valueExpr, SQLObject parent) {
      ValueMemo value = this.getValueMemo((SQLExpr)null, valueExpr);
      value.ref = valueExpr;
      value.expr = parent;
      value.value = this.getValue(valueExpr);
      value.operator = "COMPLEX";
      this.scope.getValues().add(value);
   }

   private Object getValue(SQLExpr value) {
      if (value instanceof SQLNullExpr) {
         return "NULL";
      } else if (value instanceof SQLHexExpr) {
         return value.toString();
      } else {
         return value instanceof SQLValuableExpr ? ((SQLValuableExpr)value).getValue() : value.toString();
      }
   }

   public boolean visit(SQLTimestampExpr x) {
      this.setValue(x);
      return true;
   }

   public boolean visit(SQLNumberExpr x) {
      this.setValue(x);
      return true;
   }

   public boolean visit(SQLDateExpr x) {
      this.setValue(x);
      return true;
   }

   public boolean visit(SQLCharExpr x) {
      this.setValue(x);
      return true;
   }

   public boolean visit(SQLNCharExpr x) {
      this.setValue(x);
      return true;
   }

   public boolean visit(SQLIntegerExpr x) {
      this.setValue(x);
      return true;
   }

   public boolean visit(SQLBigIntExpr x) {
      this.setValue(x);
      return true;
   }

   public boolean visit(SQLBooleanExpr x) {
      this.setValue(x);
      return true;
   }

   public boolean visit(SQLDateTimeExpr x) {
      this.setValue(x);
      return true;
   }

   public boolean visit(SQLBinaryExpr x) {
      this.setValue(x);
      return true;
   }

   public boolean visit(SQLDecimalExpr x) {
      this.setValue(x);
      return true;
   }

   public boolean visit(SQLDoubleExpr x) {
      this.setValue(x);
      return true;
   }

   public boolean visit(SQLFloatExpr x) {
      this.setValue(x);
      return true;
   }

   public boolean visit(SQLHexExpr x) {
      this.setValue(x);
      return true;
   }

   public boolean visit(SQLJSONExpr x) {
      this.setValue(x);
      return true;
   }

   public boolean visit(SQLRealExpr x) {
      this.setValue(x);
      return true;
   }

   public boolean visit(SQLSmallIntExpr x) {
      this.setValue(x);
      return true;
   }

   public boolean visit(SQLTimeExpr x) {
      this.setValue(x);
      return true;
   }

   public boolean visit(SQLTinyIntExpr x) {
      this.setValue(x);
      return true;
   }
}
