package com.chenyang.druid.sql.visitor;

import com.chenyang.druid.DbType;
import com.chenyang.druid.sql.SQLUtils;
import com.chenyang.druid.sql.ast.SQLExpr;
import com.chenyang.druid.sql.ast.SQLStatement;
import com.chenyang.druid.sql.ast.expr.SQLExprUtils;
import com.chenyang.druid.sql.ast.expr.SQLVariantRefExpr;
import com.chenyang.druid.sql.ast.statement.SQLDDLStatement;
import com.chenyang.druid.sql.ast.statement.SQLSelectQueryBlock;
import com.chenyang.druid.sql.ast.statement.SQLSelectStatement;
import com.chenyang.druid.sql.dialect.mysql.ast.statement.MySqlInsertStatement;
import com.chenyang.druid.sql.dialect.mysql.visitor.MySqlASTVisitor;
import com.chenyang.druid.sql.dialect.mysql.visitor.MySqlOutputVisitor;
import com.chenyang.druid.sql.dialect.mysql.visitor.MySqlParameterizedVisitor;
import com.chenyang.druid.sql.dialect.oracle.visitor.OracleASTParameterizedVisitor;
import com.chenyang.druid.sql.dialect.oracle.visitor.OracleParameterizedOutputVisitor;
import com.chenyang.druid.sql.dialect.phoenix.visitor.PhoenixOutputVisitor;
import com.chenyang.druid.sql.parser.SQLParserFeature;
import com.chenyang.druid.sql.parser.SQLParserUtils;
import com.chenyang.druid.sql.parser.SQLSelectListCache;
import com.chenyang.druid.sql.parser.SQLStatementParser;
import com.chenyang.druid.util.FnvHash;
import java.util.List;
import java.util.Map;
import java.util.TimeZone;

public class ParameterizedOutputVisitorUtils {
   private static final SQLParserFeature[] defaultFeatures;
   private static final SQLParserFeature[] defaultFeatures2;
   private static final SQLParserFeature[] defaultFeatures_tddl;
   private static final SQLParserFeature[] defaultFeatures2_tddl;

   public static String parameterize(String sql, DbType dbType) {
      return parameterize(sql, dbType, (List)null, (VisitorFeature[])null);
   }

   public static String parameterize(String sql, DbType dbType, VisitorFeature... features) {
      return parameterize(sql, dbType, (List)null, features);
   }

   public static String parameterize(String sql, DbType dbType, SQLSelectListCache selectListCache) {
      return parameterize(sql, dbType, selectListCache, (List)null);
   }

   public static String parameterize(String sql, DbType dbType, List<Object> outParameters) {
      return parameterize(sql, dbType, (SQLSelectListCache)null, outParameters);
   }

   public static String parameterize(String sql, DbType dbType, List<Object> outParameters, VisitorFeature... features) {
      return parameterize(sql, dbType, (SQLSelectListCache)null, outParameters, features);
   }

   public static String parameterizeForTDDL(String sql, DbType dbType, List<Object> outParameters, VisitorFeature... features) {
      return parameterizeForTDDL(sql, dbType, (SQLSelectListCache)null, outParameters, features);
   }

   private static void configVisitorFeatures(ParameterizedVisitor visitor, VisitorFeature... features) {
      if (features != null) {
         for(int i = 0; i < features.length; ++i) {
            visitor.config(features[i], true);
         }
      }

   }

   public static String parameterize(String sql, DbType dbType, SQLSelectListCache selectListCache, List<Object> outParameters, VisitorFeature... visitorFeatures) {
      SQLParserFeature[] features = outParameters == null ? defaultFeatures2 : defaultFeatures;
      return parameterize(sql, dbType, selectListCache, outParameters, features, visitorFeatures);
   }

   public static String parameterizeForTDDL(String sql, DbType dbType, SQLSelectListCache selectListCache, List<Object> outParameters, VisitorFeature... visitorFeatures) {
      SQLParserFeature[] features = outParameters == null ? defaultFeatures2_tddl : defaultFeatures_tddl;
      return parameterize(sql, dbType, selectListCache, outParameters, features, visitorFeatures);
   }

