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

import com.alibaba.druid.DbType;
import com.alibaba.druid.sql.ast.SQLExpr;
import com.alibaba.druid.sql.ast.SQLName;
import com.alibaba.druid.sql.ast.expr.SQLCharExpr;
import com.alibaba.druid.sql.ast.expr.SQLListExpr;
import com.alibaba.druid.sql.ast.statement.SQLColumnDefinition;
import com.alibaba.druid.sql.ast.statement.SQLConstraint;
import com.alibaba.druid.sql.ast.statement.SQLCreateTableStatement;
import com.alibaba.druid.sql.ast.statement.SQLExprTableSource;
import com.alibaba.druid.sql.ast.statement.SQLExternalRecordFormat;
import com.alibaba.druid.sql.ast.statement.SQLSelect;
import com.alibaba.druid.sql.ast.statement.SQLSelectOrderByItem;
import com.alibaba.druid.sql.ast.statement.SQLTableConstraint;
import com.alibaba.druid.sql.ast.statement.SQLTableElement;
import com.alibaba.druid.sql.dialect.hive.ast.HiveInputOutputFormat;
import com.alibaba.druid.sql.dialect.hive.ast.stmt.HiveCreateTableStatement;
import com.alibaba.druid.sql.parser.Lexer;
import com.alibaba.druid.sql.parser.ParserException;
import com.alibaba.druid.sql.parser.SQLCreateTableParser;
import com.alibaba.druid.sql.parser.SQLExprParser;
import com.alibaba.druid.sql.parser.SQLSelectParser;
import com.alibaba.druid.sql.parser.Token;
import com.alibaba.druid.util.FnvHash;
import java.util.List;

public class HiveCreateTableParser extends SQLCreateTableParser {
   public HiveCreateTableParser(SQLExprParser exprParser) {
      super(exprParser);
   }

   public HiveCreateTableParser(Lexer lexer) {
      super((SQLExprParser)(new HiveExprParser(lexer)));
   }

   public SQLCreateTableStatement parseCreateTable() {
      List<String> comments = null;
      if (this.lexer.isKeepComments() && this.lexer.hasComment()) {
         comments = this.lexer.readAndResetComments();
      }

      SQLCreateTableStatement stmt = this.parseCreateTable(true, "table");
      if (comments != null) {
         stmt.addBeforeComment(comments);
      }

      return stmt;
   }

   public SQLCreateTableStatement parseCreateViewA() {
      SQLCreateTableStatement stmt = this.parseCreateTable(false, "view");
      return stmt;
   }

   public SQLCreateTableStatement parseCreateViewB() {
      SQLCreateTableStatement stmt = this.parseCreateTable(false, "materialized");
      return stmt;
   }

