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

import com.chenyang.druid.DbType;
import com.chenyang.druid.sql.ast.SQLArrayDataType;
import com.chenyang.druid.sql.ast.SQLDataType;
import com.chenyang.druid.sql.ast.SQLExpr;
import com.chenyang.druid.sql.ast.SQLName;
import com.chenyang.druid.sql.ast.SQLOver;
import com.chenyang.druid.sql.ast.expr.SQLArrayExpr;
import com.chenyang.druid.sql.ast.expr.SQLBinaryExpr;
import com.chenyang.druid.sql.ast.expr.SQLCharExpr;
import com.chenyang.druid.sql.ast.expr.SQLIdentifierExpr;
import com.chenyang.druid.sql.ast.expr.SQLIntervalExpr;
import com.chenyang.druid.sql.ast.expr.SQLIntervalUnit;
import com.chenyang.druid.sql.ast.expr.SQLListExpr;
import com.chenyang.druid.sql.ast.expr.SQLQueryExpr;
import com.chenyang.druid.sql.ast.expr.SQLTimestampExpr;
import com.chenyang.druid.sql.ast.expr.SQLUnaryExpr;
import com.chenyang.druid.sql.ast.expr.SQLUnaryOperator;
import com.chenyang.druid.sql.ast.expr.SQLValuesExpr;
import com.chenyang.druid.sql.ast.expr.SQLVariantRefExpr;
import com.chenyang.druid.sql.dialect.postgresql.ast.expr.PGBoxExpr;
import com.chenyang.druid.sql.dialect.postgresql.ast.expr.PGCidrExpr;
import com.chenyang.druid.sql.dialect.postgresql.ast.expr.PGCircleExpr;
import com.chenyang.druid.sql.dialect.postgresql.ast.expr.PGDateField;
import com.chenyang.druid.sql.dialect.postgresql.ast.expr.PGExtractExpr;
import com.chenyang.druid.sql.dialect.postgresql.ast.expr.PGInetExpr;
import com.chenyang.druid.sql.dialect.postgresql.ast.expr.PGLineSegmentsExpr;
import com.chenyang.druid.sql.dialect.postgresql.ast.expr.PGMacAddrExpr;
import com.chenyang.druid.sql.dialect.postgresql.ast.expr.PGPointExpr;
import com.chenyang.druid.sql.dialect.postgresql.ast.expr.PGPolygonExpr;
import com.chenyang.druid.sql.dialect.postgresql.ast.expr.PGTypeCastExpr;
import com.chenyang.druid.sql.parser.Lexer;
import com.chenyang.druid.sql.parser.SQLExprParser;
import com.chenyang.druid.sql.parser.SQLParserFeature;
import com.chenyang.druid.sql.parser.Token;
import com.chenyang.druid.util.FnvHash;
import java.util.Arrays;

public class PGExprParser extends SQLExprParser {
   public static final String[] AGGREGATE_FUNCTIONS;
   public static final long[] AGGREGATE_FUNCTIONS_CODES;

   public PGExprParser(String sql) {
      this((Lexer)(new PGLexer(sql, new SQLParserFeature[0])));
      this.lexer.nextToken();
      this.dbType = DbType.postgresql;
   }

   public PGExprParser(String sql, SQLParserFeature... features) {
      this((Lexer)(new PGLexer(sql, new SQLParserFeature[0])));
      this.lexer.nextToken();
      this.dbType = DbType.postgresql;
   }

   public PGExprParser(Lexer lexer) {
      super(lexer);
      this.aggregateFunctions = AGGREGATE_FUNCTIONS;
      this.aggregateFunctionHashCodes = AGGREGATE_FUNCTIONS_CODES;
      this.dbType = DbType.postgresql;
   }

   public SQLDataType parseDataType() {
      if (this.lexer.token() == Token.TYPE) {
         this.lexer.nextToken();
      }

      return super.parseDataType();
   }

