using Gee;

public class PhpToValaNativeExpressionGenerator : Object {
    private PhpToValaTranspiler transpiler;

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

    public string? zval_to_native_assignment_value (string expression, string type_name) {
        switch (PhpToValaTranspiler.normalize_runtime_type (type_name)) {
        case "int":
        case "int64":
        case "long":
            return "Php.zval_to_int (%s)".printf (expression);
        case "double":
        case "float":
            return "Php.zval_to_double (%s)".printf (expression);
        case "string":
            return "Php.zval_to_string (%s)".printf (expression);
        case "bool":
            return "Php.truthy (%s)".printf (expression);
        default:
            return null;
        }
    }

    public string default_native_initializer (string type_name) {
        switch (PhpToValaTranspiler.normalize_runtime_type (type_name)) {
        case "double":
        case "float":
            return "0.0";
        case "bool":
            return "false";
        case "string":
            return "\"\"";
        default:
            if (type_name.index_of ("ArrayList<") >= 0
                || type_name.has_suffix ("Resource")
                || type_name.index_of (".StreamResource") >= 0) {
                return "null";
            }
            return "0";
        }
    }

    public string? typed_native_assignment_return_type (string raw_expression) {
        string expression = PhpToValaTranspiler.trim_trailing_semicolon (raw_expression.strip ());
        if (can_convert_to_native_int_expression (expression)) {
            return "int";
        }
        if (can_convert_to_native_double_expression (expression)) {
            return "double";
        }
        if (can_convert_to_native_string_expression (expression)) {
            return "string";
        }
        return null;
    }

    private bool can_convert_to_native_int_expression (string expression) {
        string clean = strip_balanced_outer_parens (PhpToValaTranspiler.trim_trailing_semicolon (expression.strip ()));
        if (is_integer_literal (clean)) {
            return true;
        }

        if (is_standalone_php_variable_access (clean)) {
            string variable_name = aliased_variable_name (clean);
            if (!transpiler.variable_types.has_key (variable_name)) {
                return false;
            }
            string type_name = PhpToValaTranspiler.normalize_runtime_type (transpiler.variable_types[variable_name]);
            return type_name == "int" || type_name == "long" || type_name == "int64";
        }

        foreach (string op in new string[] {"+", "-", "*", "%"}) {
            int index = transpiler.find_top_level_operator_index (clean, op);
            if (index > 0) {
                return can_convert_to_native_int_expression (clean.substring (0, index).strip ())
                    && can_convert_to_native_int_expression (clean.substring (index + op.length).strip ());
            }
        }

        return false;
    }

    private bool can_convert_to_native_double_expression (string expression) {
        string clean = strip_balanced_outer_parens (PhpToValaTranspiler.trim_trailing_semicolon (expression.strip ()));
        if (is_microtime_true_expression (clean) || is_numeric_literal (clean)) {
            return true;
        }

        if (is_standalone_php_variable_access (clean)) {
            string variable_name = aliased_variable_name (clean);
            return transpiler.variable_types.has_key (variable_name);
        }

        foreach (string op in new string[] {"+", "-", "*", "/", "%"}) {
            int index = transpiler.find_top_level_operator_index (clean, op);
            if (index > 0) {
                return can_convert_to_native_double_expression (clean.substring (0, index).strip ())
                    && can_convert_to_native_double_expression (clean.substring (index + op.length).strip ());
            }
        }

        return false;
    }

    private string strip_balanced_outer_parens (string expression) {
        string clean = expression.strip ();
        while (clean.has_prefix ("(") && clean.has_suffix (")")) {
            int close_index = PhpToValaTranspiler.find_matching_paren (clean, 0);
            if (close_index != clean.length - 1) {
                break;
            }
            clean = clean.substring (1, clean.length - 2).strip ();
        }
        return clean;
    }

    private string aliased_variable_name (string expression) {
        string variable_name = PhpToValaTranspiler.replace_php_variables (expression.strip ());
        foreach (string name in transpiler.variable_aliases.keys) {
            variable_name = PhpToValaTranspiler.replace_identifier_token (variable_name, name, transpiler.variable_aliases[name]);
        }
        return variable_name;
    }

