using Gee;

public class PhpToValaCrossFileFuncVarsOptimizer : Object {
    private PhpToValaTranspiler transpiler;
    private PhpToValaFileFuncVarsOptimizer file_optimizer;

    public PhpToValaCrossFileFuncVarsOptimizer (PhpToValaTranspiler transpiler, PhpToValaFileFuncVarsOptimizer file_optimizer) {
        this.transpiler = transpiler;
        this.file_optimizer = file_optimizer;
    }

    public bool can_call_external_static (LocalFunction external_function) {
        if (external_function.static_call_name == null || external_function.static_return_type == null) {
            return false;
        }
        if (file_optimizer.native_scalar_type (external_function.static_return_type) == null) {
            return false;
        }
        foreach (LocalParameter parameter in external_function.parameters) {
            if (file_optimizer.native_scalar_type (parameter.type_name) == null && file_optimizer.infer_default_value_type (parameter.default_value) == null) {
                return false;
            }
        }
        return true;
    }

    public void run_stage (SourceParts source_parts, int stage_index) {
        if (stage_index < 1) {
            return;
        }
        foreach (string function_name in transpiler.external_functions.keys) {
            LocalFunction? external_function = transpiler.external_functions[function_name];
            if (external_function != null) {
                can_call_external_static (external_function);
            }
        }
    }

    public string external_function_call_as_native_value (LocalFunction external_function, string args, int php_line_number) throws Error {
        string call_name = external_function.static_call_name ?? external_function.name;
        transpiler.mark_external_function_used (external_function, call_name);
        return "%s (%s)".printf (call_name, convert_external_native_argument_list (external_function, args, php_line_number));
    }

    public string external_function_call_as_zval_value (LocalFunction external_function, string args, int php_line_number) throws Error {
        if (external_function.zval_call_name != null) {
            transpiler.mark_external_function_used (external_function, external_function.zval_call_name);
            return "%s (%s)".printf (external_function.zval_call_name, transpiler.convert_zval_argument_list_public (args, php_line_number));
        }
        string return_type = file_optimizer.native_scalar_type (external_function.static_return_type) ?? "void";
        if (return_type == "void") {
            return "Php.zval.null_value ()";
        }
        return file_optimizer.native_to_zval_return (external_function_call_as_native_value (external_function, args, php_line_number), return_type);
    }

    private string convert_external_native_argument_list (LocalFunction external_function, string args, int php_line_number) throws Error {
        if (args.strip () == "") {
            return "";
        }

        var raw_args = transpiler.split_top_level (args, ',');
        var converted = new StringBuilder ();
        for (int i = 0; i < raw_args.size; i++) {
            if (converted.len > 0) {
                converted.append (", ");
            }
            string expected_type = "";
            if (i < external_function.parameters.size) {
                expected_type = file_optimizer.native_scalar_type (external_function.parameters[i].type_name) ?? file_optimizer.infer_default_value_type (external_function.parameters[i].default_value) ?? "";
            }
            converted.append (file_optimizer.convert_native_expression (raw_args[i].strip (), php_line_number, expected_type));
        }
        return converted.str;
    }

    public string? transpile_native_external_function_statement (string line, int php_line_number) throws Error {
        if (!line.has_suffix (");")) {
            return null;
        }
        int open_index = line.index_of ("(");
        if (open_index <= 0) {
            return null;
        }
        string function_name = line.substring (0, open_index).strip ();
        if (!transpiler.external_functions.has_key (function_name)) {
            return null;
        }
        int close_index = PhpToValaTranspiler.find_matching_paren (line, open_index);
        if (close_index < open_index || close_index != line.length - 2) {
            return null;
        }
        LocalFunction target = transpiler.external_functions[function_name];
        string inner_args = line.substring (open_index + 1, close_index - open_index - 1);
        if (transpiler.optimizes_cross_file_func_vars () && can_call_external_static (target)) {
            return "%s;".printf (external_function_call_as_native_value (target, inner_args, php_line_number));
        }
        return "%s;".printf (external_function_call_as_zval_value (target, inner_args, php_line_number));
    }

    public string replace_inline_external_function_call (string expression, LocalFunction external_function, int php_line_number) {
        string function_name = external_function.name;
        var output = new StringBuilder ();
        int i = 0;
        while (i < expression.length) {
            if (expression.substring (i).has_prefix (function_name + "(")
                && (i == 0 || (!PhpToValaTranspiler.is_identifier_char (expression[i - 1]) && expression[i - 1] != '.'))) {
                int open_index = i + function_name.length;
                int close_index = PhpToValaTranspiler.find_matching_paren (expression, open_index);
                if (close_index > open_index) {
                    string inner_args = expression.substring (open_index + 1, close_index - open_index - 1);
                    try {
                        output.append (external_function_call_as_native_value (external_function, inner_args, php_line_number));
                        i = close_index + 1;
                        continue;
                    } catch (Error e) {
                    }
                }
            }
            output.append_c (expression[i]);
            i++;
        }
        return output.str;
    }

}
