using Gee;

public class PhpToValaTypedAssignmentOptimizer : Object {
    private PhpToValaTranspiler transpiler;

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

    public string? transpile_zval_typed_static_assignment (string storage_name, string raw_expression, int php_line_number) throws Error {
        if (!(transpiler.optimizes_local_vars () || transpiler.optimizes_file_func_vars ())) {
            return null;
        }
        string expression = PhpToValaTranspiler.trim_trailing_semicolon (raw_expression.strip ());
        MatchInfo match;
        if (!transpiler.function_expression_regex.match (expression, 0, out match)) {
            return null;
        }

        int open_index = expression.index_of ("(");
        if (open_index < 0 || PhpToValaTranspiler.find_matching_paren (expression, open_index) != expression.length - 1) {
            return null;
        }

        string function_name = match.fetch (1);
        string args = match.fetch (2).strip ();
        var zargs = new ArrayList<string> ();
        foreach (string raw_arg in transpiler.split_top_level (args, ',')) {
            string clean_arg = raw_arg.strip ();
            if (clean_arg != "") {
                zargs.add (transpiler.convert_zval_expression (clean_arg, php_line_number));
            }
        }

        string runtime_function_name;
        FunctionBinding? binding = transpiler.resolve_dynamic_static_binding_for_native_local (
            function_name,
            zargs.size,
            out runtime_function_name
        );
        if (binding == null || !transpiler.can_use_runtime_static_return_as_native_local (binding.return_type)) {
            return null;
        }
        if (!transpiler.can_convert_runtime_static_arguments (binding, zargs)) {
            return null;
        }

        string type_name = PhpToValaTranspiler.normalize_runtime_type (binding.return_type);
        string prelude;
        string postlude;
        string call = transpiler.build_dynamic_static_call_with_zval_outs (binding, runtime_function_name, zargs, php_line_number, out prelude, out postlude);
        transpiler.mark_function_used (runtime_function_name);

        if (transpiler.variable_types.has_key (storage_name)) {
            if (PhpToValaTranspiler.normalize_runtime_type (transpiler.variable_types[storage_name]) != type_name) {
                return null;
            }
            return @"$prelude$storage_name = $call;$postlude";
        }

        transpiler.variable_types[storage_name] = type_name;
        return @"$prelude$type_name $storage_name = $call;$postlude";
    }

    public string? transpile_zval_typed_native_assignment (string storage_name, string raw_expression, int php_line_number) throws Error {
        if (!(transpiler.optimizes_local_vars () || transpiler.optimizes_file_func_vars ())) {
            return null;
        }

        string? type_name = transpiler.typed_native_assignment_return_type (raw_expression);
        if (type_name == null) {
            return null;
        }

        string? value = transpiler.convert_typed_native_expression (raw_expression, php_line_number, type_name);
        if (value == null) {
            return null;
        }

        if (transpiler.variable_types.has_key (storage_name)) {
            if (PhpToValaTranspiler.normalize_runtime_type (transpiler.variable_types[storage_name]) != type_name) {
                return null;
            }
            return @"$storage_name = $value;";
        }

        transpiler.variable_types[storage_name] = type_name;
        return @"$type_name $storage_name = $value;";
    }

    public string? transpile_zval_existing_native_assignment (string storage_name, string raw_expression, int php_line_number) throws Error {
        if (!(transpiler.optimizes_local_vars () || transpiler.optimizes_file_func_vars ()) || !transpiler.variable_types.has_key (storage_name)) {
            return null;
        }

        string type_name = PhpToValaTranspiler.normalize_runtime_type (transpiler.variable_types[storage_name]);
        if (type_name == "Php.zval") {
            return null;
        }

        string? native_value = transpiler.convert_typed_native_expression (raw_expression, php_line_number, type_name);
        if (native_value == null) {
            string zval_value = transpiler.convert_zval_expression (raw_expression, php_line_number);
            native_value = transpiler.zval_to_native_assignment_value (zval_value, type_name);
        }
        if (native_value == null) {
            return null;
        }

        return @"$storage_name = $native_value;";
    }

    public string? transpile_zval_type_change_assignment (string assignment_name, string storage_name, string raw_expression, int php_line_number) throws Error {
        if (!(transpiler.optimizes_local_vars () || transpiler.optimizes_file_func_vars ()) || !transpiler.variable_types.has_key (storage_name)) {
            return null;
        }

        if (PhpToValaTranspiler.normalize_runtime_type (transpiler.variable_types[storage_name]) == "Php.zval") {
            return null;
        }

        if (!raw_php_expression_returns_zval (raw_expression)) {
            return null;
        }

        string expression = transpiler.convert_zval_expression (raw_expression, php_line_number);
        string alias_name = transpiler.unique_alias_name (assignment_name, "Php.zval");
        transpiler.variable_aliases[assignment_name] = alias_name;
        transpiler.variable_types[alias_name] = "Php.zval";
        return @"Php.zval $alias_name = $expression;";
    }

    private bool raw_php_expression_returns_zval (string raw_expression) {
        string expression = PhpToValaTranspiler.trim_trailing_semicolon (raw_expression.strip ());
        MatchInfo match;
        if (!transpiler.function_expression_regex.match (expression, 0, out match)) {
            return false;
        }

        int open_index = expression.index_of ("(");
        if (open_index < 0 || PhpToValaTranspiler.find_matching_paren (expression, open_index) != expression.length - 1) {
            return false;
        }

        switch (match.fetch (1)) {
        case "array":
        case "explode":
        case "json_decode":
        case "unserialize":
        case "token_get_all":
            return true;
        }

        return false;
    }


}
