package com.alibaba.druid.sql.ast;

import com.alibaba.druid.sql.ast.expr.SQLCharExpr;
import com.alibaba.druid.sql.ast.expr.SQLIdentifierExpr;
import com.alibaba.druid.sql.ast.expr.SQLPropertyExpr;
import com.alibaba.druid.sql.dialect.mysql.parser.MySqlExprParser;
import com.alibaba.druid.sql.parser.Lexer;
import com.alibaba.druid.sql.parser.SQLParserFeature;
import com.alibaba.druid.sql.parser.Token;
import com.alibaba.druid.util.FnvHash;

import java.util.ArrayList;
import java.util.List;

public class TDDLHint extends SQLCommentHint {
    private List<Function> functions = new ArrayList<Function>();
    private String json;
    private Type type = Type.Unknown;

    public List<Function> getFunctions() {
        return functions;
    }

    public TDDLHint(String text) {
        super(text);

        MySqlExprParser hintParser = new MySqlExprParser(text, SQLParserFeature.TDDLHint);
        Lexer lexer = hintParser.getLexer();
        if (lexer.token() == Token.PLUS || lexer.token() == Token.BANG) {
            lexer.nextToken();
        }

        if (!lexer.identifierEquals(FnvHash.Constants.TDDL)) {
            // error tddl hint
            return;
        }

        lexer.nextToken();

        switch (lexer.token()) {
            case COLON:
                lexer.nextToken();
                break;
            case LPAREN:
                int rp = text.lastIndexOf(')');
                if (rp != -1) {
                    json = text.substring(lexer.pos(), rp);
                    type = Type.JSON;
                }
                return;
            default:
                return;
        }

        for (; ; ) {
            if (lexer.token() == Token.AND) {
                lexer.nextToken();
            }

            String name = lexer.stringVal();
            long hash = lexer.hashLCase();

            if (lexer.identifierEquals(FnvHash.Constants.NODE)) {
                lexer.nextToken();
                if (lexer.token() == Token.IN) {
                    lexer.nextToken();
                    name = "NODE_IN";
                } else if (lexer.token() == Token.EQ) {
                    lexer.nextToken();
                    name = "NODE_IN";
                    SQLExpr value = hintParser.primary();
                    Function function = new Function(name);
                    Argument argument = new Argument(null, value);
                    function.getArguments().add(argument);
                    functions.add(function);

                    if (lexer.token() == Token.EOF) {
                        break;
                    }

                    continue;
                }
            } else if (hash == FnvHash.Constants.SCAN || hash == FnvHash.Constants.DEFER) {
                lexer.nextToken();

                if (lexer.token() == Token.EQ) {
                    lexer.nextToken();
                    SQLExpr value = hintParser.primary();
                    Function function = new Function(name);
                    Argument argument = new Argument(null, value);
                    function.getArguments().add(argument);
                    functions.add(function);

                    if (lexer.token() == Token.EOF) {
                        break;
                    }

                    continue;
                } else if (lexer.token() == Token.EOF) {
                    Function function = new Function(name);
                    functions.add(function);
                    break;
                }
            } else if (hash == FnvHash.Constants.SQL_DELAY_CUTOFF
                    || hash == FnvHash.Constants.SOCKET_TIMEOUT
                    || hash == FnvHash.Constants.UNDO_LOG_LIMIT
                    || hash == FnvHash.Constants.FORBID_EXECUTE_DML_ALL) {
                lexer.nextToken();

                if (lexer.token() == Token.EQ) {
                    lexer.nextToken();
                    SQLExpr value = hintParser.primary();
                    Function function = new Function(name);
                    Argument argument = new Argument(null, value);
                    function.getArguments().add(argument);
                    functions.add(function);

                    if (lexer.token() == Token.EOF) {
                        break;
                    }

                    continue;
                }
            } else {
                lexer.nextToken();
            }

            // /!TDDL:table_name.partition_key=value [and table_name1.partition_key=value1]*/
            // =>
            // /!TDDL:partitions('table_name.partition_key=value','table_name1.partition_key=value1')*/
            if (lexer.token() == Token.DOT) {
                lexer.nextToken();
                if (lexer.identifierEquals("partition_key")) {
                    String table = name;
                    lexer.nextToken();
                    hintParser.accept(Token.EQ);
                    SQLExpr value = hintParser.primary();

                    Function function = new Function("PARTITIONS");
                    functions.add(function);

                    function.getArguments().add(new Argument(new SQLPropertyExpr(name, "partition_key"), value));

                    while (lexer.token() == Token.AND) {
                        lexer.nextToken();
                        SQLExpr key = hintParser.primary();
                        hintParser.accept(Token.EQ);
                        value = hintParser.primary();

                        function.getArguments().add(new Argument(key, value));
                    }

                    if (lexer.token() == Token.EOF) {
                        break;
                    }
                } else {
                    return; // skip
                }
            } else if (lexer.token() == Token.EQ) {
                lexer.nextToken();
                // For other KV.
                SQLExpr value = hintParser.primary();
                Function function = new Function(name);
                Argument argument = new Argument(null, value);
                function.getArguments().add(argument);
                functions.add(function);

                if (lexer.token() == Token.EOF) {
                    break;
                }

                continue;
            }

            Function function = new Function(name);

            functions.add(function);

            if (hash == FnvHash.Constants.MASTER) {
                if (lexer.token() == Token.EOF) {
                    break;
                } else if (lexer.token() == Token.BAR) {
                    lexer.nextToken();
                    continue;
                }
            }

            if (hash == FnvHash.Constants.SLAVE) {
                if (lexer.token() == Token.EOF) {
                    break;
                } else if (lexer.token() == Token.AND) {
                    lexer.nextToken();
                    continue;
                }
            }

            if (lexer.token() == Token.AND) {
                continue;
            }

            hintParser.accept(Token.LPAREN);

            if (lexer.token() != Token.RPAREN) {
                for (; ; ) {
                    Lexer.SavePoint mark = lexer.mark();

                    SQLExpr value = null;

                    String keyVal = lexer.stringVal();
                    long keyHash = lexer.hashLCase();

                    lexer.nextToken();

                    SQLIdentifierExpr key = new SQLIdentifierExpr(keyVal, keyHash);

                    if (lexer.token() == Token.EQ) {
                        hintParser.accept(Token.EQ);

                        if (lexer.token() == Token.LITERAL_ALIAS) {
                            String stringVal = lexer.stringVal();
                            stringVal = stringVal.substring(1, stringVal.length() - 1);
                            value = new SQLCharExpr(stringVal);
                            value = hintParser.exprRest(value);

                            lexer.nextToken();
                        } else {
                            value = hintParser.expr();
                        }
                    }

                    //为了处理 add_ms(t.pk, true) 的情况；
                    if (value == null) {
                        lexer.reset(mark);
                        key = null;
                        value = hintParser.expr();
                    }

                    Argument argument = new Argument(key, value);
                    function.getArguments().add(argument);

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

                    if (lexer.token() == Token.RPAREN) {
                        lexer.nextToken();
                        break;
                    }
                }
            } else {
                lexer.nextToken();
            }

            if (lexer.token() == Token.AND) {
                continue;
            }

            if (hash == FnvHash.Constants.MASTER && lexer.token() == Token.BAR) {
                lexer.nextToken();
            }

            if (lexer.token() == Token.EOF) {
                break;
            }
        }

        if (functions.size() > 0) {
            type = Type.Function;
        }
    }

    public String getJson() {
        return json;
    }

    public Type getType() {
        return type;
    }

    public static class Function {
        private final String name;
        private final List<Argument> arguments = new ArrayList<Argument>();

        public Function(String name) {
            this.name = name;
        }

        public String getName() {
            return name;
        }

        public List<Argument> getArguments() {
            return arguments;
        }
    }

    public static class Argument {
        private final SQLExpr name;
        private final SQLExpr value;

        public Argument(SQLExpr name, SQLExpr value) {
            this.name = name;
            this.value = value;
        }

        public SQLExpr getName() {
            return name;
        }

        public SQLExpr getValue() {
            return value;
        }
    }

    public static enum Type {
        Function,
        JSON,
        Unknown
    }
}