    private static bool is_standalone_php_variable_access (string expression) {
        return PhpToValaGeneratorUtils.is_standalone_php_variable_access (expression);
    }

    private static bool is_microtime_true_expression (string expression) {
        return PhpToValaGeneratorUtils.is_microtime_true_expression (expression);
    }

    private static bool is_numeric_literal (string expression) {
        return PhpToValaGeneratorUtils.is_numeric_literal (expression);
    }

    private static bool is_string_literal (string expression) {
        string clean = expression.strip ();
        return clean.length >= 2 && ((clean.has_prefix ("\"") && clean.has_suffix ("\"")) || (clean.has_prefix ("'") && clean.has_suffix ("'")));
    }

    private static bool is_integer_literal (string expression) {
        return PhpToValaGeneratorUtils.is_integer_literal (expression);
    }

    public string? convert_typed_native_expression (string raw_expression, int php_line_number, string type_name) throws Error {
        string expression = PhpToValaTranspiler.trim_trailing_semicolon (raw_expression.strip ());
        if (type_name == "int") {
            return convert_native_int_expression (expression, php_line_number);
        }
        if (type_name == "double") {
            return convert_native_double_expression (expression, php_line_number);
        }
        if (type_name == "string") {
            return convert_native_string_expression (expression, php_line_number);
        }
        return null;
    }

    public string? convert_native_int_expression (string expression, int php_line_number) throws Error {
        string clean = strip_balanced_outer_parens (PhpToValaTranspiler.trim_trailing_semicolon (expression.strip ()));
        if (is_integer_literal (clean)) {
            return clean;
        }
        if (is_standalone_php_variable_access (clean)) {
            string converted = transpiler.convert_zval_expression (clean, php_line_number);
            string variable_name = aliased_variable_name (clean);
            if (transpiler.variable_types.has_key (variable_name)) {
                string type_name = PhpToValaTranspiler.normalize_runtime_type (transpiler.variable_types[variable_name]);
                if (type_name == "int" || type_name == "long" || type_name == "int64") {
                    return variable_name;
                }
            }
            return "Php.zval_to_int (%s)".printf (converted);
        }
        string? count_expression = convert_native_count_expression (clean, php_line_number);
        if (count_expression != null) {
            return count_expression;
        }
        foreach (string op in new string[] {"+", "-", "*", "%"}) {
            int index = transpiler.find_top_level_operator_index (clean, op);
            if (index > 0) {
                string? left = convert_native_int_expression (clean.substring (0, index).strip (), php_line_number);
                string? right = convert_native_int_expression (clean.substring (index + op.length).strip (), php_line_number);
                if (left != null && right != null) {
                    return "(%s %s %s)".printf (left, op, right);
                }
            }
        }
        return null;
    }

    private bool can_convert_to_native_string_expression (string expression) {
        string clean = strip_balanced_outer_parens (PhpToValaTranspiler.trim_trailing_semicolon (expression.strip ()));
        if (is_string_literal (clean)) {
            return true;
        }
        if (is_numeric_literal (clean)) {
            return true;
        }
        if (is_standalone_php_variable_access (clean)) {
            string variable_name = aliased_variable_name (clean);
            if (!transpiler.variable_types.has_key (variable_name)) {
                return false;
            }
            string type_name = PhpToValaTranspiler.normalize_runtime_type (transpiler.variable_types[variable_name]);
            return type_name == "string" || type_name == "int" || type_name == "long" || type_name == "int64" || type_name == "double" || type_name == "float";
        }
        var parts = transpiler.split_top_level (clean, '.');
        if (parts.size <= 1) {
            return false;
        }
        foreach (string part in parts) {
            if (!can_convert_to_native_string_expression (part.strip ())) {
                return false;
            }
        }
        return true;
    }

