/*
 * 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.hive.parser;

import com.alibaba.druid.sql.ast.SQLExpr;
import com.alibaba.druid.sql.ast.SQLName;
import com.alibaba.druid.sql.ast.expr.SQLListExpr;
import com.alibaba.druid.sql.ast.statement.*;
import com.alibaba.druid.sql.dialect.hive.ast.HiveInputOutputFormat;
import com.alibaba.druid.sql.dialect.hive.stmt.HiveCreateTableStatement;
import com.alibaba.druid.sql.parser.*;
import com.alibaba.druid.util.FnvHash;

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

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

    protected void createTableBefore(SQLCreateTableStatement stmt) {
        if (lexer.nextIfIdentifier(FnvHash.Constants.EXTERNAL)) {
            stmt.setExternal(true);
        }

        if (lexer.nextIfIdentifier(FnvHash.Constants.TEMPORARY)) {
            stmt.setTemporary(true);
        }

        if (lexer.nextIfIdentifier("TRANSACTIONAL")) {
            stmt.config(SQLCreateTableStatement.Feature.Transactional);
        }
    }

    protected void createTableBody(SQLCreateTableStatement stmt) {
        if (lexer.token() == Token.LPAREN) {
            lexer.nextToken();

            for (; ; ) {
                Token token = lexer.token();
                if (token == Token.IDENTIFIER //
                        || token == Token.LITERAL_ALIAS) {
                    SQLColumnDefinition column = this.exprParser.parseColumn();
                    stmt.getTableElementList().add(column);
                } else if (token == Token.PRIMARY //
                        || token == Token.UNIQUE //
                        || token == Token.CHECK //
                        || token == Token.CONSTRAINT
                        || token == Token.FOREIGN) {
                    SQLConstraint constraint = this.exprParser.parseConstraint();
                    constraint.setParent(stmt);
                    stmt.getTableElementList().add((SQLTableElement) constraint);
                } else if (token == Token.TABLESPACE) {
                    throw new ParserException("TODO " + lexer.info());
                } else {
                    SQLColumnDefinition column = this.exprParser.parseColumn();
                    stmt.getTableElementList().add(column);
                }

                if (lexer.token() == Token.COMMA) {
                    lexer.nextToken();

                    if (lexer.token() == Token.RPAREN) { // compatible for sql server
                        break;
                    }
                    continue;
                }

                break;
            }

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

    protected void createTableQuery(SQLCreateTableStatement stmt) {
        if (lexer.token() == Token.SELECT || lexer.token() == Token.AS) {
            if (lexer.token() == Token.AS) {
                lexer.nextToken();
            }
            SQLSelect select = this.createSQLSelectParser().select();
            stmt.setSelect(select);
        }
    }

    protected void parseCreateTableWithSerderPropertie(HiveCreateTableStatement stmt) {
        if (lexer.token() == Token.WITH) {
            lexer.nextToken();
            acceptIdentifier("SERDEPROPERTIES");

            accept(Token.LPAREN);

            for (; ; ) {
                String key = lexer.stringVal();
                lexer.nextToken();
                accept(Token.EQ);
                SQLExpr value = this.exprParser.primary();
                stmt.getSerdeProperties().put(key, value);
                if (lexer.token() == Token.COMMA) {
                    lexer.nextToken();
                    continue;
                }
                break;
            }

            accept(Token.RPAREN);
        }
    }

    protected void parseCreateTableRest(SQLCreateTableStatement createTable) {
        HiveCreateTableStatement stmt = (HiveCreateTableStatement) createTable;
        if (lexer.nextIfIdentifier(FnvHash.Constants.ENGINE)) {
            // skip engine=xxx
            accept(Token.EQ);
            lexer.nextToken();
        }

        if (lexer.nextIfIdentifier(FnvHash.Constants.CHARSET)) {
            // skip charset = xxx
            accept(Token.EQ);
            lexer.nextToken();
        }

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

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

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

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

        if (lexer.nextIf(Token.PARTITIONED)) {
            accept(Token.BY);
            accept(Token.LPAREN);

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

                SQLColumnDefinition column = this.exprParser.parseColumn();
                stmt.addPartitionColumn(column);

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

                if (lexer.token() != Token.COMMA) {
                    break;
                } else {
                    lexer.nextToken();
                    if (lexer.isKeepComments() && lexer.hasComment()) {
                        column.addAfterComment(lexer.readAndResetComments());
                    }
                }
            }

            accept(Token.RPAREN);
        }

        if (lexer.nextIfIdentifier(FnvHash.Constants.CLUSTERED)) {
            accept(Token.BY);
            accept(Token.LPAREN);
            for (; ; ) {
                SQLSelectOrderByItem item = this.exprParser.parseSelectOrderByItem();
                stmt.addClusteredByItem(item);
                if (lexer.token() == Token.COMMA) {
                    lexer.nextToken();
                    continue;
                }
                break;
            }
            accept(Token.RPAREN);
        }

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

        if (stmt.getClusteredBy().size() > 0 || stmt.getSortedBy().size() > 0) {
            accept(Token.INTO);
            if (lexer.token() == Token.LITERAL_INT) {
                stmt.setBuckets(lexer.integerValue().intValue());
                lexer.nextToken();
            } else {
                throw new ParserException("into buckets must be integer. " + lexer.info());
            }
            acceptIdentifier("BUCKETS");
        }

        if (lexer.nextIfIdentifier(FnvHash.Constants.SKEWED)) {
            accept(Token.BY);
            accept(Token.LPAREN);
            this.exprParser.exprList(stmt.getSkewedBy(), stmt);
            accept(Token.RPAREN);
            accept(Token.ON);
            accept(Token.LPAREN);
            for (; ; ) {
                if (lexer.token() == Token.LPAREN) {
                    SQLListExpr list = new SQLListExpr();
                    lexer.nextToken();
                    this.exprParser.exprList(list.getItems(), list);
                    accept(Token.RPAREN);
                    stmt.addSkewedByOn(list);
                } else {
                    SQLExpr expr = this.exprParser.expr();
                    stmt.addSkewedByOn(expr);
                }
                if (lexer.token() == Token.COMMA) {
                    lexer.nextToken();
                    continue;
                }
                break;
            }
            accept(Token.RPAREN);
            if (lexer.nextIfIdentifier(FnvHash.Constants.STORED)) {
                accept(Token.AS);
                acceptIdentifier("DIRECTORIES");
                stmt.setSkewedByStoreAsDirectories(true);
            }
        }

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

        if (Token.LBRACKET.equals(lexer.token())) {
            stmt.setLbracketUse(true);
            lexer.nextToken();
        }
        if (lexer.identifierEquals(FnvHash.Constants.STORED)) {
            lexer.nextToken();
            if (lexer.token() == Token.BY) {
                accept(Token.BY);
                SQLName name = this.exprParser.name();
                stmt.setStoredBy(name);

                parseCreateTableWithSerderPropertie(stmt);
            } else {
                accept(Token.AS);

                if (lexer.identifierEquals(FnvHash.Constants.INPUTFORMAT)) {
                    HiveInputOutputFormat format = new HiveInputOutputFormat();
                    lexer.nextToken();
                    format.setInput(this.exprParser.primary());

                    if (lexer.identifierEquals(FnvHash.Constants.OUTPUTFORMAT)) {
                        lexer.nextToken();
                        format.setOutput(this.exprParser.primary());
                    }
                    stmt.setStoredAs(format);
                } else {
                    SQLName name = this.exprParser.name();
                    stmt.setStoredAs(name);
                }
            }
        }

        if (Token.RBRACKET.equals(lexer.token())) {
            stmt.setRbracketUse(true);
            lexer.nextToken();
        }
        if (lexer.identifierEquals(FnvHash.Constants.LOCATION)) {
            lexer.nextToken();
            SQLExpr location = this.exprParser.primary();
            stmt.setLocation(location);
        }

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

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

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

        createTableQuery(stmt);

        if (lexer.token() == Token.LIKE) {
            lexer.nextToken();
            Lexer.SavePoint mark = lexer.mark();
            if (lexer.token() == Token.SELECT) {
                stmt.setLikeQuery(true);
                SQLSelect select = this.createSQLSelectParser().select();
                stmt.setSelect(select);
            } else {
                lexer.reset(mark);

                if (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 (lexer.token() == Token.COMMENT) {
            lexer.nextToken();
            SQLExpr comment = this.exprParser.expr();
            stmt.setComment(comment);
        }

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

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

    protected void parseOptions(SQLCreateTableStatement stmt) {
        lexer.nextToken();
        accept(Token.LPAREN);

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

            accept(Token.EQ);
            SQLExpr value = this.exprParser.primary();
            stmt.addOption(name, value);
            if (lexer.token() == Token.COMMA) {
                lexer.nextToken();
                if (lexer.token() == Token.RPAREN) {
                    break;
                }
                continue;
            }
            break;
        }

        accept(Token.RPAREN);
    }

    protected void parseLike(HiveCreateTableStatement stmt) {
        lexer.nextToken();

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

    protected void parseSortedBy(HiveCreateTableStatement stmt) {
        lexer.nextToken();
        accept(Token.BY);
        accept(Token.LPAREN);
        for (; ; ) {
            SQLSelectOrderByItem item = this.exprParser.parseSelectOrderByItem();
            stmt.addSortedByItem(item);
            if (lexer.token() == Token.COMMA) {
                lexer.nextToken();
                continue;
            }
            break;
        }
        accept(Token.RPAREN);
    }

    protected void parseRowFormat(HiveCreateTableStatement stmt) {
        SQLExternalRecordFormat format = this.getExprParser().parseRowFormat();
        stmt.setRowFormat(format);
        parseCreateTableWithSerderPropertie(stmt);
    }

    @Override
    public HiveExprParser getExprParser() {
        return (HiveExprParser) exprParser;
    }

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

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