package com.alibaba.druid.sql.dialect.hive.parser;

import com.alibaba.druid.DbType;
import com.alibaba.druid.sql.ast.SQLCurrentTimeExpr;
import com.alibaba.druid.sql.ast.SQLCurrentUserExpr;
import com.alibaba.druid.sql.ast.SQLExpr;
import com.alibaba.druid.sql.ast.expr.SQLArrayExpr;
import com.alibaba.druid.sql.ast.expr.SQLCharExpr;
import com.alibaba.druid.sql.ast.expr.SQLDecimalExpr;
import com.alibaba.druid.sql.ast.expr.SQLIdentifierExpr;
import com.alibaba.druid.sql.ast.expr.SQLIntervalExpr;
import com.alibaba.druid.sql.ast.expr.SQLIntervalUnit;
import com.alibaba.druid.sql.ast.expr.SQLNumericLiteralExpr;
import com.alibaba.druid.sql.ast.statement.SQLColumnDefinition;
import com.alibaba.druid.sql.dialect.hive.ast.HiveGrantPrinciple;
import com.alibaba.druid.sql.dialect.mysql.ast.expr.MySqlOutFileExpr;
import com.alibaba.druid.sql.parser.Lexer;
import com.alibaba.druid.sql.parser.ParserException;
import com.alibaba.druid.sql.parser.SQLExprParser;
import com.alibaba.druid.sql.parser.SQLParserFeature;
import com.alibaba.druid.sql.parser.Token;
import com.alibaba.druid.util.FnvHash;
import java.util.Arrays;

public class HiveExprParser extends SQLExprParser {
   private static final String[] AGGREGATE_FUNCTIONS;
   private static final long[] AGGREGATE_FUNCTIONS_CODES;

   public HiveExprParser(String sql) {
      this((Lexer)(new HiveLexer(sql)));
      this.lexer.nextToken();
   }

   public HiveExprParser(String sql, SQLParserFeature... features) {
      this((Lexer)(new HiveLexer(sql, features)));
      this.lexer.nextToken();
   }

   public HiveExprParser(Lexer lexer) {
      super(lexer);
      this.aggregateFunctions = AGGREGATE_FUNCTIONS;
      this.aggregateFunctionHashCodes = AGGREGATE_FUNCTIONS_CODES;
   }

   public SQLExpr primaryRest(SQLExpr expr) {
      switch (this.lexer.token()) {
         case LBRACKET:
            SQLArrayExpr array = new SQLArrayExpr();
            array.setExpr(expr);
            this.lexer.nextToken();
            this.exprList(array.getValues(), array);
            this.accept(Token.RBRACKET);
            return this.primaryRest(array);
         case LITERAL_CHARS:
            if (expr instanceof SQLCharExpr) {
               String text2 = ((SQLCharExpr)expr).getText();

               do {
                  String chars = this.lexer.stringVal();
                  text2 = text2 + chars;
                  this.lexer.nextToken();
               } while(this.lexer.token() == Token.LITERAL_CHARS || this.lexer.token() == Token.LITERAL_ALIAS);

               expr = new SQLCharExpr(text2);
            }
            break;
         case IDENTIFIER:
            if (this.lexer.identifierEquals(FnvHash.Constants.BD) && expr instanceof SQLNumericLiteralExpr) {
               this.lexer.nextToken();
               Number num = ((SQLNumericLiteralExpr)expr).getNumber();
               expr = new SQLDecimalExpr(num.toString());
            }
      }

      return super.primaryRest(expr);
   }

   public SQLExpr primary() {
      Token tok = this.lexer.token();
      switch (tok) {
         case IDENTIFIER:
            long hash_lower = this.lexer.hash_lower();
            if (hash_lower == FnvHash.Constants.OUTLINE) {
               this.lexer.nextToken();
               SQLExpr file = this.primary();
               SQLExpr expr = new MySqlOutFileExpr(file);
               return this.primaryRest(expr);
            } else {
               SQLCurrentTimeExpr currentTimeExpr = null;
               if (hash_lower == FnvHash.Constants.CURRENT_TIMESTAMP) {
                  currentTimeExpr = new SQLCurrentTimeExpr(SQLCurrentTimeExpr.Type.CURRENT_TIMESTAMP);
               } else if (hash_lower == FnvHash.Constants.CURRENT_DATE) {
                  currentTimeExpr = new SQLCurrentTimeExpr(SQLCurrentTimeExpr.Type.CURRENT_DATE);
               } else if (hash_lower == FnvHash.Constants.CURRENT_USER && this.isEnabled(SQLParserFeature.EnableCurrentUserExpr)) {
                  this.lexer.nextToken();
                  return this.primaryRest(new SQLCurrentUserExpr());
               }

               if (currentTimeExpr != null) {
                  String methodName = this.lexer.stringVal();
                  this.lexer.nextToken();
                  if (this.lexer.token() == Token.LPAREN) {
                     this.lexer.nextToken();
                     if (this.lexer.token() != Token.LPAREN) {
                        return this.primaryRest(this.methodRest(new SQLIdentifierExpr(methodName), false));
                     }

                     this.lexer.nextToken();
                  }

                  return this.primaryRest(currentTimeExpr);
               }
            }
         default:
            return super.primary();
      }
   }