   public SQLCreateTableStatement parseCreateTable(boolean acceptCreate, String type) {
      HiveCreateTableStatement stmt = this.newCreateStatement();
      stmt.type = type;
      if (acceptCreate) {
         if (this.lexer.hasComment() && this.lexer.isKeepComments()) {
            stmt.addBeforeComment(this.lexer.readAndResetComments());
         }

         this.accept(Token.CREATE);
      }

      if (this.lexer.identifierEquals(FnvHash.Constants.EXTERNAL)) {
         this.lexer.nextToken();
         stmt.setExternal(true);
      }

      if (this.lexer.identifierEquals(FnvHash.Constants.TEMPORARY)) {
         this.lexer.nextToken();
         stmt.setType(SQLCreateTableStatement.Type.TEMPORARY);
      }

      if (type.equals("table")) {
         this.accept(Token.TABLE);
      } else if (type.equals("view")) {
         this.accept(Token.VIEW);
      } else if (type.equals("materialized")) {
         this.accept(Token.MATERIALIZED);
         this.accept(Token.VIEW);
      }

      if (this.lexer.token() == Token.IF || this.lexer.identifierEquals(FnvHash.Constants.IF)) {
         this.lexer.nextToken();
         this.accept(Token.NOT);
         this.accept(Token.EXISTS);
         stmt.setIfNotExiists(true);
      }

      stmt.setName(this.exprParser.name());
      if (this.lexer.token() == Token.LPAREN) {
         this.lexer.nextToken();

         do {
            Token token = this.lexer.token();
            if (type == "table") {
               if (token != Token.IDENTIFIER && token != Token.LITERAL_ALIAS) {
                  if (token != Token.PRIMARY && token != Token.UNIQUE && token != Token.CHECK && token != Token.CONSTRAINT && token != Token.FOREIGN) {
                     if (token == Token.TABLESPACE) {
                        throw new ParserException("TODO " + this.lexer.info());
                     }

                     SQLColumnDefinition column = this.exprParser.parseColumn();
                     stmt.getTableElementList().add(column);
                  } else {
                     SQLConstraint constraint = this.exprParser.parseConstaint();
                     constraint.setParent(stmt);
                     stmt.getTableElementList().add((SQLTableElement)constraint);
                  }
               } else {
                  SQLColumnDefinition column = this.exprParser.parseColumn();
                  stmt.getTableElementList().add(column);
               }
            } else if (this.lexer.token() == Token.CONSTRAINT) {
               SQLTableConstraint constraint = (SQLTableConstraint)this.exprParser.parseConstaint();
               stmt.addColumn(constraint);
            } else {
               SQLColumnDefinition column = new SQLColumnDefinition();
               column.setDbType(this.dbType);
               SQLName expr = this.exprParser.name();
               column.setName(expr);
               if (this.dbType == DbType.odps && expr.getSimpleName().startsWith("@")) {
                  column.setDataType(this.exprParser.parseDataType());
               }

               this.exprParser.parseColumnRest(column);
               if (this.lexer.token() == Token.COMMENT) {
                  this.lexer.nextToken();
                  SQLExpr comment;
                  if (this.lexer.token() == Token.LITERAL_ALIAS) {
                     String alias = this.lexer.stringVal();
                     if (alias.length() > 2 && alias.charAt(0) == '"' && alias.charAt(alias.length() - 1) == '"') {
                        alias = alias.substring(1, alias.length() - 1);
                     }

                     comment = new SQLCharExpr(alias);
                     this.lexer.nextToken();
                  } else {
                     comment = this.exprParser.primary();
                  }

                  column.setComment(comment);
               }

               column.setParent(stmt);
               stmt.addColumn(column);
            }

            if (this.lexer.token() != Token.COMMA) {
               break;
            }

            this.lexer.nextToken();
         } while(this.lexer.token() != Token.RPAREN);

         this.accept(Token.RPAREN);
      } else if (this.lexer.token() == Token.LIKE) {
         this.parseLike(stmt);
      }

      if (this.lexer.identifierEquals(FnvHash.Constants.ENGINE)) {
         this.lexer.nextToken();
         this.accept(Token.EQ);
         this.lexer.nextToken();
      }

      if (this.lexer.identifierEquals(FnvHash.Constants.CHARSET)) {
         this.lexer.nextToken();
         this.accept(Token.EQ);
         this.lexer.nextToken();
      }

      if (this.lexer.identifierEquals(FnvHash.Constants.USING) || this.lexer.token() == Token.USING) {
         this.lexer.nextToken();
         SQLExpr using = this.exprParser.expr();
         stmt.setUsing(using);
      }

      if (this.lexer.identifierEquals(FnvHash.Constants.OPTIONS)) {
         this.lexer.nextToken();
         this.accept(Token.LPAREN);
         this.parseAssignItems(stmt.getTableOptions(), stmt, false);
         this.accept(Token.RPAREN);
      }

      if (this.lexer.token() == Token.COMMENT) {
         this.lexer.nextToken();
         SQLExpr comment = this.exprParser.expr();
         stmt.setComment(comment);
      }

      if (this.lexer.identifierEquals(FnvHash.Constants.MAPPED)) {
         this.lexer.nextToken();
         this.accept(Token.BY);
         this.exprParser.parseAssignItem(stmt.getMappedBy(), stmt);
      }

      if (this.lexer.token() == Token.PARTITIONED) {
         this.lexer.nextToken();
         this.accept(Token.BY);
         this.accept(Token.LPAREN);

         while(true) {
            if (this.lexer.token() != Token.IDENTIFIER) {
               throw new ParserException("expect identifier. " + this.lexer.info());
            }

            SQLColumnDefinition column = this.exprParser.parseColumn();
            stmt.addPartitionColumn(column);
            if (this.lexer.isKeepComments() && this.lexer.hasComment()) {
               column.addAfterComment(this.lexer.readAndResetComments());
            }

            if (this.lexer.token() != Token.COMMA) {
               this.accept(Token.RPAREN);
               break;
            }

            this.lexer.nextToken();
            if (this.lexer.isKeepComments() && this.lexer.hasComment()) {
               column.addAfterComment(this.lexer.readAndResetComments());
            }
         }
      }

      if (this.lexer.identifierEquals(FnvHash.Constants.CLUSTERED)) {
         this.lexer.nextToken();
         this.accept(Token.BY);
         this.accept(Token.LPAREN);

         while(true) {
            SQLSelectOrderByItem item = this.exprParser.parseSelectOrderByItem();
            stmt.addClusteredByItem(item);
            if (this.lexer.token() != Token.COMMA) {
               this.accept(Token.RPAREN);
               break;
            }

            this.lexer.nextToken();
         }
      }

      if (this.lexer.identifierEquals(FnvHash.Constants.SORTED)) {
         this.parseSortedBy(stmt);
      }

      if (stmt.getClusteredBy().size() > 0 || stmt.getSortedBy().size() > 0) {
         this.accept(Token.INTO);
         if (this.lexer.token() != Token.LITERAL_INT) {
            throw new ParserException("into buckets must be integer. " + this.lexer.info());
         }

         stmt.setBuckets(this.lexer.integerValue().intValue());
         this.lexer.nextToken();
         this.acceptIdentifier("BUCKETS");
      }

      if (this.lexer.identifierEquals(FnvHash.Constants.SKEWED)) {
         this.lexer.nextToken();
         this.accept(Token.BY);
         this.accept(Token.LPAREN);
         this.exprParser.exprList(stmt.getSkewedBy(), stmt);
         this.accept(Token.RPAREN);
         this.accept(Token.ON);
         this.accept(Token.LPAREN);

         while(true) {
            if (this.lexer.token() == Token.LPAREN) {
               SQLListExpr list = new SQLListExpr();
               this.lexer.nextToken();
               this.exprParser.exprList(list.getItems(), list);
               this.accept(Token.RPAREN);
               stmt.addSkewedByOn(list);
            } else {
               SQLExpr expr = this.exprParser.expr();
               stmt.addSkewedByOn(expr);
            }

            if (this.lexer.token() != Token.COMMA) {
               this.accept(Token.RPAREN);
               break;
            }

            this.lexer.nextToken();
         }
      }

      if (this.lexer.token() == Token.ROW || this.lexer.identifierEquals(FnvHash.Constants.ROW)) {
         this.parseRowFormat(stmt);
      }

      if (this.lexer.identifierEquals(FnvHash.Constants.STORED)) {
         this.lexer.nextToken();
         this.accept(Token.AS);
         if (this.lexer.identifierEquals(FnvHash.Constants.INPUTFORMAT)) {
            HiveInputOutputFormat format = new HiveInputOutputFormat();
            this.lexer.nextToken();
            format.setInput(this.exprParser.primary());
            if (this.lexer.identifierEquals(FnvHash.Constants.OUTPUTFORMAT)) {
               this.lexer.nextToken();
               format.setOutput(this.exprParser.primary());
            }

            stmt.setStoredAs(format);
         } else {
            SQLName name = this.exprParser.name();
            stmt.setStoredAs(name);
         }
      }

      if (this.lexer.identifierEquals(FnvHash.Constants.LOCATION)) {
         this.lexer.nextToken();
         SQLExpr location = this.exprParser.primary();
         stmt.setLocation(location);
      }

      if (this.lexer.token() == Token.LIKE) {
         this.parseLike(stmt);
      }

      if (this.lexer.identifierEquals(FnvHash.Constants.TBLPROPERTIES)) {
         this.parseTblProperties(stmt);
      }

      if (this.lexer.identifierEquals(FnvHash.Constants.META)) {
         this.lexer.nextToken();
         this.acceptIdentifier("LIFECYCLE");
         stmt.setMetaLifeCycle(this.exprParser.primary());
      }

      if (this.lexer.token() == Token.AS) {
         this.lexer.nextToken();
         SQLSelect select = this.createSQLSelectParser().select();
         stmt.setSelect(select);
      }

      if (this.lexer.token() == Token.LIKE) {
         this.lexer.nextToken();
         Lexer.SavePoint mark = this.lexer.mark();
         if (this.lexer.token() == Token.SELECT) {
            stmt.setLikeQuery(true);
            SQLSelect select = this.createSQLSelectParser().select();
            stmt.setSelect(select);
         } else {
            this.lexer.reset(mark);
            if (this.lexer.identifierEquals(FnvHash.Constants.MAPPING)) {
               SQLExpr like = this.exprParser.primary();
               stmt.setLike(new SQLExprTableSource(like));
            } else {
               SQLName name = this.exprParser.name();
               stmt.setLike(name);
            }
         }
      }

      if (this.lexer.token() == Token.COMMENT) {
         this.lexer.nextToken();
         SQLExpr comment = this.exprParser.expr();
         stmt.setComment(comment);
      }

      if (this.lexer.identifierEquals(FnvHash.Constants.USING) || this.lexer.token() == Token.USING) {
         this.lexer.nextToken();
         SQLExpr using = this.exprParser.expr();
         stmt.setUsing(using);
      }

      if (this.lexer.identifierEquals(FnvHash.Constants.TBLPROPERTIES)) {
         this.lexer.nextToken();
         this.accept(Token.LPAREN);
         this.parseAssignItems(stmt.getTblProperties(), stmt, false);
         this.accept(Token.RPAREN);
      }

      return stmt;
   }

