/*
 * Copyright (C) 2016-2023 ActionTech.
 * based on code by MyCATCopyrightHolder Copyright (c) 2013, OpenCloudDB/MyCAT.
 * License: http://www.gnu.org/licenses/gpl.html GPL version 2 or higher.
 */
package com.actiontech.dble.server.parser;

import com.actiontech.dble.config.Versions;
import com.actiontech.dble.route.parser.util.ParseUtil;

import java.util.LinkedList;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * @author dcy
 */
public abstract class AbstractServerParse implements ServerParse {

    static final Pattern CALL_PATTERN = Pattern.compile("\\w*\\;\\s*\\s*(call)+\\s+\\w*\\s*", Pattern.CASE_INSENSITIVE);
    static final Pattern AUTOCOMMIT_PATTERN = Pattern.compile("^\\s*set\\s+(([a-zA-Z0-9]+\\s*=\\s*[a-zA-Z0-9]+)\\s*,)*\\s*autocommit\\s*=(\\s*(0|1|on|off))\\s*$", Pattern.CASE_INSENSITIVE);
    protected ServerParseValidations serverParseValidations = new ServerParseValidations();

    public enum BusinessType {
        R, W, OTHER
    }

    @Override
    public boolean startWithHint(String stmt) {
        int length = stmt.length();
        for (int i = 0; i < length; ++i) {
            switch (stmt.charAt(i)) {
                case ' ':
                case '\t':
                case '\r':
                case '\n':
                    continue;
                case '/':
                    if (i == 0 && stmt.charAt(1) == '*' && stmt.charAt(2) == '!' && stmt.charAt(length - 2) == '*' &&
                            stmt.charAt(length - 1) == '/') {
                        return false;
                    }
                    //fall through
                case '#':
                    i = ParseUtil.commentHint(stmt, i);
                    if (i == -1) {
                        return true;
                    }
                    continue;
                default:
                    break;
            }
            break;
        }
        return false;
    }


    @Override
    public boolean isMultiStatement(String sql) {
        int index = ParseUtil.findNextBreak(sql);
        return index + 1 < sql.length() && !ParseUtil.isEOF(sql, index);
    }

    @Override
    public void getMultiStatement(String sql, LinkedList<String> splitSql) {
        int index = ParseUtil.findNextBreak(sql);
        if (index + 1 < sql.length() && !ParseUtil.isEOF(sql, index)) {
            splitSql.add(sql.substring(0, index).trim());
            sql = sql.substring(index + 1);
            getMultiStatement(sql, splitSql);
        } else {
            if (sql.endsWith(";")) {
                splitSql.add(sql.substring(0, sql.length() - 1).trim());
            } else {
                splitSql.add(sql.trim());
            }
        }
    }

    public static boolean isTCL(int sqlType) {
        switch (sqlType) {
            case ServerParse.BEGIN:
            case ServerParse.START_TRANSACTION:
            case ServerParse.COMMIT:
            case ServerParse.ROLLBACK:
                return true;
            default:
                return false;
        }
    }

    public static boolean isImplicitlyCommitSql(int sqlType) {
        switch (sqlType) {
            case ServerParse.DDL:
            case ServerParse.ALTER_VIEW:
            case ServerParse.CREATE_DATABASE:
            case ServerParse.CREATE_VIEW:
            case ServerParse.DROP_VIEW:
            case ServerParse.DROP_TABLE:
            case RwSplitServerParse.INSTALL:
            case RwSplitServerParse.RENAME:
            case RwSplitServerParse.UNINSTALL:
            case RwSplitServerParse.GRANT:
            case RwSplitServerParse.REVOKE:
            case ServerParse.LOCK:
                return true;
            default:
                return false;
        }
    }

    public static BusinessType getBusinessType(int sqlType) {
        switch (sqlType) {
            case ServerParse.SELECT:
            case ServerParse.SHOW:
            case ServerParse.EXPLAIN:
            case ServerParse.DESCRIBE:
                return BusinessType.R;
            case ServerParse.UPDATE:
            case ServerParse.INSERT:
            case ServerParse.DELETE:
            case ServerParse.REPLACE:
            case ServerParse.DDL:
            case ServerParse.ALTER_VIEW:
            case ServerParse.CREATE_DATABASE:
            case ServerParse.CREATE_VIEW:
            case ServerParse.DROP_VIEW:
            case ServerParse.DROP_TABLE:
                return BusinessType.W;
            default:
                return BusinessType.OTHER;
        }
    }

    protected int eCheck(String stmt, int offset) {
        int sqlType = OTHER;
        if (stmt.length() > offset + 1) {
            char c1 = stmt.charAt(++offset);
            char c2 = stmt.charAt(++offset);
            if (c1 == 'X' || c1 == 'x') {
                switch (c2) {
                    case 'E':
                    case 'e':
                        sqlType = serverParseValidations.executeCheck(stmt, offset);
                        break;
                    case 'P':
                    case 'p':
                        sqlType = serverParseValidations.explainCheck(stmt, offset);
                        break;
                    default:
                        break;
                }
            }
        }
        return sqlType;
    }

    protected int lCheck(String stmt, int offset) {
        if (stmt.length() > offset + 3) {
            char c1 = stmt.charAt(++offset);
            if (c1 == 'o' || c1 == 'O') {
                switch (stmt.charAt(++offset)) {
                    case 'A':
                    case 'a':
                        return serverParseValidations.loadCheck(stmt, offset);
                    case 'C':
                    case 'c':
                        return serverParseValidations.lockCheck(stmt, offset);
                    default:
                        return OTHER;
                }
            }
        }
        return OTHER;
    }