   protected SQLDataType parseDataTypeRest(SQLDataType dataType) {
      dataType = super.parseDataTypeRest(dataType);
      if (this.lexer.token() == Token.LBRACKET) {
         this.lexer.nextToken();
         this.accept(Token.RBRACKET);
         dataType = new SQLArrayDataType(dataType);
      }

      return dataType;
   }

   public PGSelectParser createSelectParser() {
      return new PGSelectParser(this);
   }

   public SQLExpr primary() {
      if (this.lexer.token() == Token.ARRAY) {
         String ident = this.lexer.stringVal();
         this.lexer.nextToken();
         if (this.lexer.token() == Token.LPAREN) {
            SQLIdentifierExpr array = new SQLIdentifierExpr(ident);
            return this.methodRest(array, true);
         } else {
            SQLArrayExpr array = new SQLArrayExpr();
            array.setExpr(new SQLIdentifierExpr(ident));
            this.accept(Token.LBRACKET);
            this.exprList(array.getValues(), array);
            this.accept(Token.RBRACKET);
            return this.primaryRest(array);
         }
      } else if (this.lexer.token() == Token.POUND) {
         this.lexer.nextToken();
         if (this.lexer.token() == Token.LBRACE) {
            this.lexer.nextToken();
            String varName = this.lexer.stringVal();
            this.lexer.nextToken();
            this.accept(Token.RBRACE);
            SQLVariantRefExpr expr = new SQLVariantRefExpr("#{" + varName + "}");
            return this.primaryRest(expr);
         } else {
            SQLExpr value = this.primary();
            SQLUnaryExpr expr = new SQLUnaryExpr(SQLUnaryOperator.Pound, value);
            return this.primaryRest(expr);
         }
      } else if (this.lexer.token() != Token.VALUES) {
         if (this.lexer.token() == Token.WITH) {
            SQLQueryExpr queryExpr = new SQLQueryExpr(this.createSelectParser().select());
            return queryExpr;
         } else {
            return super.primary();
         }
      } else {
         this.lexer.nextToken();
         SQLValuesExpr values = new SQLValuesExpr();

         while(true) {
            this.accept(Token.LPAREN);
            SQLListExpr listExpr = new SQLListExpr();
            this.exprList(listExpr.getItems(), listExpr);
            this.accept(Token.RPAREN);
            listExpr.setParent(values);
            values.getValues().add(listExpr);
            if (this.lexer.token() != Token.COMMA) {
               return values;
            }

            this.lexer.nextToken();
         }
      }
   }

   protected SQLExpr parseInterval() {
      this.accept(Token.INTERVAL);
      SQLIntervalExpr intervalExpr = new SQLIntervalExpr();
      if (this.lexer.token() != Token.LITERAL_CHARS) {
         return new SQLIdentifierExpr("INTERVAL");
      } else {
         intervalExpr.setValue(new SQLCharExpr(this.lexer.stringVal()));
         this.lexer.nextToken();
         if (this.lexer.identifierEquals(FnvHash.Constants.DAY)) {
            this.lexer.nextToken();
            intervalExpr.setUnit(SQLIntervalUnit.DAY);
         } else if (this.lexer.identifierEquals(FnvHash.Constants.MONTH)) {
            this.lexer.nextToken();
            intervalExpr.setUnit(SQLIntervalUnit.MONTH);
         } else if (this.lexer.identifierEquals(FnvHash.Constants.YEAR)) {
            this.lexer.nextToken();
            intervalExpr.setUnit(SQLIntervalUnit.YEAR);
         } else if (this.lexer.identifierEquals(FnvHash.Constants.HOUR)) {
            this.lexer.nextToken();
            intervalExpr.setUnit(SQLIntervalUnit.HOUR);
         } else if (this.lexer.identifierEquals(FnvHash.Constants.MINUTE)) {
            this.lexer.nextToken();
            intervalExpr.setUnit(SQLIntervalUnit.MINUTE);
         } else if (this.lexer.identifierEquals(FnvHash.Constants.SECOND)) {
            this.lexer.nextToken();
            intervalExpr.setUnit(SQLIntervalUnit.SECOND);
         }

         return intervalExpr;
      }
   }

