/*
 * Copyright (C) 2016-2022 ActionTech.
 * License: http://www.gnu.org/licenses/gpl.html GPL version 2 or higher.
 */

package com.actiontech.dble.util;

public class CompareLike {
    private enum CompareType {
        IS_NULL, FULL_MATCH, LIKE
    }

    private static final int MATCH = 0, ONE = 1, ANY = 2;
    private final CompareType type;

    private char[] patternChars;
    private String patternString;
    private int[] patternTypes;
    private int patternLength;

    public CompareLike(String pattern) {
        this(pattern, "");
    }

    public CompareLike(String pattern, String escape) {
        if ("%".equals(pattern)) {
            type = CompareType.IS_NULL;
        } else {
            initPattern(pattern, getEscapeChar(escape));
            if (isFullMatch()) {
                type = CompareType.FULL_MATCH;
            } else {
                type = CompareType.LIKE;
            }
        }
    }

    private Character getEscapeChar(String es) {
        Character esc;
        if (es == null) {
            esc = getEscapeChar("//");
        } else if (es.length() == 0) {
            esc = null;
        } else {
            esc = es.charAt(0);
        }
        return esc;
    }

    private void initPattern(String p, Character escapeChar) {
        patternLength = 0;
        if (p == null) {
            patternTypes = null;
            patternChars = null;
            return;
        }
        int len = p.length();
        patternChars = new char[len];
        patternTypes = new int[len];
        boolean lastAny = false;
        for (int i = 0; i < len; i++) {
            char c = p.charAt(i);
            int patternType;
            if (escapeChar != null && escapeChar == c) {
                if (i >= len - 1) {
                    // invalidPattern = true;
                    return;
                }
                c = p.charAt(++i);
                patternType = MATCH;
                lastAny = false;
            } else if (c == '%') {
                if (lastAny) {
                    continue;
                }
                patternType = ANY;
                lastAny = true;
            } else if (c == '_') {
                patternType = ONE;
            } else {
                patternType = MATCH;
                lastAny = false;
            }
            patternTypes[patternLength] = patternType;
            patternChars[patternLength++] = c;
        }
        for (int i = 0; i < patternLength - 1; i++) {
            if ((patternTypes[i] == ANY) && (patternTypes[i + 1] == ONE)) {
                patternTypes[i] = ONE;
                patternTypes[i + 1] = ANY;
            }
        }
        patternString = new String(patternChars, 0, patternLength);
    }

    private boolean isFullMatch() {
        if (patternTypes == null) {
            return false;
        }
        for (int patternType : patternTypes) {
            if (patternType != MATCH) {
                return false;
            }
        }
        return true;
    }

    public boolean compare(String s) {
        if (type == CompareType.IS_NULL) {
            return s != null;
        } else if (type == CompareType.FULL_MATCH) {
            return StringUtil.equalsIgnoreCase(patternString, s);
        } else {
            return compareAt(s, 0, 0, s.length(), patternChars, patternTypes);
        }
    }

    private boolean compare(char[] pattern, String s, int pi, int si) {
        if (s == null || s.length() == 0) {
            return false;
        }
        return pattern[pi] == s.charAt(si) || equalsChars(patternString, pi, s, si, true);
    }

    private boolean compareAt(String s, int pi, int si, int sLen, char[] pattern, int[] types) {
        for (; pi < patternLength; pi++) {
            switch (types[pi]) {
                case MATCH:
                    if ((si >= sLen) || !compare(pattern, s, pi, si++)) {
                        return false;
                    }
                    break;
                case ONE:
                    if (si++ >= sLen) {
                        return false;
                    }
                    break;
                case ANY:
                    if (++pi >= patternLength) {
                        return true;
                    }
                    while (si < sLen) {
                        if (compare(pattern, s, pi, si) && compareAt(s, pi, si, sLen, pattern, types)) {
                            return true;
                        }
                        si++;
                    }
                    return false;
                default:
                    break;
            }
        }
        return si == sLen;
    }

    public boolean equalsChars(String a, int ai, String b, int bi, boolean ignoreCase) {
        char ca = a.charAt(ai);
        char cb = b.charAt(bi);
        if (ignoreCase) {
            ca = Character.toUpperCase(ca);
            cb = Character.toUpperCase(cb);
        }
        return ca == cb;
    }
}