    //alter table/view/... and analyze
    protected int aCheck(String stmt, int offset) {
        if (stmt.length() > offset + 1) {
            switch (stmt.charAt(++offset)) {
                case 'l':
                case 'L':
                    return serverParseValidations.alterCheck(stmt, offset);
                case 'n':
                case 'N':
                    return serverParseValidations.analyzeCheck(stmt, offset);
                default:
                    return OTHER;
            }
        }
        return OTHER;
    }

    protected int orCheck(String stmt, int offset) {
        int len = stmt.length();
        if (len > ++offset) {
            char c1 = stmt.charAt(offset);
            if ((c1 == 'R' || c1 == 'r')) {
                while (len > ++offset) {
                    if (ParseUtil.isSpace(stmt.charAt(offset))) {
                        continue;
                    }
                    return replaceViewCheck(stmt, offset);
                }
            }
        }
        return DDL;
    }

    private int replaceViewCheck(String stmt, int offset) {
        int len = stmt.length();
        if (len > offset + 7) {
            char c1 = stmt.charAt(++offset);
            char c2 = stmt.charAt(++offset);
            char c3 = stmt.charAt(++offset);
            char c4 = stmt.charAt(++offset);
            char c5 = stmt.charAt(++offset);
            char c6 = stmt.charAt(++offset);
            if ((c1 == 'E' || c1 == 'e') && (c2 == 'P' || c2 == 'p') && (c3 == 'L' || c3 == 'l') &&
                    (c4 == 'A' || c4 == 'a') && (c5 == 'C' || c5 == 'c') && (c6 == 'E' || c6 == 'e')) {
                while (len > ++offset) {
                    if (ParseUtil.isSpace(stmt.charAt(offset))) {
                        continue;
                    }
                    return serverParseValidations.viewCheck(stmt, offset, true);
                }
            }
        }
        return DDL;
    }

    // KILL' '
    protected int killCheck(String stmt, int offset) {
        if (stmt.length() > offset + "ILL ".length()) {
            char c1 = stmt.charAt(++offset);
            char c2 = stmt.charAt(++offset);
            char c3 = stmt.charAt(++offset);
            char c4 = stmt.charAt(++offset);
            if ((c1 == 'I' || c1 == 'i') && (c2 == 'L' || c2 == 'l') &&
                    (c3 == 'L' || c3 == 'l') &&
                    (c4 == ' ' || c4 == '\t' || c4 == '\r' || c4 == '\n')) {
                while (stmt.length() > ++offset) {
                    switch (stmt.charAt(offset)) {
                        case ' ':
                        case '\t':
                        case '\r':
                        case '\n':
                            continue;
                        case 'Q':
                        case 'q':
                            return serverParseValidations.killQueryCheck(stmt, offset);
                        case 'c':
                        case 'C':
                            return serverParseValidations.killConnection(stmt, offset);
                        default:
                            return (offset << 8) | KILL;
                    }
                }
                return OTHER;
            }
        }
        return OTHER;
    }

    // DESCRIBE or desc or DELETE' ' or DEALLOCATE' '
    protected int dCheck(String stmt, int offset) {
        int sqlType = OTHER;
        if (stmt.length() > offset + 1) {
            char c1 = stmt.charAt(++offset);
            char c2 = stmt.charAt(++offset);
            if ((c1 == 'E' || c1 == 'e')) {
                switch (c2) {
                    case 'A':
                    case 'a':
                        sqlType = serverParseValidations.dealCheck(stmt, offset);
                        break;
                    case 'S':
                    case 's':
                        sqlType = serverParseValidations.descCheck(stmt, offset);
                        break;
                    case 'L':
                    case 'l':
                        sqlType = serverParseValidations.deleCheck(stmt, offset);
                        break;
                    default:
                        break;
                }
            }
        }
        return sqlType;
    }

    protected int repCheck(String stmt, int offset) {
        if (stmt.length() > ++offset) {
            switch (stmt.charAt(offset)) {
                case 'A':
                case 'a':
                    return serverParseValidations.repair(stmt, offset);
                case 'L':
                case 'l':
                    return serverParseValidations.replace(stmt, offset);
                default:
                    return OTHER;
            }
        }
        return OTHER;
    }

    protected int seCheck(String stmt, int offset) {
        if (stmt.length() > ++offset) {
            switch (stmt.charAt(offset)) {
                case 'L':
                case 'l':
                    return serverParseValidations.selectCheck(stmt, offset);
                case 'T':
                case 't':
                    if (stmt.length() > ++offset) {
                        //support QUERY like this
                        //  /*!dble: sql=SELECT * FROM test where id=99 */set @pin=1;
                        //  call p_test(@pin,@pout);
                        //  select @pout;
                        if (stmt.startsWith("/*!" + Versions.ANNOTATION_NAME) || stmt.startsWith("/*#" + Versions.ANNOTATION_NAME) || stmt.startsWith("/*" + Versions.ANNOTATION_NAME)) {
                            Matcher matcher = CALL_PATTERN.matcher(stmt);
                            if (matcher.find()) {
                                return CALL;
                            }
                        }

                        char c = stmt.charAt(offset);
                        if (c == ' ' || c == '\r' || c == '\n' || c == '\t' || c == '/' || c == '#') {
                            return (offset << 8) | SET;
                        }
                    }
                    return OTHER;
                default:
                    return OTHER;
            }
        }
        return OTHER;
    }

    protected int prepareCheck(String stmt, int offset) {
        if (serverParseValidations.isPrepare(stmt, offset)) return SCRIPT_PREPARE;
        return OTHER;
    }
}
