using Gee;

public class PhpToValaIrBuilder : Object {
    public static string debug_ir_dump (PhpIrProgram ir_program) {
        var output = new StringBuilder ();
        output.append ("# php2vala IR dump\n");
        output.append_printf ("functions\t%d\n", ir_program.functions.size);
        foreach (LocalFunction local_function in ir_program.functions) {
            output.append_printf (
                "function\t%d\t%s\tparams=%d\treturn=%s\tstatements=%d\n",
                local_function.line,
                local_function.name,
                local_function.parameters.size,
                local_function.return_type ?? "",
                ir_program.statements_for_function (local_function).size
            );
            output.append ("line\tcolumn\tkind\tassign\tassign_expr\tcontrol\treturn_expr\ttext\n");
            foreach (PhpIrStatement statement in ir_program.statements_for_function (local_function)) {
                output.append_printf (
                    "%d\t%d\t%s\t%s\t%s\t%s\t%s\t%s\n",
                    statement.source_line,
                    statement.source_column,
                    ir_statement_kind_name (statement.kind),
                    escape_debug_field (statement.assignment_name ?? ""),
                    escape_debug_field (statement.assignment_expression ?? ""),
                    escape_debug_field (statement.control_keyword ?? ""),
                    escape_debug_field (statement.return_expression ?? ""),
                    escape_debug_field (statement.text)
                );
            }
        }
        output.append_printf ("classes\t%d\n", ir_program.classes.size);
        foreach (PhpClass php_class in ir_program.classes) {
            output.append_printf (
                "class\t%d\t%s\textends=%s\tmethods=%d\n",
                php_class.line,
                php_class.name,
                php_class.extends_name,
                php_class.methods.size
            );
            foreach (LocalFunction method in php_class.methods) {
                output.append_printf (
                    "method\t%d\t%s::%s\tparams=%d\treturn=%s\tstatements=%d\n",
                    method.line,
                    php_class.name,
                    method.name,
                    method.parameters.size,
                    method.return_type ?? "",
                    ir_program.statements_for_method (php_class, method).size
                );
                output.append ("line\tcolumn\tkind\tassign\tassign_expr\tcontrol\treturn_expr\ttext\n");
                foreach (PhpIrStatement statement in ir_program.statements_for_method (php_class, method)) {
                    output.append_printf (
                        "%d\t%d\t%s\t%s\t%s\t%s\t%s\t%s\n",
                        statement.source_line,
                        statement.source_column,
                        ir_statement_kind_name (statement.kind),
                        escape_debug_field (statement.assignment_name ?? ""),
                        escape_debug_field (statement.assignment_expression ?? ""),
                    escape_debug_field (statement.control_keyword ?? ""),
                    escape_debug_field (statement.return_expression ?? ""),
                    escape_debug_field (statement.text)
                    );
                }
            }
        }
        output.append_printf ("main_statements\t%d\n", ir_program.main_statements.size);
        output.append ("line\tcolumn\tkind\tassign\tassign_expr\tcontrol\treturn_expr\ttext\n");
        foreach (PhpIrStatement statement in ir_program.main_statements) {
            output.append_printf (
                "%d\t%d\t%s\t%s\t%s\t%s\t%s\t%s\n",
                statement.source_line,
                statement.source_column,
                ir_statement_kind_name (statement.kind),
                escape_debug_field (statement.assignment_name ?? ""),
                escape_debug_field (statement.assignment_expression ?? ""),
                    escape_debug_field (statement.control_keyword ?? ""),
                    escape_debug_field (statement.return_expression ?? ""),
                    escape_debug_field (statement.text)
            );
        }
        return output.str;
    }

    private static string ir_statement_kind_name (PhpIrStatementKind kind) {
        switch (kind) {
        case PhpIrStatementKind.COMMENT:
            return "COMMENT";
        case PhpIrStatementKind.EXPRESSION:
            return "EXPRESSION";
        case PhpIrStatementKind.CONTROL:
            return "CONTROL";
        case PhpIrStatementKind.DECLARATION:
            return "DECLARATION";
        default:
            return "UNKNOWN";
        }
    }

    private static string escape_debug_field (string value) {
        return value
            .replace ("\\", "\\\\")
            .replace ("\t", "\\t")
            .replace ("\r", "\\r")
            .replace ("\n", "\\n");
    }

    public static PhpIrProgram lower_ast (PhpAstProgram ast_program) {
        var ir_program = new PhpIrProgram (
            lower_ast_statements (ast_program.main_statements),
            ast_program.functions,
            ast_program.classes
        );

        foreach (LocalFunction local_function in ast_program.functions) {
            ir_program.function_statements[local_function.name] = lower_ast_statements (ast_program.statements_for_function (local_function));
        }
        foreach (PhpClass php_class in ast_program.classes) {
            foreach (LocalFunction method in php_class.methods) {
                ir_program.method_statements[PhpIrProgram.method_statement_key (php_class.name, method.name)] = lower_ast_statements (ast_program.statements_for_method (php_class, method));
            }
        }

        return ir_program;
    }

    public static PhpIrProgram parse_ir (SourceParts source_parts) throws Error {
        return lower_ast (PhpToValaAstBuilder.parse_ast (source_parts));
    }

