using Php;

namespace PhpNative {
    private errordomain PregRuntimeError {
        RUNTIME
    }

    private void throw_preg_error (string function_name, string php_file, int php_line, string message) throws Error {
        throw new PregRuntimeError.RUNTIME ("%s:%d: PHP %s failed: %s", php_file, php_line, function_name, message);
    }

    private int find_php_regex_delimiter (string pattern, char delimiter) {
        for (int i = pattern.length - 1; i > 0; i--) {
            if (pattern[i] != delimiter) {
                continue;
            }

            bool escaped = false;
            for (int j = i - 1; j >= 0 && pattern[j] == '\\'; j--) {
                escaped = !escaped;
            }
            if (!escaped) {
                return i;
            }
        }
        return -1;
    }

    private Regex compile_php_regex (string pattern) throws RegexError {
        string expression = pattern;
        string flags = "";
        if (pattern.length >= 2) {
            char delimiter = pattern[0];
            if (!delimiter.isalnum () && delimiter != '\\' && !delimiter.isspace ()) {
                int end = find_php_regex_delimiter (pattern, delimiter);
                if (end > 0) {
                    expression = pattern.substring (1, end - 1).replace ("\\" + delimiter.to_string (), delimiter.to_string ());
                    flags = pattern.substring (end + 1);
                }
            }
        }

        RegexCompileFlags compile_flags = RegexCompileFlags.OPTIMIZE;
        for (int i = 0; i < flags.length; i++) {
            switch (flags[i]) {
            case 'i':
                compile_flags |= RegexCompileFlags.CASELESS;
                break;
            case 'm':
                compile_flags |= RegexCompileFlags.MULTILINE;
                break;
            case 's':
                compile_flags |= RegexCompileFlags.DOTALL;
                break;
            case 'x':
                compile_flags |= RegexCompileFlags.EXTENDED;
                break;
            default:
                break;
            }
        }

        return new Regex (expression, compile_flags);
    }

    public bool preg_match_bool_static (string pattern, string value, string php_file, int php_line) throws Error {
        try {
            return compile_php_regex (pattern).match (value);
        } catch (RegexError e) {
            throw_preg_error ("preg_match", php_file, php_line, e.message);
        }
        return false;
    }

    public zval preg_match_bool_zval (zval pattern, zval value, string php_file, int php_line) throws Error {
        return zval.integer (preg_match_bool_static (zval_to_string (pattern), zval_to_string (value), php_file, php_line) ? 1 : 0);
    }

    public int preg_match_static (string pattern, string value, out zval matches, string php_file, int php_line) throws Error {
        matches = zval.array ();
        try {
            MatchInfo match_info;
            if (!compile_php_regex (pattern).match (value, 0, out match_info)) {
                return 0;
            }

            int count = match_info.get_match_count ();
            for (int i = 0; i < count; i++) {
                string? part = match_info.fetch (i);
                array_append (matches, zval.string_value (part ?? ""));
            }
            return 1;
        } catch (RegexError e) {
            throw_preg_error ("preg_match", php_file, php_line, e.message);
        }
        return 0;
    }

    public zval preg_match_zval (zval pattern, zval value, out zval matches, string php_file, int php_line) throws Error {
        int result = preg_match_static (zval_to_string (pattern), zval_to_string (value), out matches, php_file, php_line);
        return zval.integer (result);
    }

    public int preg_match_all_set_order_static (string pattern, string value, out zval matches, string php_file, int php_line) throws Error {
        matches = zval.array ();
        try {
            MatchInfo match_info;
            if (!compile_php_regex (pattern).match (value, 0, out match_info)) {
                return 0;
            }

            int count = 0;
            while (match_info.matches ()) {
                zval row = zval.array ();
                int capture_count = match_info.get_match_count ();
                for (int i = 0; i < capture_count; i++) {
                    string? part = match_info.fetch (i);
                    array_append (row, zval.string_value (part ?? ""));
                }
                array_append (matches, row);
                count++;
                if (!match_info.next ()) {
                    break;
                }
            }
            return count;
        } catch (RegexError e) {
            throw_preg_error ("preg_match_all", php_file, php_line, e.message);
        }
        return 0;
    }

    public int preg_match_all_static (string pattern, string value, out zval matches, int flags, string php_file, int php_line) throws Error {
        return preg_match_all_set_order_static (pattern, value, out matches, php_file, php_line);
    }

    public zval preg_match_all_zval (zval pattern, zval value, out zval matches, zval flags, string php_file, int php_line) throws Error {
        int result = preg_match_all_static (zval_to_string (pattern), zval_to_string (value), out matches, zval_to_int (flags), php_file, php_line);
        return zval.integer (result);
    }

    public string preg_replace_static (string pattern, string replacement, string value, string php_file, int php_line) throws Error {
        try {
            return compile_php_regex (pattern).replace (value, -1, 0, replacement);
        } catch (RegexError e) {
            throw_preg_error ("preg_replace", php_file, php_line, e.message);
        }
        return value;
    }

    public zval preg_replace_zval (zval pattern, zval replacement, zval value, string php_file, int php_line) throws Error {
        return zval.string_value (preg_replace_static (zval_to_string (pattern), zval_to_string (replacement), zval_to_string (value), php_file, php_line));
    }

    public zval preg_split_static (string pattern, string value, string php_file, int php_line) throws Error {
        zval result = zval.array ();
        try {
            foreach (string part in compile_php_regex (pattern).split (value)) {
                array_append (result, zval.string_value (part));
            }
        } catch (RegexError e) {
            throw_preg_error ("preg_split", php_file, php_line, e.message);
        }
        return result;
    }

    public zval preg_split_zval (zval pattern, zval value, string php_file, int php_line) throws Error {
        return preg_split_static (zval_to_string (pattern), zval_to_string (value), php_file, php_line);
    }

    public Gee.ArrayList<string> preg_split_string_list_static (string pattern, string value, string php_file, int php_line) throws Error {
        var result = new Gee.ArrayList<string> ();
        try {
            foreach (string part in compile_php_regex (pattern).split (value)) {
                result.add (part);
            }
        } catch (RegexError e) {
            throw_preg_error ("preg_split", php_file, php_line, e.message);
        }
        return result;
    }
}