   public static String parameterize(String sql, DbType dbType, SQLSelectListCache selectListCache, List<Object> outParameters, SQLParserFeature[] features, VisitorFeature... visitorFeatures) {
      SQLStatementParser parser = SQLParserUtils.createSQLStatementParser(sql, dbType, features);
      if (selectListCache != null) {
         parser.setSelectListCache(selectListCache);
      }

      List<SQLStatement> statementList = parser.parseStatementList();
      if (statementList.size() == 0) {
         return sql;
      } else {
         StringBuilder out = new StringBuilder(sql.length());
         ParameterizedVisitor visitor = createParameterizedOutputVisitor(out, dbType);
         if (outParameters != null) {
            visitor.setOutputParameters(outParameters);
         }

         configVisitorFeatures(visitor, visitorFeatures);

         for(int i = 0; i < statementList.size(); ++i) {
            SQLStatement stmt = (SQLStatement)statementList.get(i);
            if (i > 0) {
               SQLStatement preStmt = (SQLStatement)statementList.get(i - 1);
               if (preStmt.getClass() == stmt.getClass()) {
                  StringBuilder buf = new StringBuilder();
                  ParameterizedVisitor v1 = createParameterizedOutputVisitor(buf, dbType);
                  preStmt.accept(v1);
                  if (out.toString().equals(buf.toString())) {
                     continue;
                  }
               }

               if (!preStmt.isAfterSemi()) {
                  out.append(";\n");
               } else {
                  out.append('\n');
               }
            }

            if (stmt.hasBeforeComment()) {
               stmt.getBeforeCommentsDirect().clear();
            }

            Class<?> stmtClass = stmt.getClass();
            if (stmtClass == SQLSelectStatement.class) {
               SQLSelectStatement selectStatement = (SQLSelectStatement)stmt;
               visitor.visit(selectStatement);
               visitor.postVisit(selectStatement);
            } else {
               stmt.accept(visitor);
            }
         }

         if (visitor.getReplaceCount() == 0 && parser.getLexer().getCommentCount() == 0 && sql.charAt(0) != '/') {
            boolean notUseOriginalSql = false;
            if (visitorFeatures != null) {
               for(VisitorFeature visitorFeature : visitorFeatures) {
                  if (visitorFeature == VisitorFeature.OutputParameterizedZeroReplaceNotUseOriginalSql) {
                     notUseOriginalSql = true;
                  }
               }
            }

            if (!notUseOriginalSql) {
               int ddlStmtCount = 0;

               for(SQLStatement stmt : statementList) {
                  if (stmt instanceof SQLDDLStatement) {
                     ++ddlStmtCount;
                  }
               }

               if (ddlStmtCount == statementList.size()) {
                  notUseOriginalSql = true;
               }
            }

            if (!notUseOriginalSql) {
               return sql;
            }
         }

         return out.toString();
      }
   }

   public static long parameterizeHash(String sql, DbType dbType, List<Object> outParameters) {
      return parameterizeHash(sql, dbType, (SQLSelectListCache)null, outParameters, (VisitorFeature[])null);
   }

   public static long parameterizeHash(String sql, DbType dbType, SQLSelectListCache selectListCache, List<Object> outParameters) {
      return parameterizeHash(sql, dbType, selectListCache, outParameters, (VisitorFeature[])null);
   }

