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

import com.chenyang.druid.sql.ast.SQLExpr;
import com.chenyang.druid.sql.ast.SQLObject;
import com.chenyang.druid.sql.ast.expr.SQLIdentifierExpr;
import com.chenyang.druid.sql.ast.expr.SQLLiteralExpr;
import com.chenyang.druid.sql.ast.expr.SQLVariantRefExpr;
import com.chenyang.druid.sql.ast.statement.SQLSelect;
import com.chenyang.druid.sql.ast.statement.SQLSelectQuery;
import com.chenyang.druid.sql.ast.statement.SQLSelectQueryBlock;
import com.chenyang.druid.sql.ast.statement.SQLTableSource;
import com.chenyang.druid.sql.ast.statement.SQLUnionQuery;
import com.chenyang.druid.sql.dialect.mariadb.ast.MariadbForceIndexHint;
import com.chenyang.druid.sql.dialect.mariadb.ast.MariadbIgnoreIndexHint;
import com.chenyang.druid.sql.dialect.mariadb.ast.MariadbIndexHint;
import com.chenyang.druid.sql.dialect.mariadb.ast.MariadbIndexHintImpl;
import com.chenyang.druid.sql.dialect.mariadb.ast.MariadbUseIndexHint;
import com.chenyang.druid.sql.dialect.mariadb.ast.clause.MariadbSelectIntoStatement;
import com.chenyang.druid.sql.dialect.mariadb.ast.expr.MariadbOutFileExpr;
import com.chenyang.druid.sql.dialect.mariadb.ast.statement.MariadbSelectQueryBlock;
import com.chenyang.druid.sql.parser.ParserException;
import com.chenyang.druid.sql.parser.SQLExprParser;
import com.chenyang.druid.sql.parser.SQLSelectParser;
import com.chenyang.druid.sql.parser.Token;
import com.chenyang.druid.util.FnvHash;
import java.util.ArrayList;
import java.util.List;

public class MariadbSelectIntoParser extends SQLSelectParser {
   private List<SQLExpr> argsList;

   public MariadbSelectIntoParser(SQLExprParser exprParser) {
      super(exprParser);
   }

   public MariadbSelectIntoParser(String sql) {
      this((SQLExprParser)(new MariadbExprParser(sql)));
   }

   public MariadbSelectIntoStatement parseSelectInto() {
      SQLSelect select = this.select();
      MariadbSelectIntoStatement stmt = new MariadbSelectIntoStatement();
      stmt.setSelect(select);
      stmt.setVarList(this.argsList);
      return stmt;
   }

   public SQLSelectQuery query(SQLObject parent, boolean acceptUnion) {
      if (this.lexer.token() == Token.LPAREN) {
         this.lexer.nextToken();
         SQLSelectQuery select = this.query();
         this.accept(Token.RPAREN);
         return this.queryRest(select, acceptUnion);
      } else {
         MariadbSelectQueryBlock queryBlock = new MariadbSelectQueryBlock();
         if (this.lexer.token() == Token.SELECT) {
            this.lexer.nextToken();
            if (this.lexer.token() == Token.HINT) {
               this.exprParser.parseHints(queryBlock.getHints());
            }

            if (this.lexer.token() == Token.COMMENT) {
               this.lexer.nextToken();
            }

            if (this.lexer.token() == Token.DISTINCT) {
               queryBlock.setDistionOption(2);
               this.lexer.nextToken();
            } else if (this.lexer.identifierEquals("DISTINCTROW")) {
               queryBlock.setDistionOption(4);
               this.lexer.nextToken();
            } else if (this.lexer.token() == Token.ALL) {
               queryBlock.setDistionOption(1);
               this.lexer.nextToken();
            }

            if (this.lexer.identifierEquals("HIGH_PRIORITY")) {
               queryBlock.setHignPriority(true);
               this.lexer.nextToken();
            }

            if (this.lexer.identifierEquals("STRAIGHT_JOIN")) {
               queryBlock.setStraightJoin(true);
               this.lexer.nextToken();
            }

            if (this.lexer.identifierEquals("SQL_SMALL_RESULT")) {
               queryBlock.setSmallResult(true);
               this.lexer.nextToken();
            }

            if (this.lexer.identifierEquals("SQL_BIG_RESULT")) {
               queryBlock.setBigResult(true);
               this.lexer.nextToken();
            }

            if (this.lexer.identifierEquals("SQL_BUFFER_RESULT")) {
               queryBlock.setBufferResult(true);
               this.lexer.nextToken();
            }

            if (this.lexer.identifierEquals("SQL_CACHE")) {
               queryBlock.setCache(true);
               this.lexer.nextToken();
            }

            if (this.lexer.identifierEquals("SQL_NO_CACHE")) {
               queryBlock.setCache(false);
               this.lexer.nextToken();
            }

            if (this.lexer.identifierEquals("SQL_CALC_FOUND_ROWS")) {
               queryBlock.setCalcFoundRows(true);
               this.lexer.nextToken();
            }

            this.parseSelectList(queryBlock);
            this.argsList = this.parseIntoArgs();
         }

         this.parseFrom(queryBlock);
         this.parseWhere(queryBlock);
         this.parseGroupBy(queryBlock);
         queryBlock.setOrderBy(this.exprParser.parseOrderBy());
         if (this.lexer.token() == Token.LIMIT) {
            queryBlock.setLimit(this.exprParser.parseLimit());
         }

         if (this.lexer.token() == Token.PROCEDURE) {
            this.lexer.nextToken();
            throw new ParserException("TODO. " + this.lexer.info());
         } else {
            this.parseInto(queryBlock);
            if (this.lexer.token() == Token.FOR) {
               this.lexer.nextToken();
               this.accept(Token.UPDATE);
               queryBlock.setForUpdate(true);
            }

            if (this.lexer.token() == Token.LOCK) {
               this.lexer.nextToken();
               this.accept(Token.IN);
               this.acceptIdentifier("SHARE");
               this.acceptIdentifier("MODE");
               queryBlock.setLockInShareMode(true);
            }

            return this.queryRest(queryBlock, acceptUnion);
         }
      }
   }