   public DbType getDbType() {
      return DbType.hive;
   }

   protected SQLExpr parseAliasExpr(String alias) {
      String chars = alias.substring(1, alias.length() - 1);
      StringBuilder buf = null;

      for(int i = 0; i < chars.length(); ++i) {
         char ch = chars.charAt(i);
         if (ch == '\\' && i < chars.length() - 1) {
            char next = chars.charAt(i + 1);
            if (next == '\\') {
               if (buf == null) {
                  buf = new StringBuilder();
                  buf.append(chars.substring(0, i));
               }

               buf.append('\\');
               ++i;
            } else if (next == '"') {
               if (buf == null) {
                  buf = new StringBuilder();
                  buf.append(chars.substring(0, i));
               }

               buf.append('"');
               ++i;
            } else if (buf != null) {
               buf.append(ch);
            }
         } else if (buf != null) {
            buf.append(ch);
         }
      }

      if (buf != null) {
         chars = buf.toString();
      }

      return new SQLCharExpr(chars);
   }

   public SQLColumnDefinition parseColumnRest(SQLColumnDefinition column) {
      if (this.lexer.identifierEquals(FnvHash.Constants.MAPPED)) {
         this.lexer.nextToken();
         this.accept(Token.BY);
         this.parseAssignItem(column.getMappedBy(), column);
      }

      if (this.lexer.identifierEquals(FnvHash.Constants.COLPROPERTIES)) {
         this.lexer.nextToken();
         this.parseAssignItem(column.getColProperties(), column);
      }

      return super.parseColumnRest(column);
   }

   protected SQLExpr parseInterval() {
      this.accept(Token.INTERVAL);
      SQLExpr value = this.expr();
      if (this.lexer.token() != Token.IDENTIFIER) {
         throw new ParserException("Syntax error. " + this.lexer.info());
      } else {
         String unit = this.lexer.stringVal();
         this.lexer.nextToken();
         SQLIntervalExpr intervalExpr = new SQLIntervalExpr();
         intervalExpr.setValue(value);
         SQLIntervalUnit intervalUnit = SQLIntervalUnit.valueOf(unit.toUpperCase());
         if (intervalUnit == SQLIntervalUnit.YEAR && this.lexer.token() == Token.TO) {
            this.lexer.nextToken();
            this.acceptIdentifier("MONTH");
            intervalUnit = SQLIntervalUnit.YEAR_TO_MONTH;
         }

         intervalExpr.setUnit(intervalUnit);
         return intervalExpr;
      }
   }

   public SQLExpr parseUserPrinciple() {
      HiveGrantPrinciple owner = new HiveGrantPrinciple();
      if (this.lexer.token() != Token.USER && !this.lexer.identifierEquals("USER")) {
         if (this.lexer.identifierEquals("ROLE")) {
            owner.setRole(true);
            this.lexer.nextToken();
         } else {
            owner.setRole(true);
         }
      } else {
         owner.setUser(true);
         this.lexer.nextToken();
      }

      SQLExpr expr = this.expr();
      owner.setExpr(expr);
      return owner;
   }

   static {
      String[] strings = new String[]{"AVG", "COUNT", "MAX", "MIN", "STDDEV", "SUM", "ROW_NUMBER", "ROWNUMBER", "PARTITION"};
      AGGREGATE_FUNCTIONS_CODES = FnvHash.fnv1a_64_lower(strings, true);
      AGGREGATE_FUNCTIONS = new String[AGGREGATE_FUNCTIONS_CODES.length];

      for(String str : strings) {
         long hash = FnvHash.fnv1a_64_lower(str);
         int index = Arrays.binarySearch(AGGREGATE_FUNCTIONS_CODES, hash);
         AGGREGATE_FUNCTIONS[index] = str;
      }

   }
}
