package com.chenyang.druid.sql.ast.expr;

import com.chenyang.druid.FastsqlException;
import com.chenyang.druid.sql.SQLUtils;
import com.chenyang.druid.sql.ast.SQLDataType;
import com.chenyang.druid.sql.ast.SQLDataTypeImpl;
import com.chenyang.druid.sql.ast.SQLExpr;
import com.chenyang.druid.sql.ast.SQLExprImpl;
import com.chenyang.druid.sql.ast.SQLObject;
import com.chenyang.druid.sql.ast.SQLReplaceable;
import com.chenyang.druid.sql.dialect.oracle.visitor.OracleASTVisitor;
import com.chenyang.druid.sql.visitor.SQLASTVisitor;
import com.chenyang.druid.util.FnvHash;
import java.io.IOException;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

public class SQLMethodInvokeExpr extends SQLExprImpl implements SQLReplaceable, Serializable {
   private static final long serialVersionUID = 1L;
   protected final List<SQLExpr> arguments = new ArrayList();
   protected String methodName;
   protected long methodNameHashCode64;
   protected SQLExpr owner;
   protected SQLExpr from;
   protected SQLExpr using;
   protected SQLExpr _for;
   protected String trimOption;
   protected transient SQLDataType resolvedReturnDataType;

   public SQLMethodInvokeExpr() {
   }

   public SQLMethodInvokeExpr(String methodName) {
      this.methodName = methodName;
   }

   public SQLMethodInvokeExpr(String methodName, long methodNameHashCode64) {
      this.methodName = methodName;
      this.methodNameHashCode64 = methodNameHashCode64;
   }

   public SQLMethodInvokeExpr(String methodName, SQLExpr owner) {
      this.methodName = methodName;
      this.setOwner(owner);
   }

   public SQLMethodInvokeExpr(String methodName, SQLExpr owner, SQLExpr... params) {
      this.methodName = methodName;
      this.setOwner(owner);

      for(SQLExpr param : params) {
         this.addArgument(param);
      }

   }

   public SQLMethodInvokeExpr(String methodName, SQLExpr owner, List<SQLExpr> params) {
      this.methodName = methodName;
      this.setOwner(owner);

      for(SQLExpr param : params) {
         this.addArgument(param);
      }

   }

   public long methodNameHashCode64() {
      if (this.methodNameHashCode64 == 0L && this.methodName != null) {
         this.methodNameHashCode64 = FnvHash.hashCode64(this.methodName);
      }

      return this.methodNameHashCode64;
   }

   public String getMethodName() {
      return this.methodName;
   }

   public void setMethodName(String methodName) {
      this.methodName = methodName;
      this.methodNameHashCode64 = 0L;
   }

   /** @deprecated */
   public List<SQLExpr> getParameters() {
      return this.arguments;
   }

   public List<SQLExpr> getArguments() {
      return this.arguments;
   }

   public void setArgument(int i, SQLExpr arg) {
      if (arg != null) {
         arg.setParent(this);
      }

      this.arguments.set(i, arg);
   }

   /** @deprecated */
   public void addParameter(SQLExpr param) {
      if (param != null) {
         param.setParent(this);
      }

      this.arguments.add(param);
   }

   public void addArgument(SQLExpr arg) {
      if (arg != null) {
         arg.setParent(this);
      }

      this.arguments.add(arg);
   }

   public SQLExpr getOwner() {
      return this.owner;
   }

   public void setOwner(SQLExpr owner) {
      if (owner != null) {
         owner.setParent(this);
      }

      this.owner = owner;
   }

   public SQLExpr getFrom() {
      return this.from;
   }

   public void setFrom(SQLExpr x) {
      if (x != null) {
         x.setParent(this);
      }

      this.from = x;
   }

   public void output(Appendable buf) {
      try {
         if (this.owner != null) {
            this.owner.output(buf);
            buf.append(".");
         }

         buf.append(this.methodName);
         buf.append("(");
         int i = 0;

         for(int size = this.arguments.size(); i < size; ++i) {
            if (i != 0) {
               buf.append(", ");
            }

            ((SQLExpr)this.arguments.get(i)).output(buf);
         }

         buf.append(")");
      } catch (IOException ex) {
         throw new FastsqlException("output error", ex);
      }
   }

   protected void accept0(SQLASTVisitor visitor) {
      if (visitor.visit(this)) {
         if (this.owner != null) {
            this.owner.accept(visitor);
         }

         for(SQLExpr arg : this.arguments) {
            if (arg != null) {
               arg.accept(visitor);
            }
         }

         if (this.from != null) {
            this.from.accept(visitor);
         }

         if (this.using != null) {
            this.using.accept(visitor);
         }

         if (this._for != null) {
            this._for.accept(visitor);
         }
      }

      visitor.endVisit(this);
   }