   protected List<SQLExpr> parseIntoArgs() {
      List<SQLExpr> args = new ArrayList();
      if (this.lexer.token() == Token.INTO) {
         this.accept(Token.INTO);

         while(true) {
            SQLExpr var = this.exprParser.primary();
            if (var instanceof SQLIdentifierExpr) {
               var = new SQLVariantRefExpr(((SQLIdentifierExpr)var).getName());
            }

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

            this.accept(Token.COMMA);
         }
      }

      return args;
   }

   protected void parseInto(SQLSelectQueryBlock queryBlock) {
      if (this.lexer.token() == Token.INTO) {
         this.lexer.nextToken();
         if (this.lexer.identifierEquals("OUTFILE")) {
            this.lexer.nextToken();
            MariadbOutFileExpr outFile = new MariadbOutFileExpr();
            outFile.setFile(this.expr());
            queryBlock.setInto((SQLExpr)outFile);
            if (this.lexer.identifierEquals("FIELDS") || this.lexer.identifierEquals("COLUMNS")) {
               this.lexer.nextToken();
               if (this.lexer.identifierEquals("TERMINATED")) {
                  this.lexer.nextToken();
                  this.accept(Token.BY);
               }

               outFile.setColumnsTerminatedBy((SQLLiteralExpr)this.expr());
               if (this.lexer.identifierEquals("OPTIONALLY")) {
                  this.lexer.nextToken();
                  outFile.setColumnsEnclosedOptionally(true);
               }

               if (this.lexer.identifierEquals("ENCLOSED")) {
                  this.lexer.nextToken();
                  this.accept(Token.BY);
                  outFile.setColumnsEnclosedBy((SQLLiteralExpr)this.expr());
               }

               if (this.lexer.identifierEquals("ESCAPED")) {
                  this.lexer.nextToken();
                  this.accept(Token.BY);
                  outFile.setColumnsEscaped((SQLLiteralExpr)this.expr());
               }
            }

            if (this.lexer.identifierEquals("LINES")) {
               this.lexer.nextToken();
               if (this.lexer.identifierEquals("STARTING")) {
                  this.lexer.nextToken();
                  this.accept(Token.BY);
                  outFile.setLinesStartingBy((SQLLiteralExpr)this.expr());
               } else {
                  this.lexer.identifierEquals("TERMINATED");
                  this.lexer.nextToken();
                  this.accept(Token.BY);
                  outFile.setLinesTerminatedBy((SQLLiteralExpr)this.expr());
               }
            }
         } else {
            queryBlock.setInto((SQLExpr)this.exprParser.name());
         }
      }

   }

   public SQLTableSource parseTableSourceRest(SQLTableSource tableSource) {
      if (this.lexer.identifierEquals("USING")) {
         return tableSource;
      } else {
         this.parseIndexHintList(tableSource);
         return super.parseTableSourceRest(tableSource);
      }
   }

   private void parseIndexHintList(SQLTableSource tableSource) {
      if (this.lexer.token() == Token.USE) {
         this.lexer.nextToken();
         MariadbUseIndexHint hint = new MariadbUseIndexHint();
         this.parseIndexHint(hint);
         tableSource.getHints().add(hint);
         this.parseIndexHintList(tableSource);
      }

      if (this.lexer.identifierEquals("IGNORE")) {
         this.lexer.nextToken();
         MariadbIgnoreIndexHint hint = new MariadbIgnoreIndexHint();
         this.parseIndexHint(hint);
         tableSource.getHints().add(hint);
         this.parseIndexHintList(tableSource);
      }

      if (this.lexer.identifierEquals(FnvHash.Constants.FORCE)) {
         this.lexer.nextToken();
         MariadbForceIndexHint hint = new MariadbForceIndexHint();
         this.parseIndexHint(hint);
         tableSource.getHints().add(hint);
         this.parseIndexHintList(tableSource);
      }

   }

   private void parseIndexHint(MariadbIndexHintImpl hint) {
      if (this.lexer.token() == Token.INDEX) {
         this.lexer.nextToken();
      } else {
         this.accept(Token.KEY);
      }

      if (this.lexer.token() == Token.FOR) {
         this.lexer.nextToken();
         if (this.lexer.token() == Token.JOIN) {
            this.lexer.nextToken();
            hint.setOption(MariadbIndexHint.Option.JOIN);
         } else if (this.lexer.token() == Token.ORDER) {
            this.lexer.nextToken();
            this.accept(Token.BY);
            hint.setOption(MariadbIndexHint.Option.ORDER_BY);
         } else {
            this.accept(Token.GROUP);
            this.accept(Token.BY);
            hint.setOption(MariadbIndexHint.Option.GROUP_BY);
         }
      }

      this.accept(Token.LPAREN);
      if (this.lexer.token() == Token.PRIMARY) {
         this.lexer.nextToken();
         hint.getIndexList().add(new SQLIdentifierExpr("PRIMARY"));
      } else {
         this.exprParser.names(hint.getIndexList());
      }

      this.accept(Token.RPAREN);
   }

   public SQLUnionQuery unionRest(SQLUnionQuery union) {
      if (this.lexer.token() == Token.LIMIT) {
         union.setLimit(this.exprParser.parseLimit());
      }

      return super.unionRest(union);
   }

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