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

import com.alibaba.druid.DbType;
import com.alibaba.druid.sql.parser.CharTypes;
import com.alibaba.druid.sql.parser.Keywords;
import com.alibaba.druid.sql.parser.Lexer;
import com.alibaba.druid.sql.parser.NotAllowCommentException;
import com.alibaba.druid.sql.parser.ParserException;
import com.alibaba.druid.sql.parser.SQLParserFeature;
import com.alibaba.druid.sql.parser.Token;
import com.alibaba.druid.util.FnvHash;
import java.util.HashMap;
import java.util.Map;

public class SparkLexer extends Lexer {
   public static final Keywords DEFAULT_ANTSPARK_KEYWORDS;

   public SparkLexer(String input) {
      super(input);
      this.dbType = DbType.spark;
      super.keywords = DEFAULT_ANTSPARK_KEYWORDS;
   }

   public final void nextToken() {
      this.startPos = this.pos;
      this.bufPos = 0;
      if (this.comments != null && this.comments.size() > 0) {
         this.comments = null;
      }

      this.lines = 0;
      int startLine = this.line;

      while(true) {
         while(CharTypes.isWhitespace(this.ch)) {
            if (this.ch == '\n') {
               ++this.line;
               this.lines = this.line - startLine;
            }

            this.ch = this.charAt(++this.pos);
            this.startPos = this.pos;
         }

         if (this.ch == '$' && isVaraintChar(this.charAt(this.pos + 1))) {
            this.scanVariable();
            return;
         }

         if (CharTypes.isFirstIdentifierChar(this.ch)) {
            if (this.ch == '（') {
               this.scanChar();
               this.token = Token.LPAREN;
               return;
            }

            if (this.ch == '）') {
               this.scanChar();
               this.token = Token.RPAREN;
               return;
            }

            if ((this.ch == 'N' || this.ch == 'n') && this.charAt(this.pos + 1) == '\'') {
               ++this.pos;
               this.ch = '\'';
               this.scanString();
               this.token = Token.LITERAL_NCHARS;
               return;
            }

            this.scanIdentifier();
            return;
         }

         switch (this.ch) {
            case '"':
               this.scanAlias();
               return;
            case '#':
               this.scanSharp();
               if (this.token != Token.LINE_COMMENT && this.token != Token.MULTI_LINE_COMMENT || !this.skipComment) {
                  return;
               }

               this.bufPos = 0;
               break;
            case '\'':
               this.scanString();
               return;
            case '(':
            case '（':
               this.scanChar();
               this.token = Token.LPAREN;
               return;
            case ')':
            case '）':
               this.scanChar();
               this.token = Token.RPAREN;
               return;
            case '*':
               this.scanChar();
               if (this.ch == '=' && DbType.sqlserver == this.dbType) {
                  this.scanChar();
                  this.token = Token.MultiplyAssignment;
               } else {
                  this.token = Token.STAR;
               }

               return;
            case ',':
            case '，':
               this.scanChar();
               this.token = Token.COMMA;
               return;
            case '-':
               char next = this.charAt(this.pos + 1);
               if (next == '-') {
                  this.scanComment();
                  if ((this.token == Token.LINE_COMMENT || this.token == Token.MULTI_LINE_COMMENT) && this.skipComment) {
                     this.bufPos = 0;
                     break;
                  }
               } else if (this.isMoney(next) && DbType.sqlserver == this.dbType) {
                  this.scanMoney();
               } else if (next >= '0' && next <= '9') {
                  if (this.token == null) {
                     this.scanNumber();
                     return;
                  }

                  switch (this.token) {
                     case COMMA:
                     case LPAREN:
                     case WITH:
                     case BY:
                        this.scanNumber();
                        break;
                     default:
                        this.scanOperator();
                  }
               } else {
                  this.scanOperator();
               }

               return;
            case '.':
               this.scanChar();
               if (!this.isDigit(this.ch) || this.pos != 1 && this.token == Token.IDENTIFIER) {
                  if (this.ch == '.') {
                     this.scanChar();
                     if (this.ch == '.') {
                        this.scanChar();
                        this.token = Token.DOTDOTDOT;
                     } else {
                        this.token = Token.DOTDOT;
                     }
                  } else {
                     this.token = Token.DOT;
                  }

                  return;
               }

               this.unscan();
               this.scanNumber();
               return;
            case '/':
               char nextChar = this.charAt(this.pos + 1);
               if (nextChar == '/' || nextChar == '*' || nextChar == '!' && this.isEnabled(SQLParserFeature.TDDLHint)) {
                  this.scanComment();
                  if ((this.token == Token.LINE_COMMENT || this.token == Token.MULTI_LINE_COMMENT) && this.skipComment) {
                     this.bufPos = 0;
                     break;
                  }
               } else if (nextChar == ' ' && this.charAt(this.pos + 2) == '*') {
                  this.scanComment();
                  if ((this.token == Token.LINE_COMMENT || this.token == Token.MULTI_LINE_COMMENT) && this.skipComment) {
                     this.bufPos = 0;
                     break;
                  }
               } else {
                  this.scanChar();
                  if (this.ch == '=' && DbType.sqlserver == this.dbType) {
                     this.scanChar();
                     this.token = Token.DivideAssignment;
                  } else {
                     this.token = Token.SLASH;
                  }
               }

               return;
            case '0':
               if (this.charAt(this.pos + 1) == 'x') {
                  this.scanChar();
                  this.scanChar();
                  this.scanHexaDecimal();
               } else {
                  this.scanNumber();
               }

               return;
            case '1':
            case '2':
            case '3':
            case '4':
            case '5':
            case '6':
            case '7':
            case '8':
            case '9':
               this.scanNumber();
               return;
            case ':':
               this.scanChar();
               if (this.ch == '=') {
                  this.scanChar();
                  this.token = Token.COLONEQ;
               } else if (this.ch == ':') {
                  this.scanChar();
                  this.token = Token.COLONCOLON;
               } else {
                  if (this.isEnabled(SQLParserFeature.TDDLHint) || this.dbType == DbType.hive || this.dbType == DbType.spark || this.dbType == DbType.odps) {
                     this.token = Token.COLON;
                     return;
                  }

                  this.unscan();
                  this.scanVariable();
               }

               return;
            case ';':
               this.scanChar();
               this.token = Token.SEMI;
               return;
            case '?':
               this.scanChar();
               if (this.ch == '?' && DbType.postgresql == this.dbType) {
                  this.scanChar();
                  if (this.ch == '|') {
                     this.scanChar();
                     this.token = Token.QUESBAR;
                  } else {
                     this.token = Token.QUESQUES;
                  }
               } else if (this.ch == '|' && DbType.postgresql == this.dbType) {
                  this.scanChar();
                  if (this.ch == '|') {
                     this.unscan();
                     this.token = Token.QUES;
                  } else {
                     this.token = Token.QUESBAR;
                  }
               } else if (this.ch == '&' && DbType.postgresql == this.dbType) {
                  this.scanChar();
                  this.token = Token.QUESAMP;
               } else {
                  this.token = Token.QUES;
               }

               return;
            case '@':
               this.scanVariable_at();
               return;
            case '[':
               this.scanLBracket();
               return;
            case ']':
               this.scanChar();
               this.token = Token.RBRACKET;
               return;
            case '`':
               throw new ParserException("TODO. " + this.info());
            case '{':
               this.scanChar();
               this.token = Token.LBRACE;
               return;
            case '}':
               this.scanChar();
               this.token = Token.RBRACE;
               return;
            default:
               if (Character.isLetter(this.ch)) {
                  this.scanIdentifier();
                  return;
               }

               if (this.isOperator(this.ch)) {
                  this.scanOperator();
                  return;
               }

               if (this.ch == '\\' && this.charAt(this.pos + 1) == 'N' && DbType.mysql == this.dbType) {
                  this.scanChar();
                  this.scanChar();
                  this.token = Token.NULL;
                  return;
               }

               if (this.isEOF()) {
                  this.token = Token.EOF;
               } else {
                  this.lexError("illegal.char", new Object[]{String.valueOf(this.ch)});
                  this.scanChar();
               }

               return;
         }
      }
   }