   private void parseTblProperties(HiveCreateTableStatement stmt) {
      this.lexer.nextToken();
      this.accept(Token.LPAREN);

      do {
         String name = this.lexer.stringVal();
         this.lexer.nextToken();
         if (this.lexer.token() == Token.DOT) {
            this.lexer.nextToken();
            name = name + "." + this.lexer.stringVal();
            this.lexer.nextToken();
         }

         this.accept(Token.EQ);
         SQLExpr value = this.exprParser.primary();
         stmt.addTblProperty(name, value);
         if (this.lexer.token() != Token.COMMA) {
            break;
         }

         this.lexer.nextToken();
      } while(this.lexer.token() != Token.RPAREN);

      this.accept(Token.RPAREN);
   }

   protected void parseLike(HiveCreateTableStatement stmt) {
      this.lexer.nextToken();
      if (this.lexer.identifierEquals(FnvHash.Constants.MAPPING)) {
         SQLExpr like = this.exprParser.primary();
         stmt.setLike(new SQLExprTableSource(like));
      } else if (this.lexer.token() != Token.SELECT && this.lexer.token() != Token.LPAREN) {
         SQLName name = this.exprParser.name();
         stmt.setLike(name);
      } else {
         SQLSelect select = this.createSQLSelectParser().select();
         stmt.setLikeQuery(true);
         stmt.setSelect(select);
      }

   }

