package com.alibaba.druid.sql.dialect.mysql.visitor.transform;

import com.alibaba.druid.sql.SQLUtils;
import com.alibaba.druid.sql.ast.SQLExpr;
import com.alibaba.druid.sql.ast.SQLName;
import com.alibaba.druid.sql.ast.SQLObject;
import com.alibaba.druid.sql.ast.expr.SQLBinaryOpExpr;
import com.alibaba.druid.sql.ast.expr.SQLExistsExpr;
import com.alibaba.druid.sql.ast.expr.SQLIdentifierExpr;
import com.alibaba.druid.sql.ast.expr.SQLInSubQueryExpr;
import com.alibaba.druid.sql.ast.expr.SQLPropertyExpr;
import com.alibaba.druid.sql.ast.statement.SQLExprTableSource;
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.dialect.oracle.visitor.OracleASTVisitorAdapter;
import com.alibaba.druid.util.FnvHash;

public class NameResolveVisitor extends OracleASTVisitorAdapter {
   public boolean visit(SQLIdentifierExpr x) {
      SQLObject parent = x.getParent();
      if (parent instanceof SQLBinaryOpExpr && x.getResolvedColumn() == null) {
         SQLBinaryOpExpr binaryOpExpr = (SQLBinaryOpExpr)parent;
         boolean isJoinCondition = binaryOpExpr.getLeft() instanceof SQLName && binaryOpExpr.getRight() instanceof SQLName;
         if (isJoinCondition) {
            return false;
         }
      }

      String name = x.getName();
      if ("ROWNUM".equalsIgnoreCase(name)) {
         return false;
      } else {
         long hash = x.nameHashCode64();
         SQLTableSource tableSource = null;
         if (hash != FnvHash.Constants.LEVEL && hash != FnvHash.Constants.CONNECT_BY_ISCYCLE && hash != FnvHash.Constants.SYSTIMESTAMP) {
            if (parent instanceof SQLPropertyExpr) {
               return false;
            } else {
               while(parent != null) {
                  if (parent instanceof SQLTableSource) {
                     return false;
                  }

                  if (parent instanceof SQLSelectQueryBlock) {
                     SQLSelectQueryBlock queryBlock = (SQLSelectQueryBlock)parent;
                     if (queryBlock.getInto() != null) {
                        return false;
                     }

                     if (queryBlock.getParent() instanceof SQLSelect) {
                        SQLObject pp = queryBlock.getParent().getParent();
                        if (pp instanceof SQLInSubQueryExpr || pp instanceof SQLExistsExpr) {
                           return false;
                        }
                     }

                     SQLTableSource from = queryBlock.getFrom();
                     if (from instanceof SQLExprTableSource || from instanceof SQLSubqueryTableSource) {
                        String alias = from.getAlias();
                        if (alias != null) {
                           boolean isRowNumColumn = this.isRowNumColumn(x, queryBlock);
                           boolean isAliasColumn = this.isAliasColumn(x, queryBlock);
                           if (!isRowNumColumn && !isAliasColumn) {
                              SQLUtils.replaceInParent((SQLExpr)x, (SQLExpr)(new SQLPropertyExpr(alias, name)));
                           }
                        }
                     }

                     return false;
                  }

                  parent = parent.getParent();
               }

               return true;
            }
         } else {
            return false;
         }
      }
   }

   public boolean visit(SQLPropertyExpr x) {
      String ownerName = x.getOwnernName();
      if (ownerName == null) {
         return super.visit((SQLPropertyExpr)x);
      } else {
         for(SQLObject parent = x.getParent(); parent != null; parent = parent.getParent()) {
            if (parent instanceof SQLSelectQueryBlock) {
               SQLSelectQueryBlock queryBlock = (SQLSelectQueryBlock)parent;
               SQLTableSource tableSource = queryBlock.findTableSource(ownerName);
               if (tableSource != null) {
                  String alias = tableSource.computeAlias();
                  if (ownerName.equalsIgnoreCase(alias) && !ownerName.equals(alias)) {
                     x.setOwner(alias);
                  }
                  break;
               }
            }
         }

         return super.visit((SQLPropertyExpr)x);
      }
   }

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

         if (source.getFrom() instanceof SQLSubqueryTableSource && ((SQLSubqueryTableSource)source.getFrom()).getSelect().getQuery() instanceof SQLSelectQueryBlock) {
            SQLSelectQueryBlock subQueryBlock = ((SQLSubqueryTableSource)source.getFrom()).getSelect().getQueryBlock();
            SQLSelectItem selectItem = subQueryBlock.findSelectItem(nameHashCode64);
            if (selectItem != null && this.isRowNumColumn(selectItem.getExpr(), subQueryBlock)) {
               return true;
            }
         }
      }

      return false;
   }

   public boolean isAliasColumn(SQLExpr x, SQLSelectQueryBlock source) {
      if (x instanceof SQLIdentifierExpr) {
         SQLIdentifierExpr identifierExpr = (SQLIdentifierExpr)x;
         long nameHashCode64 = identifierExpr.nameHashCode64();
         SQLSelectItem selectItem = source.findSelectItem(nameHashCode64);
         if (selectItem != null) {
            return true;
         }

         if (source.getFrom() instanceof SQLSubqueryTableSource && ((SQLSubqueryTableSource)source.getFrom()).getSelect().getQuery() instanceof SQLSelectQueryBlock) {
            SQLSelectQueryBlock subQueryBlock = ((SQLSubqueryTableSource)source.getFrom()).getSelect().getQueryBlock();
            if (this.isAliasColumn(x, subQueryBlock)) {
               return true;
            }
         }
      }

      return false;
   }
}
