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

import com.alibaba.druid.sql.ast.SQLExpr;
import com.alibaba.druid.sql.ast.SQLName;
import com.alibaba.druid.sql.ast.SQLObject;
import com.alibaba.druid.sql.ast.SQLOver;
import com.alibaba.druid.sql.ast.SQLWindow;
import com.alibaba.druid.sql.ast.expr.SQLAggregateExpr;
import com.alibaba.druid.sql.ast.expr.SQLIdentifierExpr;
import com.alibaba.druid.sql.ast.expr.SQLListExpr;
import com.alibaba.druid.sql.ast.expr.SQLSizeExpr;
import com.alibaba.druid.sql.ast.statement.SQLExprHint;
import com.alibaba.druid.sql.ast.statement.SQLExprTableSource;
import com.alibaba.druid.sql.ast.statement.SQLSelect;
import com.alibaba.druid.sql.ast.statement.SQLSelectQuery;
import com.alibaba.druid.sql.ast.statement.SQLSelectQueryBlock;
import com.alibaba.druid.sql.ast.statement.SQLSubqueryTableSource;
import com.alibaba.druid.sql.ast.statement.SQLTableSampling;
import com.alibaba.druid.sql.ast.statement.SQLTableSource;
import com.alibaba.druid.sql.ast.statement.SQLUnionQuery;
import com.alibaba.druid.sql.ast.statement.SQLValuesTableSource;
import com.alibaba.druid.sql.ast.statement.SQLWithSubqueryClause;
import com.alibaba.druid.sql.dialect.sqlserver.ast.SQLServerSelectQueryBlock;
import com.alibaba.druid.sql.dialect.sqlserver.ast.SQLServerTop;
import com.alibaba.druid.sql.dialect.sqlserver.ast.clause.SQLServerOver;
import com.alibaba.druid.sql.dialect.sqlserver.ast.stmt.SQLServerSelectPivot;
import com.alibaba.druid.sql.dialect.sqlserver.ast.stmt.SQLServerSelectSubqueryTableSource;
import com.alibaba.druid.sql.dialect.sqlserver.ast.stmt.SQLServerSelectTableReference;
import com.alibaba.druid.sql.dialect.sqlserver.ast.stmt.SQLServerSelectTableSource;
import com.alibaba.druid.sql.dialect.sqlserver.ast.stmt.SQLServerSelectUnPivot;
import com.alibaba.druid.sql.dialect.sqlserver.ast.stmt.SQLServerUnionQueryTableSource;
import com.alibaba.druid.sql.dialect.sqlserver.ast.stmt.SQLServerValuesTableSource;
import com.alibaba.druid.sql.parser.Lexer;
import com.alibaba.druid.sql.parser.ParserException;
import com.alibaba.druid.sql.parser.SQLExprParser;
import com.alibaba.druid.sql.parser.SQLSelectListCache;
import com.alibaba.druid.sql.parser.SQLSelectParser;
import com.alibaba.druid.sql.parser.Token;
import com.alibaba.druid.util.FnvHash;

public class SQLServerSelectParser extends SQLSelectParser {
   public SQLServerSelectParser(String sql) {
      super((SQLExprParser)(new SQLServerExprParser(sql)));
   }

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

   public SQLServerSelectParser(SQLExprParser exprParser, SQLSelectListCache selectListCache) {
      super(exprParser, selectListCache);
   }

