package com.chenyang.druid.sql.transform;

import com.chenyang.druid.DbType;
import com.chenyang.druid.sql.ast.SQLExpr;
import com.chenyang.druid.sql.ast.SQLObject;
import com.chenyang.druid.sql.ast.SQLOrderBy;
import com.chenyang.druid.sql.ast.expr.SQLAggregateExpr;
import com.chenyang.druid.sql.ast.expr.SQLIdentifierExpr;
import com.chenyang.druid.sql.ast.expr.SQLPropertyExpr;
import com.chenyang.druid.sql.ast.statement.SQLExprTableSource;
import com.chenyang.druid.sql.ast.statement.SQLSelectGroupByClause;
import com.chenyang.druid.sql.ast.statement.SQLSelectItem;
import com.chenyang.druid.sql.ast.statement.SQLSelectQueryBlock;
import com.chenyang.druid.sql.repository.SchemaObject;
import com.chenyang.druid.sql.visitor.SQLASTVisitorAdapter;
import com.chenyang.druid.util.FnvHash;
import java.util.HashMap;
import java.util.Map;

public class SQLRefactorVisitor extends SQLASTVisitorAdapter {
   private int havingLevel = 0;
   private int groupByLevel = 0;
   private char quote = '"';
   private Map<Long, TableMapping> tableMappings = new HashMap();

   public SQLRefactorVisitor(DbType dbType) {
      this.dbType = dbType;
      switch (dbType) {
         case mysql:
         case mariadb:
         case ads:
            this.quote = '`';
         default:
      }
   }

   public void addMapping(TableMapping mapping) {
      this.tableMappings.put(mapping.getSrcTableHash(), mapping);
   }

   public boolean visit(SQLExprTableSource x) {
      TableMapping mapping = this.findMapping(x);
      if (mapping == null) {
         return true;
      } else {
         String destTable = mapping.getDestTable();
         x.setExpr((SQLExpr)(new SQLIdentifierExpr(this.quote(destTable))));
         return false;
      }
   }

   private TableMapping findMapping(SQLExprTableSource x) {
      SchemaObject schemaObject = x.getSchemaObject();
      if (schemaObject == null) {
         return null;
      } else {
         long nameHashCode = FnvHash.hashCode64(schemaObject.getName());
         return (TableMapping)this.tableMappings.get(nameHashCode);
      }
   }

   public boolean visit(SQLIdentifierExpr x) {
      TableMapping mapping = null;
      if (this.groupByLevel > 0 || this.havingLevel > 0) {
         SQLSelectQueryBlock queryBlock = null;

         for(SQLObject parent = x.getParent(); parent != null; parent = parent.getParent()) {
            if (parent instanceof SQLSelectQueryBlock) {
               queryBlock = (SQLSelectQueryBlock)parent;
               break;
            }
         }

         boolean matchAlias = false;
         if (queryBlock != null) {
            for(SQLSelectItem item : queryBlock.getSelectList()) {
               if (item.alias_hash() == x.hashCode64()) {
                  matchAlias = true;
                  break;
               }
            }
         }

         if (matchAlias) {
            SQLObject parent = x.getParent();
            if (parent instanceof SQLOrderBy || parent instanceof SQLSelectGroupByClause) {
               return false;
            }

            if (this.havingLevel > 0) {
               boolean agg;
               for(agg = false; parent != null && !(parent instanceof SQLSelectQueryBlock); parent = parent.getParent()) {
                  if (parent instanceof SQLAggregateExpr) {
                     agg = true;
                     break;
                  }
               }

               if (!agg) {
                  return false;
               }
            }
         }
      }

      SQLObject ownerObject = x.getResolvedOwnerObject();
      if (ownerObject instanceof SQLExprTableSource) {
         mapping = this.findMapping((SQLExprTableSource)ownerObject);
      }

      if (mapping == null) {
         return false;
      } else {
         String srcName = x.getName();
         String mappingColumn = mapping.getMappingColumn(srcName);
         if (mappingColumn != null) {
            x.setName(this.quote(mappingColumn));
         }

         SQLObject parent = x.getParent();
         if (parent instanceof SQLSelectItem && ((SQLSelectItem)parent).getAlias() == null) {
            ((SQLSelectItem)parent).setAlias(srcName);
         }

         return false;
      }
   }

   public boolean visit(SQLSelectGroupByClause x) {
      ++this.groupByLevel;

      for(SQLExpr item : x.getItems()) {
         item.accept(this);
      }

      SQLExpr having = x.getHaving();
      if (having != null) {
         ++this.havingLevel;
         having.accept(this);
         --this.havingLevel;
      }

      --this.groupByLevel;
      return false;
   }

   public boolean visit(SQLPropertyExpr x) {
      TableMapping mapping = null;
      SchemaObject schemaObject = null;
      boolean aliasOwer = false;
      SQLObject ownerObject = x.getResolvedOwnerObject();
      if (ownerObject instanceof SQLExprTableSource) {
         SQLExprTableSource exprTableSource = (SQLExprTableSource)ownerObject;
         if (exprTableSource.getAlias() != null && x.getOwner() instanceof SQLIdentifierExpr && FnvHash.hashCode64(exprTableSource.getAlias()) == ((SQLIdentifierExpr)x.getOwner()).nameHashCode64()) {
            aliasOwer = true;
         }

         mapping = this.findMapping(exprTableSource);
         schemaObject = exprTableSource.getSchemaObject();
      }

      if (mapping == null) {
         return false;
      } else {
         String srcName = x.getName();
         String mappingColumn = mapping.getMappingColumn(srcName);
         if (mappingColumn != null) {
            x.setName(this.quote(mappingColumn));
         }

         SQLObject parent = x.getParent();
         if (parent instanceof SQLSelectItem && ((SQLSelectItem)parent).getAlias() == null) {
            ((SQLSelectItem)parent).setAlias(srcName);
         }

         if (x.getOwner() instanceof SQLIdentifierExpr && ((SQLIdentifierExpr)x.getOwner()).nameHashCode64() == mapping.getSrcTableHash() && !aliasOwer) {
            x.setOwner((SQLExpr)(new SQLIdentifierExpr(this.quote(mapping.getDestTable()))));
         }

         return false;
      }
   }

   private String quote(String name) {
      char[] chars = new char[name.length() + 2];
      name.getChars(0, name.length(), chars, 1);
      chars[0] = '`';
      chars[chars.length - 1] = '`';
      return new String(chars);
   }
}