   public static long parameterizeHash(String sql, DbType dbType, SQLSelectListCache selectListCache, List<Object> outParameters, VisitorFeature... visitorFeatures) {
      SQLParserFeature[] features = outParameters == null ? defaultFeatures2 : defaultFeatures;
      SQLStatementParser parser = SQLParserUtils.createSQLStatementParser(sql, dbType, features);
      if (selectListCache != null) {
         parser.setSelectListCache(selectListCache);
      }

      List<SQLStatement> statementList = parser.parseStatementList();
      int stmtSize = statementList.size();
      if (stmtSize == 0) {
         return 0L;
      } else {
         StringBuilder out = new StringBuilder(sql.length());
         ParameterizedVisitor visitor = createParameterizedOutputVisitor(out, dbType);
         if (outParameters != null) {
            visitor.setOutputParameters(outParameters);
         }

         configVisitorFeatures(visitor, visitorFeatures);
         if (stmtSize == 1) {
            SQLStatement stmt = (SQLStatement)statementList.get(0);
            if (stmt.getClass() == SQLSelectStatement.class) {
               SQLSelectStatement selectStmt = (SQLSelectStatement)stmt;
               if (selectListCache != null) {
                  SQLSelectQueryBlock queryBlock = selectStmt.getSelect().getQueryBlock();
                  if (queryBlock != null) {
                     String cachedSelectList = queryBlock.getCachedSelectList();
                     long cachedSelectListHash = queryBlock.getCachedSelectListHash();
                     if (cachedSelectList != null) {
                        visitor.config(VisitorFeature.OutputSkipSelectListCacheString, true);
                     }

                     visitor.visit(selectStmt);
                     return FnvHash.fnv1a_64_lower(cachedSelectListHash, out);
                  }
               }

               visitor.visit(selectStmt);
            } else if (stmt.getClass() == MySqlInsertStatement.class) {
               MySqlInsertStatement insertStmt = (MySqlInsertStatement)stmt;
               String columnsString = insertStmt.getColumnsString();
               if (columnsString != null) {
                  long columnsStringHash = insertStmt.getColumnsStringHash();
                  visitor.config(VisitorFeature.OutputSkipInsertColumnsString, true);
                  ((MySqlASTVisitor)visitor).visit(insertStmt);
                  return FnvHash.fnv1a_64_lower(columnsStringHash, out);
               }
            } else {
               stmt.accept(visitor);
            }

            return FnvHash.fnv1a_64_lower(out);
         } else {
            for(int i = 0; i < statementList.size(); ++i) {
               if (i > 0) {
                  out.append(";\n");
               }

               SQLStatement stmt = (SQLStatement)statementList.get(i);
               if (stmt.hasBeforeComment()) {
                  stmt.getBeforeCommentsDirect().clear();
               }

               Class<?> stmtClass = stmt.getClass();
               if (stmtClass == SQLSelectStatement.class) {
                  SQLSelectStatement selectStatement = (SQLSelectStatement)stmt;
                  visitor.visit(selectStatement);
                  visitor.postVisit(selectStatement);
               } else {
                  stmt.accept(visitor);
               }
            }

            return FnvHash.fnv1a_64_lower(out);
         }
      }
   }

   public static String parameterize(List<SQLStatement> statementList, DbType dbType) {
      StringBuilder out = new StringBuilder();
      ParameterizedVisitor visitor = createParameterizedOutputVisitor(out, dbType);

      for(int i = 0; i < statementList.size(); ++i) {
         if (i > 0) {
            out.append(";\n");
         }

         SQLStatement stmt = (SQLStatement)statementList.get(i);
         if (stmt.hasBeforeComment()) {
            stmt.getBeforeCommentsDirect().clear();
         }

         stmt.accept(visitor);
      }

      return out.toString();
   }

   public static String parameterize(SQLStatement stmt, DbType dbType) {
      StringBuilder out = new StringBuilder();
      ParameterizedVisitor visitor = createParameterizedOutputVisitor(out, dbType);
      if (stmt.hasBeforeComment()) {
         stmt.getBeforeCommentsDirect().clear();
      }

      stmt.accept(visitor);
      return out.toString();
   }

   public static SQLStatement parameterizeOf(String sql, DbType dbType) {
      return parameterizeOf(sql, (List)null, dbType);
   }

   public static SQLStatement parameterizeOf(String sql, List<Object> outParameters, DbType dbType) {
      if (dbType == DbType.mysql) {
         SQLStatement stmt = SQLUtils.parseSingleMysqlStatement(sql);
         MySqlParameterizedVisitor visitor = new MySqlParameterizedVisitor(outParameters);
         stmt.accept(visitor);
         return stmt;
      } else if (dbType == DbType.oracle) {
         SQLStatement stmt = SQLUtils.parseSingleStatement(sql, DbType.oracle);
         OracleASTParameterizedVisitor visitor = new OracleASTParameterizedVisitor(outParameters);
         stmt.accept(visitor);
         return stmt;
      } else {
         throw new UnsupportedOperationException();
      }
   }