   public SQLSelect select() {
      SQLSelect select = new SQLSelect();
      if (this.lexer.token() == Token.WITH) {
         SQLWithSubqueryClause with = this.parseWith();
         select.setWithSubQuery(with);
      }

      select.setQuery(this.query());
      select.setOrderBy(this.parseOrderBy());
      if (select.getOrderBy() == null) {
         select.setOrderBy(this.parseOrderBy());
      }

      if (this.lexer.token() == Token.FOR) {
         this.lexer.nextToken();
         if (this.lexer.identifierEquals("BROWSE")) {
            this.lexer.nextToken();
            select.setForBrowse(true);
         } else {
            if (!this.lexer.identifierEquals("XML")) {
               throw new ParserException("syntax error, not support option : " + this.lexer.token() + ", " + this.lexer.info());
            }

            this.lexer.nextToken();

            while(true) {
               if (!this.lexer.identifierEquals("AUTO") && !this.lexer.identifierEquals("TYPE") && !this.lexer.identifierEquals("XMLSCHEMA")) {
                  if (this.lexer.identifierEquals("ELEMENTS")) {
                     this.lexer.nextToken();
                     if (this.lexer.identifierEquals("XSINIL")) {
                        this.lexer.nextToken();
                        select.getForXmlOptions().add("ELEMENTS XSINIL");
                     } else {
                        select.getForXmlOptions().add("ELEMENTS");
                     }
                  } else {
                     if (!this.lexer.identifierEquals("PATH")) {
                        break;
                     }

                     SQLExpr xmlPath = this.exprParser.expr();
                     select.setXmlPath(xmlPath);
                  }
               } else {
                  select.getForXmlOptions().add(this.lexer.stringVal());
                  this.lexer.nextToken();
               }

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

               this.lexer.nextToken();
            }
         }
      }

      if (this.lexer.identifierEquals("OFFSET")) {
         this.lexer.nextToken();
         SQLExpr offset = this.expr();
         this.acceptIdentifier("ROWS");
         select.setOffset(offset);
         if (this.lexer.token() == Token.FETCH) {
            this.lexer.nextToken();
            this.acceptIdentifier("NEXT");
            SQLExpr rowCount = this.expr();
            this.acceptIdentifier("ROWS");
            this.acceptIdentifier("ONLY");
            select.setRowCount(rowCount);
         }
      }

      return select;
   }

   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 {
         SQLServerSelectQueryBlock queryBlock = new SQLServerSelectQueryBlock();
         if (this.lexer.token() == Token.SELECT) {
            this.lexer.nextToken();
            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.token() == Token.ALL) {
               queryBlock.setDistionOption(1);
               this.lexer.nextToken();
            }

            if (this.lexer.token() == Token.TOP) {
               SQLServerTop top = this.createExprParser().parseTop();
               queryBlock.setTop(top);
            }

            this.parseSelectList(queryBlock);
         }

         if (this.lexer.token() == Token.INTO) {
            this.lexer.nextToken();
            SQLTableSource into = this.parseTableSource();
            queryBlock.setInto((SQLExprTableSource)into);
            if (this.lexer.token() == Token.ON) {
               this.lexer.nextToken();
               queryBlock.setOn(this.exprParser.expr());
            }
         }

         this.parseFrom(queryBlock);
         this.parseWhere(queryBlock);
         this.parseGroupBy(queryBlock);
         if (this.lexer.identifierEquals(FnvHash.Constants.WINDOW)) {
            this.parseWindow(queryBlock);
         }

