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

import com.alibaba.druid.DbType;
import com.alibaba.druid.sql.SQLUtils;
import com.alibaba.druid.sql.ast.SQLExpr;
import com.alibaba.druid.sql.ast.SQLObject;
import com.alibaba.druid.sql.ast.SQLStatement;
import com.alibaba.druid.sql.ast.expr.SQLIdentifierExpr;
import com.alibaba.druid.sql.ast.statement.SQLCreateViewStatement;
import com.alibaba.druid.sql.ast.statement.SQLExprTableSource;
import com.alibaba.druid.sql.ast.statement.SQLJoinTableSource;
import com.alibaba.druid.sql.ast.statement.SQLSelect;
import com.alibaba.druid.sql.ast.statement.SQLSelectQueryBlock;
import com.alibaba.druid.sql.ast.statement.SQLSubqueryTableSource;
import com.alibaba.druid.sql.ast.statement.SQLWithSubqueryClause;
import com.alibaba.druid.sql.dialect.oracle.ast.stmt.OracleSelectSubqueryTableSource;
import com.alibaba.druid.sql.dialect.oracle.ast.stmt.OracleSelectTableReference;
import com.alibaba.druid.sql.dialect.oracle.visitor.OracleASTVisitorAdapter;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

public class FromSubqueryResolver extends OracleASTVisitorAdapter {
   private final List<SQLStatement> targetList;
   private final String viewName;
   private final Map<String, String> mappings = new LinkedHashMap();
   private int viewNameSeed = 1;

   public FromSubqueryResolver(List<SQLStatement> targetList, String viewName) {
      this.targetList = targetList;
      this.viewName = viewName;
   }

   public boolean visit(OracleSelectSubqueryTableSource x) {
      return this.visit((SQLSubqueryTableSource)x);
   }

   public boolean visit(SQLSubqueryTableSource x) {
      String subViewName = this.generateSubViewName();
      SQLObject parent = x.getParent();
      if (parent instanceof SQLSelectQueryBlock) {
         SQLSelectQueryBlock queryBlock = (SQLSelectQueryBlock)parent;
         queryBlock.setFrom(subViewName, x.getAlias());
      } else if (parent instanceof SQLJoinTableSource) {
         SQLJoinTableSource join = (SQLJoinTableSource)parent;
         if (join.getLeft() == x) {
            join.setLeft(subViewName, x.getAlias());
         } else if (join.getRight() == x) {
            join.setRight(subViewName, x.getAlias());
         }
      }

      SQLCreateViewStatement stmt = new SQLCreateViewStatement();
      stmt.setName(this.generateSubViewName());
      SQLSelect select = x.getSelect();
      stmt.setSubQuery(select);
      this.targetList.add(0, stmt);
      stmt.accept(new FromSubqueryResolver(this.targetList, this.viewName));
      return false;
   }

   public boolean visit(SQLExprTableSource x) {
      SQLExpr expr = x.getExpr();
      if (expr instanceof SQLIdentifierExpr) {
         SQLIdentifierExpr identifierExpr = (SQLIdentifierExpr)expr;
         String ident = identifierExpr.getName();
         String mappingIdent = (String)this.mappings.get(ident);
         if (mappingIdent != null) {
            x.setExpr((SQLExpr)(new SQLIdentifierExpr(mappingIdent)));
         }
      }

      return false;
   }

   public boolean visit(OracleSelectTableReference x) {
      return this.visit((SQLExprTableSource)x);
   }

   private String generateSubViewName() {
      return this.viewName + "_" + this.targetList.size();
   }

   public static List<SQLStatement> resolve(SQLCreateViewStatement stmt) {
      List<SQLStatement> targetList = new ArrayList();
      targetList.add(stmt);
      String viewName = SQLUtils.normalize(stmt.getName().getSimpleName());
      FromSubqueryResolver visitor = new FromSubqueryResolver(targetList, viewName);
      SQLWithSubqueryClause withSubqueryClause = stmt.getSubQuery().getWithSubQuery();
      if (withSubqueryClause != null) {
         stmt.getSubQuery().setWithSubQuery((SQLWithSubqueryClause)null);

         for(SQLWithSubqueryClause.Entry entry : withSubqueryClause.getEntries()) {
            String entryName = entry.getAlias();
            SQLCreateViewStatement entryStmt = new SQLCreateViewStatement();
            entryStmt.setOrReplace(true);
            entryStmt.setDbType(stmt.getDbType());
            String entryViewName = visitor.generateSubViewName();
            entryStmt.setName(entryViewName);
            entryStmt.setSubQuery(entry.getSubQuery());
            visitor.targetList.add(0, entryStmt);
            visitor.mappings.put(entryName, entryViewName);
            entryStmt.accept(visitor);
         }
      }

      stmt.accept(visitor);
      DbType dbType = stmt.getDbType();

      for(int i = 0; i < targetList.size() - 1; ++i) {
         SQLCreateViewStatement targetStmt = (SQLCreateViewStatement)targetList.get(i);
         targetStmt.setOrReplace(true);
         targetStmt.setDbType(dbType);
         targetStmt.setAfterSemi(true);
      }

      return targetList;
   }
}
