package util.sqlparse.visitor.sqlserver.visitor;

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.SQLBinaryOpExpr;
import com.alibaba.druid.sql.ast.expr.SQLIdentifierExpr;
import com.alibaba.druid.sql.ast.expr.SQLMethodInvokeExpr;
import com.alibaba.druid.sql.ast.expr.SQLPropertyExpr;
import com.alibaba.druid.sql.ast.statement.SQLSelectItem;
import com.alibaba.druid.sql.ast.statement.SQLSelectQueryBlock;
import com.alibaba.druid.sql.ast.statement.SQLSelectStatement;
import com.alibaba.druid.util.StringUtils;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import util.JdbcUtil;
import util.sqlparse.visitor.common.Context;
import util.sqlparse.visitor.common.bean.FieldInfo;
import util.sqlparse.visitor.common.bean.SQLResult;
import util.sqlparse.visitor.common.memo.FieldMemo;
import util.sqlparse.visitor.common.scope.Scope;
import util.sqlparse.visitor.sqlserver.SQLParser;

public class FieldReplaceController {
   private final SQLResult result;
   private final Map params;
   private String replaceSchema;
   private String replaceTable;
   private String replaceColumn;
   private String expression;

   public FieldReplaceController(SQLResult result, Map params) {
      this.result = result;
      this.params = params;
      this.replaceSchema = this.normalize((String)params.get("replaceSchema"));
      this.replaceTable = this.normalize((String)params.get("replaceTable"));
      this.replaceColumn = this.normalize((String)params.get("replaceColumn"));
      this.expression = (String)params.get("expression");
   }

   public void perform() {
      SQLStatement statement = this.result.statement;
      System.out.println(statement.toString());
      List<FieldInfo> fields = this.result.outputs;
      Set<SQLObject> parsed = new HashSet();
      if (!this.preformInsertSelect(this.result.context, this.result.root, statement)) {
         this.params.put("isMatched", "false");

         for(FieldInfo fieldInfo : fields) {
            if (fieldInfo.getRelations().size() <= 0) {
               if (this.isHit(fieldInfo, this.result.isCaseSensitive)) {
                  this.repalceFromRef(fieldInfo.getMemo(), parsed);
                  this.params.put("isMatched", "true");
               }
            } else {
               for(FieldInfo fieldInfo1 : fieldInfo.getRelations()) {
                  if (this.isHit(fieldInfo1, this.result.isCaseSensitive)) {
                     for(FieldMemo memo : fieldInfo1.getMemos()) {
                        this.repalceFromRef(memo, parsed);
                     }

                     this.params.put("isMatched", "true");
                  }
               }
            }
         }

         this.params.put("newSql", statement.toString());
      }
   }

   private boolean preformInsertSelect(Context context, Scope root, SQLStatement statement) {
      boolean isInsertSelect = false;
      List<Scope> selScopes = null;
      String sql = (String)this.params.get("sql");
      if (sql != null && sql.length() > 0) {
         sql = sql.trim().toLowerCase();
         if (sql.startsWith("insert") && sql.contains("select")) {
            isInsertSelect = true;
            selScopes = this.getSelectScope(this.result.root);
         }

         if (!isInsertSelect) {
            return false;
         } else if (selScopes != null && selScopes.size() != 0) {
            SQLParser parser = new SQLParser();
            Scope virScope = new Scope();
            virScope.context = context;
            virScope.getChildren().addAll(selScopes);
            Map<String, FieldMemo> fields = new HashMap();
            Map<String, FieldInfo> fieldInfos = new HashMap();
            parser.getAllFields(virScope, fields, fieldInfos);
            this.result.fieldMemos = new ArrayList(fields.values());
            this.result.fields = new ArrayList(fieldInfos.values());
            fields.clear();
            List<FieldInfo> outputFields = new ArrayList();
            parser.getSelectFields(virScope, fieldInfos, fields, context.getTableMap(), outputFields);
            this.result.outputMemos = new ArrayList(fields.values());
            this.result.outputs = outputFields;
            fields.clear();
            List<FieldInfo> outputs = this.result.outputs;
            if (outputs != null && outputs.size() != 0) {
               Set<SQLObject> parsed = new HashSet();
               this.params.put("isMatched", "false");

               for(FieldInfo fieldInfo : outputs) {
                  if (fieldInfo.getRelations().size() <= 0) {
                     if (this.isHit(fieldInfo, this.result.isCaseSensitive)) {
                        this.repalceFromRef(fieldInfo.getMemo(), parsed);
                        this.params.put("isMatched", "true");
                     }
                  } else {
                     for(FieldInfo fieldInfo1 : fieldInfo.getRelations()) {
                        if (this.isHit(fieldInfo1, this.result.isCaseSensitive)) {
                           for(FieldMemo memo : fieldInfo1.getMemos()) {
                              this.repalceFromRef(memo, parsed);
                           }

                           this.params.put("isMatched", "true");
                        }
                     }
                  }
               }

               this.params.put("newSql", statement.toString());
               return true;
            } else {
               return true;
            }
         } else {
            return true;
         }
      } else {
         return false;
      }
   }