         queryBlock.setOrderBy(this.exprParser.parseOrderBy());
         this.parseFetchClause(queryBlock);
         return this.queryRest(queryBlock, acceptUnion);
      }
   }

   protected SQLServerExprParser createExprParser() {
      return new SQLServerExprParser(this.lexer);
   }

   public SQLTableSource parseTableSourceRest(SQLTableSource tableSource) {
      this.parseTablesample(tableSource);
      if (this.lexer.token() == Token.WITH) {
         this.lexer.nextToken();
         this.accept(Token.LPAREN);

         while(true) {
            SQLExpr expr = this.expr();
            SQLExprHint hint = new SQLExprHint(expr);
            hint.setParent(tableSource);
            tableSource.getHints().add(hint);
            if (this.lexer.token() != Token.COMMA) {
               this.accept(Token.RPAREN);
               break;
            }

            this.lexer.nextToken();
         }
      }

      return super.parseTableSourceRest(tableSource);
   }

   private void parseTablesample(SQLTableSource tableSource) {
      if (this.lexer.token() == Token.TABLESAMPLE && tableSource instanceof SQLExprTableSource) {
         Lexer.SavePoint mark = this.lexer.mark();
         this.lexer.nextToken();
         SQLTableSampling sampling = new SQLTableSampling();
         if (this.lexer.identifierEquals(FnvHash.Constants.BERNOULLI)) {
            this.lexer.nextToken();
            sampling.setBernoulli(true);
         } else if (this.lexer.identifierEquals(FnvHash.Constants.SYSTEM)) {
            this.lexer.nextToken();
            sampling.setSystem(true);
         }

         if (this.lexer.token() == Token.LPAREN) {
            this.lexer.nextToken();
            if (this.lexer.identifierEquals(FnvHash.Constants.BUCKET)) {
               this.lexer.nextToken();
               SQLExpr bucket = this.exprParser.primary();
               sampling.setBucket(bucket);
               if (this.lexer.token() == Token.OUT) {
                  this.lexer.nextToken();
                  this.accept(Token.OF);
                  SQLExpr outOf = this.exprParser.primary();
                  sampling.setOutOf(outOf);
               }

               if (this.lexer.token() == Token.ON) {
                  this.lexer.nextToken();
                  SQLExpr on = this.exprParser.expr();
                  sampling.setOn(on);
               }
            }

            if (this.lexer.token() == Token.LITERAL_INT || this.lexer.token() == Token.LITERAL_FLOAT) {
               SQLExpr val = this.exprParser.primary();
               if (this.lexer.identifierEquals(FnvHash.Constants.ROWS)) {
                  this.lexer.nextToken();
                  sampling.setRows(val);
               } else if (this.lexer.token() == Token.RPAREN) {
                  sampling.setRows(val);
               } else {
                  this.accept(Token.PERCENT);
                  sampling.setPercent(val);
               }
            }

            if (this.lexer.token() == Token.IDENTIFIER) {
               String strVal = this.lexer.stringVal();
               char first = strVal.charAt(0);
               char last = strVal.charAt(strVal.length() - 1);
               if (last >= 'a' && last <= 'z') {
                  last = (char)(last - 32);
               }

               boolean match = false;
               if (first == '.' || first >= '0' && first <= '9') {
                  switch (last) {
                     case 'B':
                     case 'G':
                     case 'K':
                     case 'M':
                     case 'P':
                     case 'T':
                        match = true;
                     case 'C':
                     case 'D':
                     case 'E':
                     case 'F':
                     case 'H':
                     case 'I':
                     case 'J':
                     case 'L':
                     case 'N':
                     case 'O':
                     case 'Q':
                     case 'R':
                     case 'S':
                  }
               }

               SQLSizeExpr size = new SQLSizeExpr(strVal.substring(0, strVal.length() - 2), last);
               sampling.setByteLength(size);
               this.lexer.nextToken();
            }

            SQLExprTableSource table = (SQLExprTableSource)tableSource;
            table.setSampling(sampling);
            this.accept(Token.RPAREN);
         } else {
            this.lexer.reset(mark);
         }
      }

   }

   protected void parseWindow(SQLSelectQueryBlock queryBlock) {
      if (this.lexer.identifierEquals(FnvHash.Constants.WINDOW) || this.lexer.token() == Token.WINDOW) {
         this.lexer.nextToken();

         while(true) {
            SQLName name = this.exprParser.name();
            this.accept(Token.AS);
            SQLServerOver over = new SQLServerOver();
            ((SQLServerExprParser)this.exprParser).parseReferenceWindowName(over);
            if (over.getReferenceWindowName() != null) {
               this.lexer.mark();
               this.lexer.nextToken();
               this.lexer.nextToken();
               if (this.lexer.token() == Token.RPAREN) {
                  this.lexer.nextToken();
                  if (this.lexer.token() != Token.COMMA) {
                     break;
                  }

                  this.lexer.nextToken();
                  continue;
               }

               this.lexer.reset();
            }

            this.exprParser.over((SQLOver)over);
            queryBlock.addWindow(new SQLWindow(name, over));
            if (this.lexer.token() != Token.COMMA) {
               break;
            }

            this.lexer.nextToken();
         }

      }
   }

   private void parsePivot(SQLTableSource tableSource) {
      if (tableSource instanceof SQLServerSelectTableSource) {
         SQLServerSelectTableSource sqlServerSelectTableSource = (SQLServerSelectTableSource)tableSource;
         if (this.lexer.token() == Token.PIVOT) {
            this.lexer.nextToken();
            SQLServerSelectPivot pivot = new SQLServerSelectPivot();
            if (this.lexer.identifierEquals("XML")) {
               this.lexer.nextToken();
               pivot.setXml(true);
            }

            this.accept(Token.LPAREN);

            while(true) {
               SQLServerSelectPivot.Item item = new SQLServerSelectPivot.Item();
               item.setExpr((SQLAggregateExpr)this.exprParser.expr());
               item.setAlias(this.as());
               pivot.addItem(item);
               if (this.lexer.token() != Token.COMMA) {
                  this.accept(Token.FOR);
                  if (this.lexer.token() != Token.LPAREN) {
                     pivot.getPivotFor().add(new SQLIdentifierExpr(this.lexer.stringVal()));
                     this.lexer.nextToken();
                  } else {
                     this.lexer.nextToken();

                     while(true) {
                        pivot.getPivotFor().add(new SQLIdentifierExpr(this.lexer.stringVal()));
                        this.lexer.nextToken();
                        if (this.lexer.token() != Token.COMMA) {
                           this.accept(Token.RPAREN);
                           break;
                        }

                        this.lexer.nextToken();
                     }
                  }

                  this.accept(Token.IN);
                  this.accept(Token.LPAREN);
                  if (this.lexer.token() == Token.SELECT) {
                     SQLExpr expr = this.exprParser.expr();
                     item = new SQLServerSelectPivot.Item();
                     item.setExpr(expr);
                     pivot.getPivotIn().add(item);
                  } else {
                     while(true) {
                        item = new SQLServerSelectPivot.Item();
                        item.setExpr(this.exprParser.expr());
                        item.setAlias(this.as());
                        pivot.getPivotIn().add(item);
                        if (this.lexer.token() != Token.COMMA) {
                           break;
                        }

                        this.lexer.nextToken();
                     }
                  }

                  this.accept(Token.RPAREN);
                  this.accept(Token.RPAREN);
                  sqlServerSelectTableSource.setPivot(pivot);
                  break;
               }

               this.lexer.nextToken();
            }
         } else if (this.lexer.token() == Token.UNPIVOT) {
            this.lexer.nextToken();
            SQLServerSelectUnPivot unPivot = new SQLServerSelectUnPivot();
            if (this.lexer.identifierEquals("INCLUDE")) {
               this.lexer.nextToken();
               this.acceptIdentifier("NULLS");
               unPivot.setNullsIncludeType(SQLServerSelectUnPivot.NullsIncludeType.INCLUDE_NULLS);
            } else if (this.lexer.identifierEquals("EXCLUDE")) {
               this.lexer.nextToken();
               this.acceptIdentifier("NULLS");
               unPivot.setNullsIncludeType(SQLServerSelectUnPivot.NullsIncludeType.EXCLUDE_NULLS);
            }

            this.accept(Token.LPAREN);
            if (this.lexer.token() == Token.LPAREN) {
               this.lexer.nextToken();
               this.exprParser.exprList(unPivot.getItems(), unPivot);
               this.accept(Token.RPAREN);
            } else {
               unPivot.addItem(this.exprParser.expr());
            }

            this.accept(Token.FOR);
            if (this.lexer.token() != Token.LPAREN) {
               unPivot.getPivotFor().add(new SQLIdentifierExpr(this.lexer.stringVal()));
               this.lexer.nextToken();
            } else {
               this.lexer.nextToken();

               while(true) {
                  unPivot.getPivotFor().add(new SQLIdentifierExpr(this.lexer.stringVal()));
                  this.lexer.nextToken();
                  if (this.lexer.token() != Token.COMMA) {
                     this.accept(Token.RPAREN);
                     break;
                  }

                  this.lexer.nextToken();
               }
            }

            this.accept(Token.IN);
            this.accept(Token.LPAREN);
            if (this.lexer.token() == Token.LPAREN) {
               throw new ParserException("TODO. " + this.lexer.info());
            }

            if (this.lexer.token() == Token.SELECT) {
               throw new ParserException("TODO. " + this.lexer.info());
            }

            while(true) {
               SQLServerSelectPivot.Item item = new SQLServerSelectPivot.Item();
               item.setExpr(this.exprParser.expr());
               item.setAlias(this.as());
               unPivot.getPivotIn().add(item);
               if (this.lexer.token() != Token.COMMA) {
                  this.accept(Token.RPAREN);
                  this.accept(Token.RPAREN);
                  sqlServerSelectTableSource.setPivot(unPivot);
                  break;
               }

               this.lexer.nextToken();
            }
         }

      }
   }

   public SQLTableSource parseTableSource() {
      if (this.lexer.token() == Token.LPAREN) {
         this.lexer.nextToken();
         SQLTableSource tableSource;
         if (this.lexer.token() != Token.SELECT && this.lexer.token() != Token.WITH && this.lexer.token() != Token.SEL) {
            if (this.lexer.token() == Token.LPAREN) {
               tableSource = this.parseTableSource();
               this.accept(Token.RPAREN);
            } else {
               tableSource = this.parseTableSource();
               this.accept(Token.RPAREN);
            }
         } else {
            SQLSelect select = this.select();
            this.accept(Token.RPAREN);
            if (select.getQuery() instanceof SQLSelectQueryBlock) {
               ((SQLSelectQueryBlock)select.getQuery()).setParenthesized(true);
            }

            SQLSelectQuery query = this.queryRest(select.getQuery(), true);
            if (query instanceof SQLUnionQuery) {
               tableSource = new SQLServerUnionQueryTableSource((SQLUnionQuery)query);
            } else {
               tableSource = new SQLServerSelectSubqueryTableSource(select);
            }
         }

         this.parseAlias(tableSource);
         this.parsePivot(tableSource);
         this.parsePivotAlias(tableSource);
         return this.parseTableSourceRest(tableSource);
      } else if (this.lexer.token() != Token.VALUES) {
         if (this.lexer.token() == Token.SELECT) {
            throw new ParserException("TODO " + this.lexer.info());
         } else {
            SQLServerSelectTableReference tableReference = new SQLServerSelectTableReference();
            this.parseTableSourceQueryTableExpr(tableReference);
            this.parseAlias(tableReference);
            this.parsePivot(tableReference);
            if (tableReference.getPivot() != null) {
               this.parsePivotAlias(tableReference);
            }

            SQLTableSource tableSrc = this.parseTableSourceRest(tableReference);
            if (this.lexer.hasComment() && this.lexer.isKeepComments()) {
               tableSrc.addAfterComment(this.lexer.readAndResetComments());
            }

            return tableSrc;
         }
      } else {
         this.lexer.nextToken();
         SQLServerValuesTableSource tableSource = new SQLServerValuesTableSource();

         while(true) {
            this.accept(Token.LPAREN);
            SQLListExpr listExpr = new SQLListExpr();
            this.exprParser.exprList(listExpr.getItems(), listExpr);
            this.accept(Token.RPAREN);
            listExpr.setParent(tableSource);
            tableSource.getValues().add(listExpr);
            if (this.lexer.token() != Token.COMMA) {
               if (this.lexer.token() == Token.RPAREN) {
                  return tableSource;
               } else {
                  String alias = this.tableAlias();
                  if (alias != null) {
                     tableSource.setAlias(alias);
                  }

                  this.accept(Token.LPAREN);
                  this.exprParser.names(tableSource.getColumns(), tableSource);
                  this.accept(Token.RPAREN);
                  return tableSource;
               }
            }

            this.lexer.nextToken();
         }
      }
   }

   private void parseAlias(SQLTableSource tableSource) {
      if (this.lexer.token() == Token.AS) {
         this.lexer.nextToken();
         String alias = this.tableAlias(true);
         tableSource.setAlias(alias);
         this.parseAliasColums(tableSource);
      } else if (this.lexer.token() != Token.IDENTIFIER || this.lexer.hash_lower() != FnvHash.Constants.STRAIGHT_JOIN && this.lexer.hash_lower() != FnvHash.Constants.CROSS) {
         String alias = this.tableAlias(false);
         if (alias != null) {
            tableSource.setAlias(alias);
            this.parseAliasColums(tableSource);
         }
      }

   }

   private void parseAliasColums(SQLTableSource tableSource) {
      if (tableSource instanceof SQLValuesTableSource && ((SQLValuesTableSource)tableSource).getColumns().size() == 0) {
         SQLValuesTableSource values = (SQLValuesTableSource)tableSource;
         this.accept(Token.LPAREN);
         this.exprParser.names(values.getColumns(), values);
         this.accept(Token.RPAREN);
      } else if (tableSource instanceof SQLSubqueryTableSource) {
         SQLSubqueryTableSource values = (SQLSubqueryTableSource)tableSource;
         if (this.lexer.token() == Token.LPAREN) {
            this.lexer.nextToken();
            this.exprParser.names(values.getColumns(), values);
            this.accept(Token.RPAREN);
         }
      } else if (tableSource instanceof SQLServerUnionQueryTableSource) {
         SQLServerUnionQueryTableSource values = (SQLServerUnionQueryTableSource)tableSource;
         if (this.lexer.token() == Token.LPAREN) {
            this.lexer.nextToken();
            this.exprParser.names(values.getColumns(), values);
            this.accept(Token.RPAREN);
         }
      }

   }

   private void parsePivotAlias(SQLTableSource tableSource) {
      if (tableSource instanceof SQLServerSelectTableSource) {
         SQLServerSelectTableSource sqlServerSelectTableSource = (SQLServerSelectTableSource)tableSource;
         if (sqlServerSelectTableSource.getPivot() != null) {
            if (this.lexer.token() == Token.AS) {
               this.lexer.nextToken();
               String alias = this.tableAlias(true);
               sqlServerSelectTableSource.setPivotAlias(alias);
            } else if (this.lexer.token() != Token.IDENTIFIER || this.lexer.hash_lower() != FnvHash.Constants.STRAIGHT_JOIN && this.lexer.hash_lower() != FnvHash.Constants.CROSS) {
               String alias = this.tableAlias(false);
               if (alias != null) {
                  sqlServerSelectTableSource.setPivotAlias(alias);
               }
            }

         }
      }
   }
}
