package com.alibaba.druid.sql.ast;

import com.alibaba.druid.sql.ast.expr.SQLCharExpr;
import com.alibaba.druid.sql.ast.expr.SQLIdentifierExpr;
import com.alibaba.druid.sql.ast.expr.SQLPropertyExpr;
import com.alibaba.druid.sql.dialect.mysql.parser.MySqlExprParser;
import com.alibaba.druid.sql.parser.Lexer;
import com.alibaba.druid.sql.parser.SQLParserFeature;
import com.alibaba.druid.sql.parser.Token;
import com.alibaba.druid.util.FnvHash;
import java.util.ArrayList;
import java.util.List;

public class TDDLHint extends SQLCommentHint {
   private List<Function> functions = new ArrayList();
   private String json;
   private Type type;

   public List<Function> getFunctions() {
      return this.functions;
   }

   public TDDLHint(String text) {
      super(text);
      this.type = Type.Unknown;
      MySqlExprParser hintParser = new MySqlExprParser(text, new SQLParserFeature[]{SQLParserFeature.TDDLHint});
      Lexer lexer = hintParser.getLexer();
      if (lexer.token() == Token.PLUS || lexer.token() == Token.BANG) {
         lexer.nextToken();
      }

      if (lexer.identifierEquals(FnvHash.Constants.TDDL)) {
         lexer.nextToken();
         switch (lexer.token()) {
            case COLON:
               lexer.nextToken();

               while(true) {
                  if (lexer.token() == Token.AND) {
                     lexer.nextToken();
                  }

                  String name = lexer.stringVal();
                  long hash = lexer.hash_lower();
                  if (lexer.identifierEquals(FnvHash.Constants.NODE)) {
                     lexer.nextToken();
                     if (lexer.token() == Token.IN) {
                        lexer.nextToken();
                        name = "NODE_IN";
                     } else if (lexer.token() == Token.EQ) {
                        lexer.nextToken();
                        name = "NODE_IN";
                        SQLExpr value = hintParser.primary();
                        Function function = new Function(name);
                        Argument argument = new Argument((SQLExpr)null, value);
                        function.getArguments().add(argument);
                        this.functions.add(function);
                        if (lexer.token() == Token.EOF) {
                           break;
                        }
                        continue;
                     }
                  } else if (hash != FnvHash.Constants.SCAN && hash != FnvHash.Constants.DEFER) {
                     if (hash != FnvHash.Constants.SQL_DELAY_CUTOFF && hash != FnvHash.Constants.SOCKET_TIMEOUT && hash != FnvHash.Constants.UNDO_LOG_LIMIT && hash != FnvHash.Constants.FORBID_EXECUTE_DML_ALL) {
                        lexer.nextToken();
                     } else {
                        lexer.nextToken();
                        if (lexer.token() == Token.EQ) {
                           lexer.nextToken();
                           SQLExpr value = hintParser.primary();
                           Function function = new Function(name);
                           Argument argument = new Argument((SQLExpr)null, value);
                           function.getArguments().add(argument);
                           this.functions.add(function);
                           if (lexer.token() == Token.EOF) {
                              break;
                           }
                           continue;
                        }
                     }
                  } else {
                     lexer.nextToken();
                     if (lexer.token() == Token.EQ) {
                        lexer.nextToken();
                        SQLExpr value = hintParser.primary();
                        Function function = new Function(name);
                        Argument argument = new Argument((SQLExpr)null, value);
                        function.getArguments().add(argument);
                        this.functions.add(function);
                        if (lexer.token() == Token.EOF) {
                           break;
                        }
                        continue;
                     }

                     if (lexer.token() == Token.EOF) {
                        Function function = new Function(name);
                        this.functions.add(function);
                        break;
                     }
                  }

                  if (lexer.token() == Token.DOT) {
                     lexer.nextToken();
                     if (!lexer.identifierEquals("partition_key")) {
                        return;
                     }

                     lexer.nextToken();
                     hintParser.accept(Token.EQ);
                     SQLExpr value = hintParser.primary();
                     Function function = new Function("PARTITIONS");
                     this.functions.add(function);
                     function.getArguments().add(new Argument(new SQLPropertyExpr(name, "partition_key"), value));

                     while(lexer.token() == Token.AND) {
                        lexer.nextToken();
                        SQLExpr key = hintParser.primary();
                        hintParser.accept(Token.EQ);
                        value = hintParser.primary();
                        function.getArguments().add(new Argument(key, value));
                     }

                     if (lexer.token() == Token.EOF) {
                        break;
                     }
                  } else if (lexer.token() == Token.EQ) {
                     lexer.nextToken();
                     SQLExpr value = hintParser.primary();
                     Function function = new Function(name);
                     Argument argument = new Argument((SQLExpr)null, value);
                     function.getArguments().add(argument);
                     this.functions.add(function);
                     if (lexer.token() == Token.EOF) {
                        break;
                     }
                     continue;
                  }

                  Function function = new Function(name);
                  this.functions.add(function);
                  if (hash == FnvHash.Constants.MASTER) {
                     if (lexer.token() == Token.EOF) {
                        break;
                     }

                     if (lexer.token() == Token.BAR) {
                        lexer.nextToken();
                        continue;
                     }
                  }

                  if (hash == FnvHash.Constants.SLAVE) {
                     if (lexer.token() == Token.EOF) {
                        break;
                     }

                     if (lexer.token() == Token.AND) {
                        lexer.nextToken();
                        continue;
                     }
                  }

                  if (lexer.token() != Token.AND) {
                     hintParser.accept(Token.LPAREN);
                     if (lexer.token() == Token.RPAREN) {
                        lexer.nextToken();
                     } else {
                        while(true) {
                           Lexer.SavePoint mark = lexer.mark();
                           SQLExpr value = null;
                           String keyVal = lexer.stringVal();
                           long keyHash = lexer.hash_lower();
                           lexer.nextToken();
                           SQLIdentifierExpr key = new SQLIdentifierExpr(keyVal, keyHash);
                           if (lexer.token() == Token.EQ) {
                              hintParser.accept(Token.EQ);
                              if (lexer.token() == Token.LITERAL_ALIAS) {
                                 String stringVal = lexer.stringVal();
                                 stringVal = stringVal.substring(1, stringVal.length() - 1);
                                 value = new SQLCharExpr(stringVal);
                                 value = hintParser.exprRest(value);
                                 lexer.nextToken();
                              } else {
                                 value = hintParser.expr();
                              }
                           }

                           if (value == null) {
                              lexer.reset(mark);
                              key = null;
                              value = hintParser.expr();
                           }

                           Argument argument = new Argument(key, value);
                           function.getArguments().add(argument);
                           if (lexer.token() == Token.COMMA) {
                              lexer.nextToken();
                           } else if (lexer.token() == Token.RPAREN) {
                              lexer.nextToken();
                              break;
                           }
                        }
                     }

                     if (lexer.token() != Token.AND) {
                        if (hash == FnvHash.Constants.MASTER && lexer.token() == Token.BAR) {
                           lexer.nextToken();
                        }

                        if (lexer.token() == Token.EOF) {
                           break;
                        }
                     }
                  }
               }

               if (this.functions.size() > 0) {
                  this.type = Type.Function;
               }

               return;
            case LPAREN:
               int rp = text.lastIndexOf(41);
               if (rp != -1) {
                  this.json = text.substring(lexer.pos(), rp);
                  this.type = Type.JSON;
               }

               return;
            default:
         }
      }
   }

   public String getJson() {
      return this.json;
   }

   public Type getType() {
      return this.type;
   }

   public static class Function {
      private final String name;
      private final List<Argument> arguments = new ArrayList();

      public Function(String name) {
         this.name = name;
      }

      public String getName() {
         return this.name;
      }

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

   public static class Argument {
      private final SQLExpr name;
      private final SQLExpr value;

      public Argument(SQLExpr name, SQLExpr value) {
         this.name = name;
         this.value = value;
      }

      public SQLExpr getName() {
         return this.name;
      }

      public SQLExpr getValue() {
         return this.value;
      }
   }

   public static enum Type {
      Function,
      JSON,
      Unknown;
   }
}