   public List getChildren() {
      if (this.owner == null) {
         return this.arguments;
      } else {
         List<SQLObject> children = new ArrayList();
         children.add(this.owner);
         children.addAll(this.arguments);
         return children;
      }
   }

   protected void accept0(OracleASTVisitor visitor) {
      if (visitor.visit((SQLMethodInvokeExpr)this)) {
         if (this.owner != null) {
            this.owner.accept(visitor);
         }

         for(SQLExpr arg : this.arguments) {
            if (arg != null) {
               arg.accept(visitor);
            }
         }

         if (this.from != null) {
            this.from.accept(visitor);
         }

         if (this.using != null) {
            this.using.accept(visitor);
         }

         if (this._for != null) {
            this._for.accept(visitor);
         }
      }

      visitor.endVisit((SQLMethodInvokeExpr)this);
   }

   public boolean equals(Object o) {
      if (this == o) {
         return true;
      } else if (o != null && this.getClass() == o.getClass()) {
         SQLMethodInvokeExpr that = (SQLMethodInvokeExpr)o;
         if (this.methodNameHashCode64() != that.methodNameHashCode64()) {
            return false;
         } else {
            if (this.owner != null) {
               if (!this.owner.equals(that.owner)) {
                  return false;
               }
            } else if (that.owner != null) {
               return false;
            }

            if (!this.arguments.equals(that.arguments)) {
               return false;
            } else {
               return this.from != null ? this.from.equals(that.from) : that.from == null;
            }
         }
      } else {
         return false;
      }
   }

   public int hashCode() {
      int result = (int)(this.methodNameHashCode64() ^ this.methodNameHashCode64() >>> 32);
      result = 31 * result + (this.owner != null ? this.owner.hashCode() : 0);
      result = 31 * result + this.arguments.hashCode();
      result = 31 * result + (this.from != null ? this.from.hashCode() : 0);
      return result;
   }

   public SQLMethodInvokeExpr clone() {
      SQLMethodInvokeExpr x = new SQLMethodInvokeExpr();
      this.cloneTo(x);
      return x;
   }

   public void cloneTo(SQLMethodInvokeExpr x) {
      x.methodName = this.methodName;
      if (this.owner != null) {
         x.setOwner(this.owner.clone());
      }

      for(SQLExpr arg : this.arguments) {
         x.addArgument(arg.clone());
      }

      if (this.from != null) {
         x.setFrom(this.from.clone());
      }

      if (this.using != null) {
         x.setUsing(this.using.clone());
      }

      if (this.trimOption != null) {
         x.setTrimOption(this.trimOption);
      }

      if (this.attributes != null) {
         for(Map.Entry<String, Object> entry : this.attributes.entrySet()) {
            String key = (String)entry.getKey();
            Object value = entry.getValue();
            if (value instanceof SQLObject) {
               value = ((SQLObject)value).clone();
            }

            x.putAttribute(key, value);
         }
      }

   }

   public boolean replace(SQLExpr expr, SQLExpr target) {
      if (target == null) {
         return false;
      } else {
         for(int i = 0; i < this.arguments.size(); ++i) {
            if (this.arguments.get(i) == expr) {
               this.arguments.set(i, target);
               target.setParent(this);
               return true;
            }
         }

         if (this.from == expr) {
            this.setFrom(target);
            return true;
         } else if (this.using == expr) {
            this.setUsing(target);
            return true;
         } else if (this._for == expr) {
            this.setFor(target);
            return true;
         } else {
            return false;
         }
      }
   }

   public boolean match(String owner, String function) {
      if (function == null) {
         return false;
      } else if (!SQLUtils.nameEquals(function, this.methodName)) {
         return false;
      } else if (owner == null && this.owner == null) {
         return true;
      } else if (owner != null && this.owner != null) {
         return this.owner instanceof SQLIdentifierExpr ? SQLUtils.nameEquals(((SQLIdentifierExpr)this.owner).name, owner) : false;
      } else {
         return false;
      }
   }