   private void parseSortedBy(HiveCreateTableStatement stmt) {
      this.lexer.nextToken();
      this.accept(Token.BY);
      this.accept(Token.LPAREN);

      while(true) {
         SQLSelectOrderByItem item = this.exprParser.parseSelectOrderByItem();
         stmt.addSortedByItem(item);
         if (this.lexer.token() != Token.COMMA) {
            this.accept(Token.RPAREN);
            return;
         }

         this.lexer.nextToken();
      }
   }

   private void parseRowFormat(HiveCreateTableStatement stmt) {
      SQLExternalRecordFormat format = this.getExprParser().parseRowFormat();
      stmt.setRowFormat(format);
      if (this.lexer.token() == Token.WITH) {
         this.lexer.nextToken();
         this.acceptIdentifier("SERDEPROPERTIES");
         this.accept(Token.LPAREN);

         while(true) {
            String name = this.lexer.stringVal();
            this.lexer.nextToken();
            this.accept(Token.EQ);
            SQLExpr value = this.exprParser.primary();
            stmt.getSerdeProperties().put(name, value);
            if (this.lexer.token() != Token.COMMA) {
               this.accept(Token.RPAREN);
               break;
            }

            this.lexer.nextToken();
         }
      }

   }

   public HiveExprParser getExprParser() {
      return (HiveExprParser)this.exprParser;
   }

   protected HiveCreateTableStatement newCreateStatement() {
      return new HiveCreateTableStatement();
   }

   public SQLSelectParser createSQLSelectParser() {
      return new HiveSelectParser(this.exprParser, this.selectListCache);
   }
}