   private List<Scope> getSelectScope(Scope root) {
      if (root == null) {
         return new ArrayList();
      } else {
         List<Scope> scopes = new ArrayList();
         List<Scope> children = root.getChildren();

         for(Scope child : children) {
            SQLObject ref = child.ref;
            if (ref != null) {
               if (ref instanceof SQLSelectQueryBlock) {
                  scopes.add(child);
               }

               if (ref instanceof SQLSelectStatement) {
                  scopes.add(child);
               }
            }
         }

         if (scopes != null && scopes.size() > 0) {
            return scopes;
         } else {
            for(Scope child : children) {
               List<Scope> subScopes = this.getSelectScope(child);
               scopes.addAll(subScopes);
            }

            return scopes;
         }
      }
   }

   private void repalceFromRef(FieldMemo memo, Set<SQLObject> parsed) {
      SQLObject ref = memo.ref;
      if (this.isOutput(ref)) {
         if (!parsed.contains(ref)) {
            if (ref instanceof SQLPropertyExpr) {
               String newColumnName = this.expression.replaceAll("\\$\\{value}", ((SQLPropertyExpr)ref).toString());
               SQLIdentifierExpr newExpr = new SQLIdentifierExpr(newColumnName);
               if (ref.getParent() != null && ref.getParent() instanceof SQLMethodInvokeExpr) {
                  this.repalceMethodInvoke(ref, newExpr);
               } else if (ref.getParent() != null && ref.getParent() instanceof SQLBinaryOpExpr) {
                  this.replaceBinaryOpExpr(ref, newExpr);
               }
            } else if (ref instanceof SQLIdentifierExpr) {
               this.replaceIdentifier((SQLIdentifierExpr)ref);
            } else if (ref instanceof SQLSelectItem) {
               this.replaceSelectItem((SQLSelectItem)ref);
            }

            parsed.add(ref);
         }
      }
   }

   private void replaceBinaryOpExpr(SQLObject ref, SQLIdentifierExpr newExpr) {
      SQLBinaryOpExpr binaryOpExpr = (SQLBinaryOpExpr)ref.getParent();
      if (ref == binaryOpExpr.getLeft()) {
         binaryOpExpr.setLeft(newExpr);
      } else if (ref == binaryOpExpr.getRight()) {
         binaryOpExpr.setRight(newExpr);
      }

   }

   private void repalceMethodInvoke(SQLObject ref, SQLIdentifierExpr newExpr) {
      SQLMethodInvokeExpr methodInvokeExpr = (SQLMethodInvokeExpr)ref.getParent();

      for(int i = 0; i < methodInvokeExpr.getArguments().size(); ++i) {
         if (methodInvokeExpr.getArguments().get(i) == ref) {
            methodInvokeExpr.setArgument(i, newExpr);
         }
      }

   }

   private void replaceSelectItem(SQLSelectItem ref) {
      SQLExpr expr = ref.getExpr();
      if (expr instanceof SQLIdentifierExpr) {
         this.replaceIdentifier((SQLIdentifierExpr)expr);
      } else if (expr instanceof SQLPropertyExpr) {
         this.replaceProperty(ref, (SQLPropertyExpr)expr);
      }

   }

   private void replaceProperty(SQLSelectItem ref, SQLPropertyExpr expr) {
      String newColumnName = this.expression.replaceAll("\\$\\{value}", expr.toString());
      SQLIdentifierExpr newExpr = new SQLIdentifierExpr(newColumnName);
      ref.setExpr(newExpr);
   }

   private void replaceIdentifier(SQLIdentifierExpr expr) {
      String newColumnName = this.expression.replaceAll("\\$\\{value}", expr.getName());
      expr.setName(newColumnName);
   }

   private boolean isOutput(SQLObject ref) {
      if (ref != null && !(ref instanceof SQLSelectQueryBlock)) {
         return ref instanceof SQLSelectItem ? true : this.isOutput(ref.getParent());
      } else {
         return false;
      }
   }

   private boolean isHit(FieldInfo fieldInfo, boolean isCaseSensitive) {
      if (fieldInfo.getMemo() != null && fieldInfo.getMemo().table != null && fieldInfo.getMemo().ref != null) {
         FieldMemo fieldMemo = fieldInfo.getMemo();
         String columnName = this.normalize(fieldMemo.name);
         String tableName = this.normalize(fieldMemo.table.name);
         String schema = this.normalize(fieldMemo.table.schema);
         return JdbcUtil.followRules(columnName, this.replaceColumn) && JdbcUtil.followRules(tableName, this.replaceTable) && JdbcUtil.followRules(schema, this.replaceSchema);
      } else {
         return true;
      }
   }

   private String normalize(String str) {
      if (StringUtils.isEmpty(str)) {
         return str;
      } else {
         SQLServerScopeDialector dialector = new SQLServerScopeDialector();
         return dialector.wrap(str);
      }
   }
}
