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

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.SQLIdentifierExpr;
import com.alibaba.druid.sql.ast.statement.SQLColumnDefinition;
import com.alibaba.druid.sql.ast.statement.SQLExternalRecordFormat;
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 AntsparkExprParser extends SQLExprParser {
   private static final String[] AGGREGATE_FUNCTIONS;
   private static final long[] AGGREGATE_FUNCTIONS_CODES;

   public AntsparkExprParser(String sql) {
      this((Lexer)(new AntsparkLexer(sql)));
      this.lexer.nextToken();
   }

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

   public SQLExpr primaryRest(SQLExpr expr) {
      if (this.lexer.token() == Token.LBRACKET) {
         SQLArrayExpr array = new SQLArrayExpr();
         array.setExpr(expr);
         this.lexer.nextToken();
         this.exprList(array.getValues(), array);
         this.accept(Token.RBRACKET);
         return this.primaryRest(array);
      } else {
         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 SQLExternalRecordFormat parseRowFormat() {
      this.lexer.nextToken();
      this.acceptIdentifier("FORMAT");
      if (this.lexer.identifierEquals(FnvHash.Constants.DELIMITED)) {
         this.lexer.nextToken();
      }

      SQLExternalRecordFormat format = new SQLExternalRecordFormat();
      if (this.lexer.identifierEquals(FnvHash.Constants.FIELDS)) {
         this.lexer.nextToken();
         this.acceptIdentifier("TERMINATED");
         this.accept(Token.BY);
         format.setTerminatedBy(this.expr());
      } else if (this.lexer.identifierEquals("FIELD")) {
         throw new ParserException("syntax error, expect FIELDS, " + this.lexer.info());
      }

      if (this.lexer.identifierEquals(FnvHash.Constants.LINES)) {
         this.lexer.nextToken();
         this.acceptIdentifier("TERMINATED");
         this.accept(Token.BY);
         format.setLinesTerminatedBy(this.expr());
      }

      if (this.lexer.token() == Token.ESCAPE || this.lexer.identifierEquals(FnvHash.Constants.ESCAPED)) {
         this.lexer.nextToken();
         this.accept(Token.BY);
         format.setEscapedBy(this.expr());
      }

      if (this.lexer.identifierEquals(FnvHash.Constants.COLLECTION)) {
         this.lexer.nextToken();
         this.acceptIdentifier("ITEMS");
         this.acceptIdentifier("TERMINATED");
         this.accept(Token.BY);
         format.setCollectionItemsTerminatedBy(this.expr());
      }

      if (this.lexer.identifierEquals(FnvHash.Constants.MAP)) {
         this.lexer.nextToken();
         this.acceptIdentifier("KEYS");
         this.acceptIdentifier("TERMINATED");
         this.accept(Token.BY);
         format.setMapKeysTerminatedBy(this.expr());
      }

      if (this.lexer.identifierEquals(FnvHash.Constants.SERDE)) {
         this.lexer.nextToken();
         format.setSerde(this.expr());
      }

      return format;
   }

   protected SQLExpr parseAliasExpr(String alias) {
      String chars = alias.substring(1, alias.length() - 1);
      return new SQLCharExpr(chars);
   }

   protected SQLExpr parseDatasource(String alias) {
      String chars = alias.substring(1, alias.length() - 1);
      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);
   }

   static {
      String[] strings = new String[]{"AVG", "COUNT", "MAX", "MIN", "STDDEV", "SUM", "ROW_NUMBER", "ROWNUMBER"};
      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;
      }

   }
}