   public SQLExpr primaryRest(SQLExpr expr) {
      if (this.lexer.token() == Token.COLONCOLON) {
         this.lexer.nextToken();
         SQLDataType dataType = this.parseDataType();
         PGTypeCastExpr castExpr = new PGTypeCastExpr();
         castExpr.setExpr(expr);
         castExpr.setDataType(dataType);
         return this.primaryRest(castExpr);
      } else if (this.lexer.token() == Token.LBRACKET) {
         SQLArrayExpr array = new SQLArrayExpr();
         array.setExpr(expr);
         this.lexer.nextToken();
         this.exprList(array.getValues(), array);
         this.accept(Token.RBRACKET);
         return this.primaryRest(array);
      } else {
         if (expr.getClass() == SQLIdentifierExpr.class) {
            SQLIdentifierExpr identifierExpr = (SQLIdentifierExpr)expr;
            String ident = identifierExpr.getName();
            long hash = identifierExpr.nameHashCode64();
            if (this.lexer.token() == Token.COMMA || this.lexer.token() == Token.RPAREN) {
               return super.primaryRest(expr);
            }

            if (FnvHash.Constants.TIMESTAMP == hash) {
               if (this.lexer.token() != Token.LITERAL_ALIAS && this.lexer.token() != Token.LITERAL_CHARS && this.lexer.token() != Token.WITH) {
                  return super.primaryRest(new SQLIdentifierExpr(ident));
               }

               SQLTimestampExpr timestamp = new SQLTimestampExpr();
               if (this.lexer.token() == Token.WITH) {
                  this.lexer.nextToken();
                  this.acceptIdentifier("TIME");
                  this.acceptIdentifier("ZONE");
                  timestamp.setWithTimeZone(true);
               }

               String literal = this.lexer.stringVal();
               timestamp.setLiteral(literal);
               this.accept(Token.LITERAL_CHARS);
               if (this.lexer.identifierEquals("AT")) {
                  this.lexer.nextToken();
                  this.acceptIdentifier("TIME");
                  this.acceptIdentifier("ZONE");
                  String timezone = this.lexer.stringVal();
                  timestamp.setTimeZone(timezone);
                  this.accept(Token.LITERAL_CHARS);
               }

               return this.primaryRest(timestamp);
            }

            if (FnvHash.Constants.TIMESTAMPTZ == hash) {
               if (this.lexer.token() != Token.LITERAL_ALIAS && this.lexer.token() != Token.LITERAL_CHARS && this.lexer.token() != Token.WITH) {
                  return super.primaryRest(new SQLIdentifierExpr(ident));
               }

               SQLTimestampExpr timestamp = new SQLTimestampExpr();
               timestamp.setWithTimeZone(true);
               String literal = this.lexer.stringVal();
               timestamp.setLiteral(literal);
               this.accept(Token.LITERAL_CHARS);
               if (this.lexer.identifierEquals("AT")) {
                  this.lexer.nextToken();
                  this.acceptIdentifier("TIME");
                  this.acceptIdentifier("ZONE");
                  String timezone = this.lexer.stringVal();
                  timestamp.setTimeZone(timezone);
                  this.accept(Token.LITERAL_CHARS);
               }

               return this.primaryRest(timestamp);
            }

            if (FnvHash.Constants.EXTRACT == hash) {
               this.accept(Token.LPAREN);
               PGExtractExpr extract = new PGExtractExpr();
               String fieldName = this.lexer.stringVal();
               PGDateField field = PGDateField.valueOf(fieldName.toUpperCase());
               this.lexer.nextToken();
               extract.setField(field);
               this.accept(Token.FROM);
               SQLExpr source = this.expr();
               extract.setSource(source);
               this.accept(Token.RPAREN);
               return this.primaryRest(extract);
            }

            if (FnvHash.Constants.POINT == hash) {
               switch (this.lexer.token()) {
                  case DOT:
                  case EQ:
                  case LTGT:
                  case GT:
                  case GTEQ:
                  case LT:
                  case LTEQ:
                  case SUB:
                  case PLUS:
                  case SUBGT:
                     break;
                  default:
                     SQLExpr value = this.primary();
                     PGPointExpr point = new PGPointExpr();
                     point.setValue(value);
                     return this.primaryRest(point);
               }
            } else {
               if (FnvHash.Constants.BOX == hash) {
                  SQLExpr value = this.primary();
                  PGBoxExpr box = new PGBoxExpr();
                  box.setValue(value);
                  return this.primaryRest(box);
               }

               if (FnvHash.Constants.MACADDR == hash) {
                  SQLExpr value = this.primary();
                  PGMacAddrExpr macaddr = new PGMacAddrExpr();
                  macaddr.setValue(value);
                  return this.primaryRest(macaddr);
               }

               if (FnvHash.Constants.INET == hash) {
                  SQLExpr value = this.primary();
                  PGInetExpr inet = new PGInetExpr();
                  inet.setValue(value);
                  return this.primaryRest(inet);
               }

               if (FnvHash.Constants.CIDR == hash) {
                  SQLExpr value = this.primary();
                  PGCidrExpr cidr = new PGCidrExpr();
                  cidr.setValue(value);
                  return this.primaryRest(cidr);
               }

               if (FnvHash.Constants.POLYGON == hash) {
                  SQLExpr value = this.primary();
                  PGPolygonExpr polygon = new PGPolygonExpr();
                  polygon.setValue(value);
                  return this.primaryRest(polygon);
               }

               if (FnvHash.Constants.CIRCLE == hash) {
                  SQLExpr value = this.primary();
                  PGCircleExpr circle = new PGCircleExpr();
                  circle.setValue(value);
                  return this.primaryRest(circle);
               }

               if (FnvHash.Constants.LSEG == hash) {
                  SQLExpr value = this.primary();
                  PGLineSegmentsExpr lseg = new PGLineSegmentsExpr();
                  lseg.setValue(value);
                  return this.primaryRest(lseg);
               }

               if (ident.equalsIgnoreCase("b") && this.lexer.token() == Token.LITERAL_CHARS) {
                  String charValue = this.lexer.stringVal();
                  this.lexer.nextToken();
                  expr = new SQLBinaryExpr(charValue);
                  return this.primaryRest(expr);
               }
            }
         }

         return super.primaryRest(expr);
      }
   }

