using Gee;

public class PhpToValaNativeRuntimeValidator : Object {
    private PhpToValaTranspiler transpiler;

    public PhpToValaNativeRuntimeValidator (PhpToValaTranspiler transpiler) {
        this.transpiler = transpiler;
    }

    public void fail_if_missing_embed_only_functions (SourceParts source_parts) throws Error {
        if (transpiler.get_runtime_name () != "native-runtime") {
            return;
        }

        var missing = new ArrayList<string> ();
        var seen = new HashMap<string, bool> ();
        collect_missing_functions (source_parts.main_source, 1, missing, seen);
        foreach (LocalFunction local_function in source_parts.functions) {
            collect_missing_functions (local_function.body, local_function.line + 1, missing, seen);
        }
        foreach (PhpClass php_class in source_parts.classes) {
            foreach (LocalFunction method in php_class.methods) {
                collect_missing_functions (method.body, method.line + 1, missing, seen);
            }
        }

        if (missing.size == 0) {
            return;
        }

        throw new PhpToValaError.UNSUPPORTED (
            "native-runtime is missing bindings for PHP functions: %s; use --both-runtimes to allow embed fallback",
            join_strings (missing, ", ")
        );
    }

    private void collect_missing_functions (string source, int first_line, ArrayList<string> missing, HashMap<string, bool> seen) {
        bool in_single_quote = false;
        bool in_double_quote = false;
        bool in_line_comment = false;
        bool in_block_comment = false;
        int line = first_line;

        for (int i = 0; i < source.length; i++) {
            char c = source[i];
            char next = i + 1 < source.length ? source[i + 1] : '\0';

            if (c == '\n') {
                line++;
                in_line_comment = false;
                continue;
            }
            if (in_line_comment) {
                continue;
            }
            if (in_block_comment) {
                if (c == '*' && next == '/') {
                    in_block_comment = false;
                    i++;
                }
                continue;
            }
            if (in_single_quote) {
                if (c == '\\') {
                    i++;
                } else if (c == '\'') {
                    in_single_quote = false;
                }
                continue;
            }
            if (in_double_quote) {
                if (c == '\\') {
                    i++;
                } else if (c == '"') {
                    in_double_quote = false;
                }
                continue;
            }
            if (c == '/' && next == '/') {
                in_line_comment = true;
                i++;
                continue;
            }
            if (c == '#') {
                in_line_comment = true;
                continue;
            }
            if (c == '/' && next == '*') {
                in_block_comment = true;
                i++;
                continue;
            }
            if (c == '\'') {
                in_single_quote = true;
                continue;
            }
            if (c == '"') {
                in_double_quote = true;
                continue;
            }

            if (!(c.isalpha () || c == '_')) {
                continue;
            }

            int start = i;
            while (i < source.length && PhpToValaTranspiler.is_identifier_char (source[i])) {
                i++;
            }
            string function_name = source.substring (start, i - start);
            int after_name = i;
            while (after_name < source.length && source[after_name].isspace ()) {
                after_name++;
            }
            i--;

            if (after_name >= source.length || source[after_name] != '(') {
                continue;
            }
            if (start > 0 && (PhpToValaTranspiler.is_identifier_char (source[start - 1]) || source[start - 1] == '$')) {
                continue;
            }
            if (start >= 2 && source[start - 2] == '-' && source[start - 1] == '>') {
                continue;
            }
            if (start >= 2 && source[start - 2] == ':' && source[start - 1] == ':') {
                continue;
            }
            if (PhpToValaGeneratorUtils.is_scan_ignored_php_function_name (function_name)) {
                continue;
            }
            if (function_name == "array_shift") {
                continue;
            }
            if (is_native_runtime_lowered_function (function_name)) {
                continue;
            }
            if (transpiler.local_functions.has_key (function_name) || transpiler.external_functions.has_key (function_name)) {
                continue;
            }
            if (transpiler.has_native_runtime_candidate (function_name) || !transpiler.has_embed_runtime_candidate (function_name)) {
                continue;
            }
            if (seen.has_key (function_name)) {
                continue;
            }

            seen[function_name] = true;
            missing.add (function_name);
            stderr.printf (
                "info: TODO in php2vala: native-runtime binding missing for PHP function '%s' at %s:%d; embed-runtime binding exists\n",
                function_name,
                transpiler.get_source_path (),
                line
            );
        }
    }

    private static bool is_native_runtime_lowered_function (string function_name) {
        switch (function_name) {
        case "array_filter":
        case "array_values":
            return true;
        }
        return false;
    }

    private static string join_strings (ArrayList<string> values, string separator) {
        var output = new StringBuilder ();
        for (int i = 0; i < values.size; i++) {
            if (i > 0) {
                output.append (separator);
            }
            output.append (values[i]);
        }
        return output.str;
    }
}
