/*
 * Copyright 1999-2017 Alibaba Group Holding Ltd.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.alibaba.druid.sql.dialect.presto.parser;

import com.alibaba.druid.DbType;
import com.alibaba.druid.sql.ast.SQLExpr;
import com.alibaba.druid.sql.ast.statement.SQLColumnDefinition;
import com.alibaba.druid.sql.dialect.presto.ast.PrestoColumnWith;
import com.alibaba.druid.sql.dialect.presto.ast.PrestoDateTimeExpr;
import com.alibaba.druid.sql.parser.Lexer;
import com.alibaba.druid.sql.parser.SQLExprParser;
import com.alibaba.druid.sql.parser.SQLParserFeature;
import com.alibaba.druid.sql.parser.Token;
import com.alibaba.druid.util.FnvHash;

import java.util.Arrays;

import static com.alibaba.druid.util.FnvHash.fnv1a_64_lower;

/**
 * Created by wenshao on 16/9/13.
 */
public class PrestoExprParser extends SQLExprParser {
    private static final String[] AGGREGATE_FUNCTIONS;
    private static final long[] AGGREGATE_FUNCTIONS_CODES;

    static {
        String[] strings = {
                "ANY_VALUE", "ARBITRARY", "ARRAY_AGG", "AVG", "BOOL_AND", "BOOL_OR", "CHECKSUM", "COUNT", "COUNT_IF",
                "EVERY", "GEOMETRIC_MEAN", "MAX_BY", "MIN_BY", "MAX", "MIN", "REDUCE_AGG", "SET_AGG", "SET_UNION",
                "SUM", "BITWISE_AND_AGG", "BITWISE_OR_AGG", "BITWISE_XOR_AGG", "HISTOGRAM", "MAP_AGG", "MAP_UNION",
                "MAP_UNION_SUM", "MULTIMAP_AGG", "APPROX_DISTINCT", "APPROX_PERCENTILE", "NUMERIC_HISTOGRAM", "CORR",
                "COVAR_POP", "COVAR_SAMP", "ENTROPY", "KURTOSIS", "REGR_INTERCEPT", "REGR_SLOPE", "REGR_AVGX",
                "REGR_AVGY", "REGR_COUNT", "REGR_R2", "REGR_SXX", "REGR_SXY", "REGR_SYY", "SKEWNESS", "STDDEV",
                "STDDEV_POP", "STDDEV_SAMP", "VARIANCE", "VAR_POP", "VAR_SAMP", "CLASSIFICATION_MISS_RATE",
                "CLASSIFICATION_FALL_OUT", "CLASSIFICATION_PRECISION", "CLASSIFICATION_RECALL",
                "CLASSIFICATION_THRESHOLDS", "DIFFERENTIAL_ENTROPY", "APPROX_MOST_FREQUENT", "RESERVOIR_SAMPLE",
                "NOISY_COUNT_GAUSSIAN", "NOISY_COUNT_IF_GAUSSIAN", "NOISY_SUM_GAUSSIAN", "NOISY_AVG_GAUSSIAN",
                "NOISY_APPROX_SET_SFM", "NOISY_APPROX_SET_SFM_FROM_INDEX_AND_ZEROS", "NOISY_APPROX_DISTINCT_SFM",
                "NOISY_EMPTY_APPROX_SET_SFM", "CARDINALITY", "MERGE", "MERGE_SFM", "CUME_DIST", "DENSE_RANK", "NTILE",
                "PERCENT_RANK", "RANK", "ROW_NUMBER", "FIRST_VALUE", "LAST_VALUE", "NTH_VALUE", "LEAD", "LAG"
        };
        AGGREGATE_FUNCTIONS_CODES = fnv1a_64_lower(strings, true);
        AGGREGATE_FUNCTIONS = new String[AGGREGATE_FUNCTIONS_CODES.length];
        for (String str : strings) {
            long hash = fnv1a_64_lower(str);
            int index = Arrays.binarySearch(AGGREGATE_FUNCTIONS_CODES, hash);
            AGGREGATE_FUNCTIONS[index] = str;
        }
    }

    public PrestoExprParser(String sql, SQLParserFeature... features) {
        this(new PrestoLexer(sql, features));
        this.lexer.nextToken();
    }

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

    @Override
    protected SQLColumnDefinition parseColumnSpecific(SQLColumnDefinition column) {
        if (lexer.token() == Token.WITH) {
            lexer.nextToken();
            PrestoColumnWith prestoColumnWith = new PrestoColumnWith();
            accept(Token.LPAREN);
            parseAssignItems(prestoColumnWith.getProperties(), prestoColumnWith, false);
            accept(Token.RPAREN);
            column.addConstraint(prestoColumnWith);
            return parseColumnRest(column);
        }
        return column;
    }

    @Override
    public SQLExpr primaryRest(SQLExpr expr) {
        Lexer.SavePoint savePoint = lexer.markOut();
        if (lexer.identifierEquals(FnvHash.Constants.AT)) {
            lexer.nextToken();
            if (lexer.nextIfIdentifier(FnvHash.Constants.TIME)) {
                acceptIdentifier("ZONE");
                SQLExpr timeZone = primary();
                expr = new PrestoDateTimeExpr(expr, timeZone);
            } else {
                lexer.reset(savePoint);
            }
        }
        return super.primaryRest(expr);
    }
}