   public void scanNumber() {
      this.mark = this.pos;
      this.numberSale = 0;
      this.numberExp = false;
      this.bufPos = 0;
      if (this.ch == '-') {
         ++this.bufPos;
         this.ch = this.charAt(++this.pos);
      }

      while(this.ch >= '0' && this.ch <= '9') {
         ++this.bufPos;
         this.ch = this.charAt(++this.pos);
      }

      if (this.ch == '.') {
         if (this.charAt(this.pos + 1) == '.') {
            this.token = Token.LITERAL_INT;
            return;
         }

         ++this.bufPos;
         this.ch = this.charAt(++this.pos);

         for(this.numberSale = 0; this.ch >= '0' && this.ch <= '9'; ++this.numberSale) {
            ++this.bufPos;
            this.ch = this.charAt(++this.pos);
         }

         this.numberExp = true;
      }

      if ((this.ch == 'e' || this.ch == 'E') && (this.isDigit(this.charAt(this.pos + 1)) || isDigit2(this.charAt(this.pos + 1)) && isDigit2(this.charAt(this.pos + 2)))) {
         this.numberExp = true;
         ++this.bufPos;
         this.ch = this.charAt(++this.pos);
         if (this.ch == '+' || this.ch == '-') {
            ++this.bufPos;
            this.ch = this.charAt(++this.pos);
         }

         while(this.ch >= '0' && this.ch <= '9') {
            ++this.bufPos;
            this.ch = this.charAt(++this.pos);
         }

         if (this.ch >= 'a' && this.ch <= 'z' || this.ch >= 'A' && this.ch <= 'Z') {
            this.numberExp = false;
         }
      }

      if (this.numberSale <= 0 && !this.numberExp) {
         if (CharTypes.isFirstIdentifierChar(this.ch) && this.ch != '）' && (this.ch != 'b' || this.bufPos != 1 || this.charAt(this.pos - 1) != '0' || this.dbType == DbType.odps)) {
            ++this.bufPos;

            while(true) {
               char c0 = this.ch;
               this.ch = this.charAt(++this.pos);
               if (!CharTypes.isIdentifierChar(this.ch)) {
                  if (this.ch != '（' && (this.ch != '）' || c0 <= 256)) {
                     this.stringVal = this.addSymbol();
                     this.hash_lower = FnvHash.hashCode64(this.stringVal);
                     this.token = Token.IDENTIFIER;
                     break;
                  }

                  ++this.bufPos;
               } else {
                  ++this.bufPos;
               }
            }
         } else {
            this.token = Token.LITERAL_INT;
         }
      } else {
         this.token = Token.LITERAL_FLOAT;
      }

      if (this.ch != 'D' && this.ch != 'd') {
         if ((this.ch == 'B' || this.ch == 'b') && (this.charAt(this.pos + 1) == 'D' || this.charAt(this.pos + 1) == 'd')) {
            this.bufPos += 2;
            this.pos += 2;
            this.scanChar();
            this.token = Token.LITERAL_DBNUMBER;
         }
      } else {
         ++this.bufPos;
         ++this.pos;
         this.scanChar();
         this.token = Token.LITERAL_DBNUMBER;
      }

   }