   public static ParameterizedVisitor createParameterizedOutputVisitor(Appendable out, DbType dbType) {
      if (dbType == null) {
         dbType = DbType.other;
      }

      switch (dbType) {
         case oracle:
         case oceanbase_oracle:
            return new OracleParameterizedOutputVisitor(out);
         case mysql:
         case mariadb:
         case elastic_search:
            return new MySqlOutputVisitor(out, true);
         case phoenix:
            return new PhoenixOutputVisitor(out, true);
         default:
            return new SQLASTOutputVisitor(out, true);
      }
   }

   public static String restore(String sql, DbType dbType, List<Object> parameters) {
      List<SQLStatement> stmtList = SQLUtils.parseStatements(sql, dbType);
      StringBuilder out = new StringBuilder();
      SQLASTOutputVisitor visitor = SQLUtils.createOutputVisitor(out, dbType);
      visitor.setInputParameters(parameters);

      for(SQLStatement stmt : stmtList) {
         stmt.accept(visitor);
      }

      return out.toString();
   }

   public static String restore(String sql, DbType dbType, Map<String, Object> parameters) {
      if (parameters != null && parameters.size() != 0) {
         List<SQLStatement> stmtList = SQLUtils.parseStatements(sql, dbType);
         RestoreVisitor v = new RestoreVisitor(parameters);

         for(SQLStatement stmt : stmtList) {
            stmt.accept(v);
         }

         StringBuilder out = new StringBuilder(sql.length());
         SQLASTOutputVisitor visitor = SQLUtils.createOutputVisitor(out, dbType);

         for(SQLStatement stmt : stmtList) {
            stmt.accept(visitor);
         }

         return out.toString();
      } else {
         return sql;
      }
   }

   static {
      defaultFeatures = new SQLParserFeature[]{SQLParserFeature.EnableSQLBinaryOpExprGroup, SQLParserFeature.UseInsertColumnsCache, SQLParserFeature.OptimizedForParameterized};
      defaultFeatures2 = new SQLParserFeature[]{SQLParserFeature.EnableSQLBinaryOpExprGroup, SQLParserFeature.UseInsertColumnsCache, SQLParserFeature.OptimizedForParameterized, SQLParserFeature.OptimizedForForParameterizedSkipValue};
      defaultFeatures_tddl = new SQLParserFeature[]{SQLParserFeature.EnableSQLBinaryOpExprGroup, SQLParserFeature.UseInsertColumnsCache, SQLParserFeature.OptimizedForParameterized, SQLParserFeature.TDDLHint};
      defaultFeatures2_tddl = new SQLParserFeature[]{SQLParserFeature.EnableSQLBinaryOpExprGroup, SQLParserFeature.UseInsertColumnsCache, SQLParserFeature.OptimizedForParameterized, SQLParserFeature.OptimizedForForParameterizedSkipValue, SQLParserFeature.TDDLHint};
   }

   private static class RestoreVisitor extends SQLASTVisitorAdapter {
      Map<String, Object> parameters;
      TimeZone timeZone;

      public RestoreVisitor(Map<String, Object> parameters) {
         this.parameters = parameters;
      }

      public boolean visit(SQLVariantRefExpr x) {
         String name = x.getName();
         if (name.length() > 3) {
            char c0 = name.charAt(0);
            char c1 = name.charAt(1);
            char c1x = name.charAt(name.length() - 1);
            if (c0 == '#' && c1 == '{' && c1x == '}') {
               name.substring(2, name.length() - 1);
               Object value = this.parameters.get(x);
               SQLExpr expr = SQLExprUtils.fromJavaObject(value, this.timeZone);
               SQLUtils.replaceInParent((SQLExpr)x, (SQLExpr)expr);
            }
         }

         return true;
      }
   }
}