   protected String alias() {
      String alias = super.alias();
      if (alias != null) {
         return alias;
      } else {
         switch (this.lexer.token()) {
            case INTERSECT:
               alias = this.lexer.stringVal();
               this.lexer.nextToken();
               return alias;
            default:
               return alias;
         }
      }
   }

   public void over(SQLOver over) {
      this.lexer.nextToken();
      if (this.lexer.token() == Token.IDENTIFIER || this.lexer.token() == Token.IDENTITY) {
         SQLExpr existingExpr = this.expr();
         over.setName((SQLName)existingExpr);
      }

      if (this.lexer.token() == Token.PARTITION || this.lexer.identifierEquals("PARTITION")) {
         this.lexer.nextToken();
         this.accept(Token.BY);
         if (this.lexer.token() == Token.LPAREN) {
            this.lexer.nextToken();
            this.exprList(over.getPartitionBy(), over);
            this.accept(Token.RPAREN);
            if (over.getPartitionBy().size() == 1 && this.lexer.token() == Token.COMMA) {
               this.lexer.nextToken();
               this.exprList(over.getPartitionBy(), over);
            }
         } else {
            this.exprList(over.getPartitionBy(), over);
         }
      }

      over.setOrderBy(this.parseOrderBy());
      over.setDistributeBy(this.parseDistributeBy());
      over.setSortBy(this.parseSortBy());
      SQLOver.WindowingType windowingType = null;
      if (!this.lexer.identifierEquals(FnvHash.Constants.ROWS) && this.lexer.token() != Token.ROWS) {
         if (this.lexer.identifierEquals(FnvHash.Constants.RANGE)) {
            windowingType = SQLOver.WindowingType.RANGE;
         }
      } else {
         windowingType = SQLOver.WindowingType.ROWS;
      }

      if (windowingType != null) {
         over.setWindowingType(windowingType);
         this.lexer.nextToken();
         if (this.lexer.token() == Token.BETWEEN) {
            this.lexer.nextToken();
            if (this.lexer.token() != Token.LITERAL_INT && this.lexer.token() != Token.LITERAL_FLOAT && this.lexer.token() != Token.LITERAL_CHARS) {
               if (this.lexer.token() == Token.IDENTIFIER) {
                  long hash = this.lexer.hash_lower();
                  if (hash != FnvHash.Constants.PRECEDING && hash != FnvHash.Constants.FOLLOWING && hash != FnvHash.Constants.CURRENT && hash != FnvHash.Constants.UNBOUNDED) {
                     SQLExpr betweenBegin = this.primary();
                     over.setWindowingBetweenBegin(betweenBegin);
                  }
               }
            } else {
               SQLExpr betweenBegin = this.additive();
               over.setWindowingBetweenBegin(betweenBegin);
            }

            SQLOver.WindowingBound beginBound = this.parseWindowingBound();
            if (beginBound != null) {
               over.setWindowingBetweenBeginBound(beginBound);
            }

            this.accept(Token.AND);
            if (this.lexer.token() != Token.LITERAL_INT && this.lexer.token() != Token.LITERAL_FLOAT && this.lexer.token() != Token.LITERAL_CHARS) {
               if (this.lexer.token() == Token.IDENTIFIER) {
                  long hash = this.lexer.hash_lower();
                  if (hash != FnvHash.Constants.PRECEDING && hash != FnvHash.Constants.FOLLOWING && hash != FnvHash.Constants.CURRENT && hash != FnvHash.Constants.UNBOUNDED) {
                     SQLExpr betweenBegin = this.additive();
                     over.setWindowingBetweenEnd(betweenBegin);
                  }
               }
            } else {
               SQLExpr betweenEnd = this.additive();
               over.setWindowingBetweenEnd(betweenEnd);
            }

            SQLOver.WindowingBound endBound = this.parseWindowingBound();
            if (endBound != null) {
               over.setWindowingBetweenEndBound(endBound);
            }
         } else {
            if (this.lexer.token() != Token.LITERAL_INT && this.lexer.token() != Token.LITERAL_FLOAT && this.lexer.token() != Token.LITERAL_CHARS) {
               if (this.lexer.token() == Token.IDENTIFIER) {
                  long hash = this.lexer.hash_lower();
                  if (hash != FnvHash.Constants.PRECEDING && hash != FnvHash.Constants.FOLLOWING && hash != FnvHash.Constants.CURRENT && hash != FnvHash.Constants.UNBOUNDED) {
                     SQLExpr betweenBegin = this.additive();
                     over.setWindowingBetweenBegin(betweenBegin);
                  }
               }
            } else {
               SQLExpr betweenBegin = this.additive();
               over.setWindowingBetweenBegin(betweenBegin);
            }

            SQLOver.WindowingBound beginBound = this.parseWindowingBound();
            if (beginBound != null) {
               over.setWindowingBetweenBeginBound(beginBound);
            }
         }
      }

      this.accept(Token.RPAREN);
   }

   static {
      String[] strings = new String[]{"AVG", "COUNT", "MAX", "MIN", "STDDEV", "SUM", "ROW_NUMBER", "PERCENTILE_CONT", "PERCENTILE_DISC", "RANK", "DENSE_RANK", "PERCENT_RANK", "CUME_DIST"};
      AGGREGATE_FUNCTIONS_CODES = FnvHash.fnv1a_64_lower(strings, true);
      AGGREGATE_FUNCTIONS = new String[AGGREGATE_FUNCTIONS_CODES.length];

      for(String str : strings) {
         long hash = FnvHash.fnv1a_64_lower(str);
         int index = Arrays.binarySearch(AGGREGATE_FUNCTIONS_CODES, hash);
         AGGREGATE_FUNCTIONS[index] = str;
      }

   }
}