   public void scanComment() {
      Token lastToken = this.token;
      if (this.ch == '-') {
         char before_1 = this.pos == 0 ? 32 : this.charAt(this.pos - 1);
         char next_2 = this.charAt(this.pos + 2);
         if (this.isDigit(next_2)) {
            this.scanChar();
            this.token = Token.SUB;
            return;
         }

         if (before_1 != ' ' && (before_1 == '-' || before_1 == '+') || next_2 != ' ' && next_2 != 26 && next_2 != '\n') {
            if ((before_1 == '-' || before_1 == '+') && next_2 == ' ') {
               throw new ParserException("illegal state. " + this.info());
            }

            if (this.ch == '-') {
               this.scanChar();
               this.token = Token.SUB;
               return;
            }

            if (this.ch == '+') {
               this.scanChar();
               this.token = Token.PLUS;
               return;
            }
         }
      } else if (this.ch != '/') {
         throw new ParserException("illegal state. " + this.info());
      }

      this.mark = this.pos;
      this.bufPos = 0;
      this.scanChar();
      if (this.ch == '*') {
         this.scanChar();
         ++this.bufPos;

         while(this.ch == ' ') {
            this.scanChar();
            ++this.bufPos;
         }

         boolean isHint = false;
         int startHintSp = this.bufPos + 1;
         if (this.ch == '!' || this.ch == '+' || this.ch == 'T' && this.charAt(this.pos + 1) == 'D' && this.charAt(this.pos + 2) == 'D' && this.charAt(this.pos + 3) == 'L' && this.isEnabled(SQLParserFeature.TDDLHint)) {
            isHint = true;
            this.scanChar();
            ++this.bufPos;
         }

         int starIndex = this.pos;
         int depth = 1;

         while(true) {
            char ch;
            while(true) {
               ch = this.charAt(starIndex);
               if (ch == '/' && this.charAt(starIndex + 1) == '*') {
                  starIndex += 2;
                  ch = this.charAt(starIndex);
                  if (ch == '!' || ch == '+') {
                     ++depth;
                     ++starIndex;
                     continue;
                  }
                  break;
               } else {
                  if (ch != '*' || this.charAt(starIndex + 1) != '/') {
                     break;
                  }

                  --depth;
                  if (0 == depth) {
                     if (isHint) {
                        this.stringVal = this.subString(this.mark + startHintSp, starIndex - startHintSp - this.mark);
                        this.token = Token.HINT;
                     } else {
                        if (!this.optimizedForParameterized) {
                           this.stringVal = this.subString(this.mark, starIndex + 2 - this.mark);
                        }

                        this.token = Token.MULTI_LINE_COMMENT;
                        ++this.commentCount;
                        if (this.keepComments) {
                           this.stringVal = this.subString(this.mark, starIndex + 2 - this.mark);
                           this.addComment(this.stringVal);
                        }
                     }

                     this.pos = starIndex + 2;
                     this.ch = this.charAt(this.pos);
                     this.endOfComment = this.isEOF();
                     if (this.commentHandler != null && this.commentHandler.handle(lastToken, this.stringVal)) {
                        return;
                     }

                     if (!isHint && !this.isAllowComment() && !this.isSafeComment(this.stringVal)) {
                        throw new NotAllowCommentException();
                     }

                     return;
                  }

                  starIndex += 2;
               }
            }

            if (ch == 26) {
               this.token = Token.ERROR;
               return;
            }

            ++starIndex;
         }
      } else if (this.ch == '!' && this.isEnabled(SQLParserFeature.TDDLHint)) {
         this.scanChar();
         ++this.bufPos;

         while(this.ch == ' ') {
            this.scanChar();
            ++this.bufPos;
         }

         int startHintSp = this.bufPos + 1;
         int starIndex = this.pos;

         while(true) {
            starIndex = this.text.indexOf(42, starIndex);
            if (starIndex == -1 || starIndex == this.text.length() - 1) {
               this.token = Token.ERROR;
               return;
            }

            if (this.charAt(starIndex + 1) == '/') {
               this.stringVal = this.subString(this.mark + startHintSp, starIndex - startHintSp - this.mark);
               this.token = Token.HINT;
               this.pos = starIndex + 2;
               this.ch = this.charAt(this.pos);
               this.endOfComment = this.isEOF();
               if (this.commentHandler != null && this.commentHandler.handle(lastToken, this.stringVal)) {
                  return;
               }

               if (!this.isAllowComment() && !this.isSafeComment(this.stringVal)) {
                  throw new NotAllowCommentException();
               }

               return;
            }

            ++starIndex;
         }
      } else if (this.ch == '/' || this.ch == '-') {
         this.scanChar();
         ++this.bufPos;

         while(true) {
            if (this.ch == '\r') {
               if (this.charAt(this.pos + 1) == '\n') {
                  this.bufPos += 2;
                  this.scanChar();
               } else {
                  ++this.bufPos;
               }
               break;
            }

            if (this.ch == 26) {
               break;
            }

            if (this.ch == '\n') {
               this.scanChar();
               ++this.bufPos;
               break;
            }

            this.scanChar();
            ++this.bufPos;
         }

         this.stringVal = this.subString(this.mark, this.bufPos);
         this.token = Token.LINE_COMMENT;
         ++this.commentCount;
         if (this.keepComments) {
            this.addComment(this.stringVal);
         }

         if (this.commentHandler == null || !this.commentHandler.handle(lastToken, this.stringVal)) {
            this.endOfComment = this.isEOF();
            if (!this.isAllowComment() && (this.isEOF() || !this.isSafeComment(this.stringVal))) {
               throw new NotAllowCommentException();
            }
         }
      }
   }