    public string? convert_native_string_expression (string expression, int php_line_number) throws Error {
        string clean = strip_balanced_outer_parens (PhpToValaTranspiler.trim_trailing_semicolon (expression.strip ()));
        if (is_string_literal (clean)) {
            return transpiler.convert_string_literal_public (clean);
        }
        if (is_numeric_literal (clean)) {
            return "(%s).to_string ()".printf (clean);
        }
        if (is_standalone_php_variable_access (clean)) {
            string variable_name = aliased_variable_name (clean);
            if (transpiler.variable_types.has_key (variable_name)) {
                string type_name = PhpToValaTranspiler.normalize_runtime_type (transpiler.variable_types[variable_name]);
                if (type_name == "string") {
                    return variable_name;
                }
                if (type_name == "int" || type_name == "long" || type_name == "int64" || type_name == "double" || type_name == "float") {
                    return "%s.to_string ()".printf (variable_name);
                }
            }
            return "Php.zval_to_string (%s)".printf (transpiler.convert_zval_expression (clean, php_line_number));
        }
        var parts = transpiler.split_top_level (clean, '.');
        if (parts.size <= 1) {
            return null;
        }
        var converted = new ArrayList<string> ();
        foreach (string part in parts) {
            string? value = convert_native_string_expression (part.strip (), php_line_number);
            if (value == null) {
                return null;
            }
            converted.add (value);
        }
        return string.joinv (" + ", converted.to_array ());
    }
    public string? convert_native_double_expression (string expression, int php_line_number) throws Error {
        string clean = strip_balanced_outer_parens (PhpToValaTranspiler.trim_trailing_semicolon (expression.strip ()));
        if (is_microtime_true_expression (clean)) {
            return "%s (%s)".printf (
                transpiler.map_php_runtime_function_name ("microtime", "microtime_float_static", php_line_number),
                transpiler.source_location_args (php_line_number).substring (2)
            );
        }
        if (is_numeric_literal (clean)) {
            return clean;
        }
        string? count_expression = convert_native_count_expression (clean, php_line_number);
        if (count_expression != null) {
            return count_expression;
        }
        if (is_standalone_php_variable_access (clean)) {
            string converted = transpiler.convert_zval_expression (clean, php_line_number);
            string variable_name = aliased_variable_name (clean);
            if (transpiler.variable_types.has_key (variable_name)) {
                string type_name = PhpToValaTranspiler.normalize_runtime_type (transpiler.variable_types[variable_name]);
                if (type_name == "double" || type_name == "float" || type_name == "int" || type_name == "long" || type_name == "int64") {
                    return variable_name;
                }
            }
            return "Php.zval_to_double (%s)".printf (converted);
        }
        foreach (string op in new string[] {"+", "-", "*", "/", "%"}) {
            int index = transpiler.find_top_level_operator_index (clean, op);
            if (index > 0) {
                string? left = convert_native_double_expression (clean.substring (0, index).strip (), php_line_number);
                string? right = convert_native_double_expression (clean.substring (index + op.length).strip (), php_line_number);
                if (left == null || right == null) {
                    return null;
                }
                if (op == "/") {
                    return "((double) (%s) / (double) (%s))".printf (left, right);
                }
                return "(%s %s %s)".printf (left, op, right);
            }
        }

        return null;
    }

    private string? convert_native_count_expression (string expression, int php_line_number) throws Error {
        MatchInfo match;
        if (!transpiler.function_expression_regex.match (expression, 0, out match) || match.fetch (1) != "count") {
            return null;
        }
        var args = transpiler.split_top_level (match.fetch (2).strip (), ',');
        if (args.size != 1) {
            return null;
        }
        string zarg = transpiler.convert_zval_expression (args[0].strip (), php_line_number);
        string arg_type = PhpToValaTranspiler.normalize_runtime_type (transpiler.infer_expression_type (zarg));
        if (arg_type.has_prefix ("Gee.ArrayList<") || arg_type.has_prefix ("Gee.HashMap<")) {
            return "%s.size".printf (zarg);
        }
        if (arg_type == "Php.zval") {
            string function_name = transpiler.map_php_runtime_function_name ("count", "count_static", php_line_number);
            return "%s (%s%s)".printf (function_name, zarg, transpiler.source_location_args (php_line_number));
        }
        return null;
    }

}