    public static ArrayList<PhpIrStatement> lower_ast_statements (ArrayList<PhpAstStatement> ast_statements) {
        var ir_statements = new ArrayList<PhpIrStatement> ();

        foreach (PhpAstStatement statement in ast_statements) {
            string? assignment_name;
            string? assignment_expression;
            string? control_keyword;
            string? return_expression;
            parse_ir_assignment (statement.text, out assignment_name, out assignment_expression);
            parse_ir_control (statement.text, out control_keyword, out return_expression);
            ir_statements.add (new PhpIrStatement (
                statement.text,
                statement.line,
                statement.column,
                ir_statement_kind_from_ast (statement.kind),
                statement,
                assignment_name,
                assignment_expression,
                control_keyword,
                return_expression
            ));
        }

        return ir_statements;
    }

    private static void parse_ir_assignment (string statement_text, out string? assignment_name, out string? assignment_expression) {
        assignment_name = null;
        assignment_expression = null;

        string statement = statement_text.strip ();
        if (!statement.has_prefix ("$") || !statement.has_suffix (";")) {
            return;
        }

        int equals_index = find_top_level_assignment_index (statement);
        if (equals_index <= 1) {
            return;
        }

        string name = statement.substring (1, equals_index - 1).strip ();
        if (!is_identifier (name)) {
            return;
        }

        assignment_name = name;
        assignment_expression = trim_trailing_semicolon (statement.substring (equals_index + 1).strip ());
    }

    private static void parse_ir_control (string statement_text, out string? control_keyword, out string? return_expression) {
        control_keyword = null;
        return_expression = null;

        string statement = statement_text.strip ();
        if (statement == "") {
            return;
        }

        string lowered = statement.down ();
        string first_word = lowered;
        int space_index = first_word.index_of_char (' ');
        int paren_index = first_word.index_of_char ('(');
        int brace_index = first_word.index_of_char ('{');
        int cut_index = first_positive_index (space_index, paren_index, brace_index);
        if (cut_index >= 0) {
            first_word = first_word.substring (0, cut_index);
        }

        switch (first_word) {
        case "return":
            control_keyword = "return";
            return_expression = trim_trailing_semicolon (statement.substring ("return".length).strip ());
            break;
        case "exit":
        case "die":
            control_keyword = first_word;
            break;
        default:
            switch (first_word) {
            case "break":
            case "case":
            case "catch":
            case "continue":
            case "default":
            case "do":
            case "else":
            case "elseif":
            case "finally":
            case "for":
            case "foreach":
            case "if":
            case "switch":
            case "throw":
            case "try":
            case "while":
                control_keyword = first_word;
                break;
            default:
                break;
            }
            break;
        }
    }

    public static ArrayList<PhpIrStatement> parse_ir_statements (string body, int first_line_number = 1) throws Error {
        return lower_ast_statements (PhpToValaAstBuilder.parse_ast_statements (body, first_line_number));
    }

    public static Statements statements_from_ir (ArrayList<PhpIrStatement> ir_statements) {
        var statements = new Statements ();

        foreach (PhpIrStatement statement in ir_statements) {
            statements.texts.add (statement.text);
            statements.lines.add (statement.source_line);
            statements.columns.add (statement.source_column);
        }

        return statements;
    }

    private static PhpIrStatementKind ir_statement_kind_from_ast (PhpAstStatementKind kind) {
        switch (kind) {
        case PhpAstStatementKind.COMMENT:
            return PhpIrStatementKind.COMMENT;
        case PhpAstStatementKind.EXPRESSION:
            return PhpIrStatementKind.EXPRESSION;
        case PhpAstStatementKind.CONTROL:
            return PhpIrStatementKind.CONTROL;
        case PhpAstStatementKind.DECLARATION:
            return PhpIrStatementKind.DECLARATION;
        default:
            return PhpIrStatementKind.UNKNOWN;
        }
    }

    private static int first_positive_index (int a, int b, int c) {
        int result = -1;
        if (a >= 0) {
            result = a;
        }
        if (b >= 0 && (result < 0 || b < result)) {
            result = b;
        }
        if (c >= 0 && (result < 0 || c < result)) {
            result = c;
        }
        return result;
    }

    private static int find_top_level_assignment_index (string expression) {
        bool in_single_quote = false;
        bool in_double_quote = false;
        int depth = 0;

        for (int i = 0; i < expression.length; i++) {
            char c = expression[i];
            if (c == '\'' && !in_double_quote) {
                in_single_quote = !in_single_quote;
            } else if (c == '"' && !in_single_quote) {
                in_double_quote = !in_double_quote;
            } else if (!in_single_quote && !in_double_quote) {
                if (c == '(' || c == '[' || c == '{') {
                    depth++;
                } else if (c == ')' || c == ']' || c == '}') {
                    depth--;
                } else if (c == '=' && depth == 0) {
                    char previous = i > 0 ? expression[i - 1] : '\0';
                    char next = i + 1 < expression.length ? expression[i + 1] : '\0';
                    if (previous != '=' && previous != '!' && previous != '<' && previous != '>' && next != '=') {
                        return i;
                    }
                }
            }
        }

        return -1;
    }

    private static string trim_trailing_semicolon (string expression) {
        string clean = expression.strip ();
        if (clean.has_suffix (";")) {
            return clean.substring (0, clean.length - 1).strip ();
        }
        return clean;
    }


    private static bool is_identifier (string name) {
        if (name == "") {
            return false;
        }
        for (int i = 0; i < name.length; i++) {
            char c = name[i];
            if (!(c.isalnum () || c == '_')) {
                return false;
            }
        }
        return true;
    }
}
