package util.sqlparse.visitor.oracle.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.util.StringUtils;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import util.JdbcUtil;
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.oracle.OracleNameWrapper;

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.normalizeRegex((String)params.get("replaceSchema"));
      this.replaceTable = this.normalizeRegex((String)params.get("replaceTable"));
      this.replaceColumn = this.normalizeRegex((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();
      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 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 = OracleNameWrapper.normalize(fieldMemo.name);
         String tableName = OracleNameWrapper.normalize(fieldMemo.table.name);
         String schema = OracleNameWrapper.normalize(fieldMemo.table.schema);
         if (isCaseSensitive) {
            if (JdbcUtil.followRulesCaseSens(columnName, this.replaceColumn) && JdbcUtil.followRulesCaseSens(tableName, this.replaceTable) && JdbcUtil.followRulesCaseSens(schema, this.replaceSchema)) {
               return true;
            }
         } else if (JdbcUtil.followRules(columnName, this.replaceColumn) && JdbcUtil.followRules(tableName, this.replaceTable) && JdbcUtil.followRules(schema, this.replaceSchema)) {
            return true;
         }

         return false;
      } else {
         return true;
      }
   }

   private String normalizeRegex(String str) {
      if (StringUtils.isEmpty(str)) {
         return str;
      } else {
         return str.startsWith("\"") ? str.replaceAll("\"", "").trim() : str;
      }
   }
}