   static {
      Map<String, Token> map = new HashMap();
      map.putAll(Keywords.DEFAULT_KEYWORDS.getKeywords());
      map.put("OF", Token.OF);
      map.put("CONCAT", Token.CONCAT);
      map.put("CONTINUE", Token.CONTINUE);
      map.put("MERGE", Token.MERGE);
      map.put("MATCHED", Token.MATCHED);
      map.put("USING", Token.USING);
      map.put("ROW", Token.ROW);
      map.put("LIMIT", Token.LIMIT);
      map.put("PARTITIONED", Token.PARTITIONED);
      map.put("PARTITION", Token.PARTITION);
      map.put("OVERWRITE", Token.OVERWRITE);
      map.put("IF", Token.IF);
      map.put("TRUE", Token.TRUE);
      map.put("FALSE", Token.FALSE);
      map.put("RLIKE", Token.RLIKE);
      map.put("CONSTRAINT", Token.CONSTRAINT);
      map.put("ANY", Token.ANY);
      map.put("SOME", Token.SOME);
      map.put("ALL", Token.ALL);
      map.put("CROSS", Token.CROSS);
      map.put("NATURAL", Token.NATURAL);
      map.put("EXCEPT", Token.EXCEPT);
      map.put("MINUS", Token.MINUS);
      map.put("LATERAL", Token.LATERAL);
      map.put("VIEW", Token.VIEW);
      map.put("DATE", Token.DATE);
      DEFAULT_ANTSPARK_KEYWORDS = new Keywords(map);
   }
}
