package com.chenyang.druid.sql.dialect.mysql.parser;

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

public class MySqlLexer extends Lexer {
   public static SymbolTable quoteTable = new SymbolTable(8192);
   public static final Keywords DEFAULT_MYSQL_KEYWORDS;
   private static final boolean[] identifierFlags;

   public MySqlLexer(char[] input, int inputLength, boolean skipComment) {
      super(input, inputLength, skipComment);
      this.dbType = DbType.mysql;
      super.keywords = DEFAULT_MYSQL_KEYWORDS;
   }

   public MySqlLexer(String input) {
      this(input, true, true);
   }

   public MySqlLexer(String input, SQLParserFeature... features) {
      super(input, true);
      this.dbType = DbType.mysql;
      this.keepComments = true;
      super.keywords = DEFAULT_MYSQL_KEYWORDS;

      for(SQLParserFeature feature : features) {
         this.config(feature, true);
      }

   }

   public MySqlLexer(String input, boolean skipComment, boolean keepComments) {
      super(input, skipComment);
      this.dbType = DbType.mysql;
      this.skipComment = skipComment;
      this.keepComments = keepComments;
      super.keywords = DEFAULT_MYSQL_KEYWORDS;
   }

   public void scanSharp() {
      if (this.ch != '#') {
         throw new ParserException("illegal stat. " + this.info());
      } else if (this.charAt(this.pos + 1) == '{') {
         this.scanVariable();
      } else {
         Token lastToken = this.token;
         this.scanChar();
         this.mark = this.pos;
         this.bufPos = 0;

         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 - 1, this.bufPos + 1);
         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();
            }
         }
      }
   }

   public void scanVariable() {
      if (this.ch != ':' && this.ch != '#' && this.ch != '$') {
         throw new ParserException("illegal variable. " + this.info());
      } else {
         this.mark = this.pos;
         this.bufPos = 1;
         if (this.charAt(this.pos + 1) == '`') {
            ++this.pos;
            ++this.bufPos;

            while(true) {
               char ch = this.charAt(++this.pos);
               if (ch == '`') {
                  ++this.bufPos;
                  this.charAt(++this.pos);
                  this.ch = this.charAt(this.pos);
                  this.stringVal = this.subString(this.mark, this.bufPos);
                  this.token = Token.VARIANT;
                  break;
               }

               if (ch == 26) {
                  throw new ParserException("illegal identifier. " + this.info());
               }

               ++this.bufPos;
            }
         } else if (this.charAt(this.pos + 1) == '{') {
            ++this.pos;
            ++this.bufPos;

            while(true) {
               char ch = this.charAt(++this.pos);
               if (ch == '}') {
                  ++this.bufPos;
                  this.charAt(++this.pos);
                  this.ch = this.charAt(this.pos);
                  this.stringVal = this.subString(this.mark, this.bufPos);
                  this.token = Token.VARIANT;
                  break;
               }

               if (ch == 26) {
                  throw new ParserException("illegal identifier. " + this.info());
               }

               ++this.bufPos;
            }
         } else {
            while(true) {
               this.ch = this.charAt(++this.pos);
               if (!isIdentifierChar(this.ch)) {
                  break;
               }

               ++this.bufPos;
            }
         }

         this.ch = this.charAt(this.pos);
         this.stringVal = this.subString(this.mark, this.bufPos);
         this.token = Token.VARIANT;
      }
   }

   protected void scanVariable_at() {
      if (this.ch != '@') {
         throw new ParserException("illegal variable. " + this.info());
      } else {
         this.mark = this.pos;
         this.bufPos = 1;
         if (this.charAt(this.pos + 1) == '@') {
            this.ch = this.charAt(++this.pos);
            ++this.bufPos;
         }

         if (this.charAt(this.pos + 1) == '`') {
            ++this.pos;
            ++this.bufPos;

            while(true) {
               char ch = this.charAt(++this.pos);
               if (ch == '`') {
                  ++this.bufPos;
                  ++this.pos;
                  this.ch = this.charAt(this.pos);
                  this.stringVal = this.subString(this.mark, this.bufPos);
                  this.token = Token.VARIANT;
                  break;
               }

               if (ch == 26) {
                  throw new ParserException("illegal identifier. " + this.info());
               }

               ++this.bufPos;
            }
         } else {
            while(true) {
               this.ch = this.charAt(++this.pos);
               if (!isIdentifierChar(this.ch)) {
                  break;
               }

               ++this.bufPos;
            }
         }

         this.ch = this.charAt(this.pos);
         this.stringVal = this.subString(this.mark, this.bufPos);
         this.token = Token.VARIANT;
      }
   }

   public void scanIdentifier() {
      this.hash_lower = 0L;
      this.hash = 0L;
      char first = this.ch;
      if (first == 'U' && this.isEnabled(SQLParserFeature.Presto) && this.charAt(this.pos + 1) == '&' && this.charAt(this.pos + 2) == '\'') {
         this.initBuff(32);
         this.pos += 3;

         while(true) {
            this.ch = this.charAt(this.pos);
            if (this.isEOF()) {
               this.lexError("unclosed.str.lit", new Object[0]);
               return;
            }

            if (this.ch == '\'') {
               this.ch = this.charAt(++this.pos);
               this.stringVal = new String(this.buf, 0, this.bufPos);
               this.token = Token.LITERAL_CHARS;
               return;
            }

            if (this.ch == '\\') {
               char c1 = this.charAt(++this.pos);
               char c2 = this.charAt(++this.pos);
               char c3 = this.charAt(++this.pos);
               char c4 = this.charAt(++this.pos);
               String tmp;
               if (this.ch == '+') {
                  char c5 = this.charAt(++this.pos);
                  char c6 = this.charAt(++this.pos);
                  tmp = new String(new char[]{c1, c2, c3, c4, c5, c6});
               } else {
                  tmp = new String(new char[]{c1, c2, c3, c4});
               }

               int intVal = Integer.parseInt(tmp, 16);
               this.putChar((char)intVal);
            } else {
               this.putChar(this.ch);
            }

            ++this.pos;
         }
      } else {
         if ((this.ch == 'b' || this.ch == 'B') && this.charAt(this.pos + 1) == '\'') {
            int i = 2;
            int mark = this.pos + 2;

            while(true) {
               char ch = this.charAt(this.pos + i);
               if (ch != '0' && ch != '1') {
                  if (ch == '\'') {
                     this.bufPos += i;
                     this.pos += i + 1;
                     this.stringVal = this.subString(mark, i - 2);
                     this.ch = this.charAt(this.pos);
                     this.token = Token.BITS;
                     return;
                  }

                  if (ch == 26) {
                     throw new ParserException("illegal identifier. " + this.info());
                  }
                  break;
               }

               ++i;
            }
         }

         if (this.ch == '`') {
            this.mark = this.pos;
            this.bufPos = 1;
            int startPos = this.pos + 1;
            this.hash_lower = -3750763034362895579L;
            this.hash = -3750763034362895579L;
            int i = startPos;

            while(true) {
               if (i >= this.text.length()) {
                  throw new ParserException("illegal identifier. " + this.info());
               }

               char ch = this.text.charAt(i);
               if ('`' == ch) {
                  if (i + 1 >= this.text.length() || '`' != this.text.charAt(i + 1)) {
                     this.stringVal = quoteTable.addSymbol(this.text, this.pos, i + 1 - this.pos, this.hash);
                     this.pos = i + 1;
                     this.ch = this.charAt(this.pos);
                     this.token = Token.IDENTIFIER;
                     break;
                  }

                  ++i;
               }

               this.hash_lower ^= (long)(ch >= 'A' && ch <= 'Z' ? ch + 32 : ch);
               this.hash_lower *= 1099511628211L;
               this.hash ^= (long)ch;
               this.hash *= 1099511628211L;
               ++i;
            }
         } else {
            boolean firstFlag = CharTypes.isFirstIdentifierChar(first);
            if (!firstFlag) {
               throw new ParserException("illegal identifier. " + this.info());
            }

            this.hash_lower = -3750763034362895579L;
            this.hash = -3750763034362895579L;
            this.hash_lower ^= (long)(this.ch >= 'A' && this.ch <= 'Z' ? this.ch + 32 : this.ch);
            this.hash_lower *= 1099511628211L;
            this.hash ^= (long)this.ch;
            this.hash *= 1099511628211L;
            this.mark = this.pos;
            this.bufPos = 1;
            char ch = '\u0000';

            while(true) {
               ch = this.charAt(++this.pos);
               if (!isIdentifierChar(ch)) {
                  this.ch = this.charAt(this.pos);
                  if (this.bufPos == 1) {
                     this.token = Token.IDENTIFIER;
                     this.stringVal = CharTypes.valueOf(first);
                     if (this.stringVal == null) {
                        this.stringVal = Character.toString(first);
                     }

                     return;
                  }

                  Token tok = this.keywords.getKeyword(this.hash_lower);
                  if (tok != null) {
                     this.token = tok;
                     if (this.token == Token.IDENTIFIER) {
                        this.stringVal = SymbolTable.global.addSymbol(this.text, this.mark, this.bufPos, this.hash);
                     } else {
                        this.stringVal = null;
                     }
                  } else {
                     this.token = Token.IDENTIFIER;
                     this.stringVal = SymbolTable.global.addSymbol(this.text, this.mark, this.bufPos, this.hash);
                  }
                  break;
               }

               ++this.bufPos;
               this.hash_lower ^= (long)(ch >= 'A' && ch <= 'Z' ? ch + 32 : ch);
               this.hash_lower *= 1099511628211L;
               this.hash ^= (long)ch;
               this.hash *= 1099511628211L;
            }
         }

      }
   }

   protected final void scanString() {
      this.scanString2();
   }

   public void skipFirstHintsOrMultiCommentAndNextToken() {
      int starIndex = this.pos + 2;

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

         int slashIndex = starIndex + 1;
         if (this.charAt(slashIndex) == '/') {
            this.pos = slashIndex + 1;
            this.ch = this.text.charAt(this.pos);
            if (this.pos < this.text.length() - 6) {
               int pos_6 = this.pos + 6;
               char c0 = this.ch;
               char c1 = this.text.charAt(this.pos + 1);
               char c2 = this.text.charAt(this.pos + 2);
               char c3 = this.text.charAt(this.pos + 3);
               char c4 = this.text.charAt(this.pos + 4);
               char c5 = this.text.charAt(this.pos + 5);
               char c6 = this.text.charAt(pos_6);
               if (c0 == 's' && c1 == 'e' && c2 == 'l' && c3 == 'e' && c4 == 'c' && c5 == 't' && c6 == ' ') {
                  this.comments = null;
                  this.reset(pos_6, ' ', Token.SELECT);
                  return;
               }

               if (c0 == 'i' && c1 == 'n' && c2 == 's' && c3 == 'e' && c4 == 'r' && c5 == 't' && c6 == ' ') {
                  this.comments = null;
                  this.reset(pos_6, ' ', Token.INSERT);
                  return;
               }

               if (c0 == 'u' && c1 == 'p' && c2 == 'd' && c3 == 'a' && c4 == 't' && c5 == 'e' && c6 == ' ') {
                  this.comments = null;
                  this.reset(pos_6, ' ', Token.UPDATE);
                  return;
               }

               if (c0 == 'd' && c1 == 'e' && c2 == 'l' && c3 == 'e' && c4 == 't' && c5 == 'e' && c6 == ' ') {
                  this.comments = null;
                  this.reset(pos_6, ' ', Token.DELETE);
                  return;
               }

               if (c0 == 'S' && c1 == 'E' && c2 == 'L' && c3 == 'E' && c4 == 'C' && c5 == 'T' && c6 == ' ') {
                  this.comments = null;
                  this.reset(pos_6, ' ', Token.SELECT);
                  return;
               }

               if (c0 == 'I' && c1 == 'N' && c2 == 'S' && c3 == 'E' && c4 == 'R' && c5 == 'T' && c6 == ' ') {
                  this.comments = null;
                  this.reset(pos_6, ' ', Token.INSERT);
                  return;
               }

               if (c0 == 'U' && c1 == 'P' && c2 == 'D' && c3 == 'A' && c4 == 'T' && c5 == 'E' && c6 == ' ') {
                  this.comments = null;
                  this.reset(pos_6, ' ', Token.UPDATE);
                  return;
               }

               if (c0 == 'D' && c1 == 'E' && c2 == 'L' && c3 == 'E' && c4 == 'T' && c5 == 'E' && c6 == ' ') {
                  this.comments = null;
                  this.reset(pos_6, ' ', Token.DELETE);
                  return;
               }

               this.nextToken();
               return;
            }

            this.nextToken();
            return;
         }

         ++starIndex;
      }
   }

   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());
            }

            char next = this.charAt(this.pos + 1);
            if (this.ch == '-' && next != '-') {
               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();
            }
         }
      }
   }

   public static boolean isIdentifierChar(char c) {
      if (c <= identifierFlags.length) {
         return identifierFlags[c];
      } else {
         return c != 12288 && c != '，';
      }
   }

   static {
      Map<String, Token> map = new HashMap();
      map.putAll(Keywords.DEFAULT_KEYWORDS.getKeywords());
      map.put("DUAL", Token.DUAL);
      map.put("FALSE", Token.FALSE);
      map.put("IF", Token.IF);
      map.put("KILL", Token.KILL);
      map.put("LIMIT", Token.LIMIT);
      map.put("TRUE", Token.TRUE);
      map.put("BINARY", Token.BINARY);
      map.put("SHOW", Token.SHOW);
      map.put("CACHE", Token.CACHE);
      map.put("ANALYZE", Token.ANALYZE);
      map.put("OPTIMIZE", Token.OPTIMIZE);
      map.put("ROW", Token.ROW);
      map.put("BEGIN", Token.BEGIN);
      map.put("END", Token.END);
      map.put("DIV", Token.DIV);
      map.put("MERGE", Token.MERGE);
      map.put("PARTITION", Token.PARTITION);
      map.put("CONTINUE", Token.CONTINUE);
      map.put("UNDO", Token.UNDO);
      map.put("SQLSTATE", Token.SQLSTATE);
      map.put("CONDITION", Token.CONDITION);
      map.put("MOD", Token.MOD);
      map.put("CONTAINS", Token.CONTAINS);
      map.put("RLIKE", Token.RLIKE);
      map.put("FULLTEXT", Token.FULLTEXT);
      map.put("INTERVAL", Token.INTERVAL);
      map.put("MINUTE", Token.MINUTE);
      map.put("DAY", Token.DAY);
      map.put("SECOND", Token.SECOND);
      map.put("HOUR", Token.HOUR);
      map.put("YEAR", Token.YEAR);
      map.put("MONTH", Token.MONTH);
      map.put("MICROSECOND", Token.MICROSECOND);
      map.put("WEEK", Token.WEEK);
      map.put("QUARTER", Token.QUARTER);
      map.put("SECOND_MICROSECOND", Token.SECOND_MICROSECOND);
      map.put("MINUTE_MICROSECOND", Token.MINUTE_MICROSECOND);
      map.put("MINUTE_SECOND", Token.MINUTE_SECOND);
      map.put("HOUR_MICROSECOND", Token.HOUR_MICROSECOND);
      map.put("HOUR_SECOND", Token.HOUR_SECOND);
      map.put("HOUR_MINUTE", Token.HOUR_MINUTE);
      map.put("DAY_MICROSECOND", Token.DAY_MICROSECOND);
      map.put("DAY_SECOND", Token.DAY_SECOND);
      map.put("DAY_MINUTE", Token.DAY_MINUTE);
      map.put("DAY_HOUR", Token.DAY_HOUR);
      map.put("YEAR_MONTH", Token.YEAR_MONTH);
      map.put("TABLE", Token.TABLE);
      map.put("ELSEIF", Token.ELSEIF);
      map.put("EXIT", Token.EXIT);
      map.put("FOLLOWS", Token.FOLLOWS);
      map.put("PRECEDES", Token.PRECEDES);
      map.put("REPAIR", Token.REPAIR);
      DEFAULT_MYSQL_KEYWORDS = new Keywords(map);
      identifierFlags = new boolean[256];

      for(char c = 0; c < identifierFlags.length; ++c) {
         if (c >= 'A' && c <= 'Z') {
            identifierFlags[c] = true;
         } else if (c >= 'a' && c <= 'z') {
            identifierFlags[c] = true;
         } else if (c >= '0' && c <= '9') {
            identifierFlags[c] = true;
         }
      }

      identifierFlags[95] = true;
      identifierFlags[36] = true;
   }
}