   public SQLDataType computeDataType() {
      if (this.resolvedReturnDataType != null) {
         return this.resolvedReturnDataType;
      } else {
         long nameHash = this.methodNameHashCode64();
         if (nameHash != FnvHash.Constants.TO_DATE && nameHash != FnvHash.Constants.ADD_MONTHS) {
            if (nameHash == FnvHash.Constants.DATE_PARSE) {
               return this.resolvedReturnDataType = SQLTimestampExpr.DATA_TYPE;
            } else if (nameHash != FnvHash.Constants.CURRENT_TIME && nameHash != FnvHash.Constants.CURTIME) {
               if (nameHash != FnvHash.Constants.BIT_COUNT && nameHash != FnvHash.Constants.ROW_NUMBER) {
                  if (this.arguments.size() == 1) {
                     if (nameHash == FnvHash.Constants.TRUNC) {
                        return this.resolvedReturnDataType = ((SQLExpr)this.arguments.get(0)).computeDataType();
                     }
                  } else if (this.arguments.size() == 2) {
                     SQLExpr param0 = (SQLExpr)this.arguments.get(0);
                     SQLExpr param1 = (SQLExpr)this.arguments.get(1);
                     if (nameHash == FnvHash.Constants.ROUND) {
                        SQLDataType dataType = param0.computeDataType();
                        if (dataType != null) {
                           return dataType;
                        }
                     } else if (nameHash == FnvHash.Constants.NVL || nameHash == FnvHash.Constants.IFNULL || nameHash == FnvHash.Constants.ISNULL || nameHash == FnvHash.Constants.COALESCE) {
                        SQLDataType dataType = param0.computeDataType();
                        return dataType != null ? dataType : param1.computeDataType();
                     }

                     if (nameHash == FnvHash.Constants.MOD) {
                        return this.resolvedReturnDataType = SQLIntegerExpr.DATA_TYPE;
                     }
                  }

                  if (nameHash == FnvHash.Constants.STDDEV_SAMP) {
                     return this.resolvedReturnDataType = SQLNumberExpr.DATA_TYPE_DOUBLE;
                  } else if (nameHash != FnvHash.Constants.CONCAT && nameHash != FnvHash.Constants.SUBSTR && nameHash != FnvHash.Constants.SUBSTRING) {
                     if (nameHash != FnvHash.Constants.YEAR && nameHash != FnvHash.Constants.MONTH && nameHash != FnvHash.Constants.DAY && nameHash != FnvHash.Constants.HOUR && nameHash != FnvHash.Constants.MINUTE && nameHash != FnvHash.Constants.SECOND && nameHash != FnvHash.Constants.PERIOD_ADD && nameHash != FnvHash.Constants.PERIOD_DIFF) {
                        if (nameHash == FnvHash.Constants.GROUPING) {
                           return this.resolvedReturnDataType = new SQLDataTypeImpl("INT");
                        } else if (nameHash != FnvHash.Constants.JSON_EXTRACT_SCALAR && nameHash != FnvHash.Constants.FORMAT_DATETIME && nameHash != FnvHash.Constants.DATE_FORMAT) {
                           if (nameHash != FnvHash.Constants.DATE_ADD && nameHash != FnvHash.Constants.DATE_SUB && nameHash != FnvHash.Constants.DATE && nameHash != FnvHash.Constants.STR_TO_DATE && nameHash != FnvHash.Constants.CURRENT_DATE) {
                              if (nameHash == FnvHash.Constants.UNIX_TIMESTAMP) {
                                 return this.resolvedReturnDataType = SQLIntegerExpr.DATA_TYPE;
                              } else if (nameHash == FnvHash.Constants.TIME) {
                                 return this.resolvedReturnDataType = new SQLDataTypeImpl("VARCHAR");
                              } else if (nameHash != FnvHash.Constants.SYSDATE && nameHash != FnvHash.Constants.CURRENT_TIMESTAMP && nameHash != FnvHash.Constants.SYSTIMESTAMP) {
                                 return null;
                              } else {
                                 return this.resolvedReturnDataType = SQLTimestampExpr.DATA_TYPE;
                              }
                           } else {
                              return this.resolvedReturnDataType = SQLDateExpr.DATA_TYPE;
                           }
                        } else {
                           return this.resolvedReturnDataType = SQLCharExpr.DATA_TYPE;
                        }
                     } else {
                        return this.resolvedReturnDataType = new SQLDataTypeImpl("INT");
                     }
                  } else {
                     return this.resolvedReturnDataType = SQLCharExpr.DATA_TYPE;
                  }
               } else {
                  return this.resolvedReturnDataType = new SQLDataTypeImpl("BIGINT");
               }
            } else {
               return this.resolvedReturnDataType = SQLTimeExpr.DATA_TYPE;
            }
         } else {
            return this.resolvedReturnDataType = SQLDateExpr.DATA_TYPE;
         }
      }
   }

   public SQLExpr getUsing() {
      return this.using;
   }

   public void setUsing(SQLExpr x) {
      if (x != null) {
         x.setParent(this);
      }

      this.using = x;
   }

   public SQLExpr getFor() {
      return this._for;
   }

   public void setFor(SQLExpr x) {
      if (x != null) {
         x.setParent(this);
      }

      this._for = x;
   }

   public String getTrimOption() {
      return this.trimOption;
   }

   public void setTrimOption(String trimOption) {
      this.trimOption = trimOption;
   }

   public SQLDataType getResolvedReturnDataType() {
      return this.resolvedReturnDataType;
   }

   public void setResolvedReturnDataType(SQLDataType resolvedReturnDataType) {
      this.resolvedReturnDataType = resolvedReturnDataType;
   }
}
