/* Utilities to manipulate GCC trees.
   Copyright (C) 2025 Free Software Foundation, Inc.

GCC is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3, or (at your option)
any later version.

GCC is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with GCC; see the file COPYING3.  If not see
<http://www.gnu.org/licenses/>.  */

#include "elna/gcc/elna-tree.h"
#include "elna/gcc/elna-diagnostic.h"
#include "elna/gcc/elna1.h"

#include "function.h"
#include "stor-layout.h"
#include "fold-const.h"
#include "diagnostic-core.h"

namespace elna
{
namespace gcc
{
    bool is_pointer_type(tree type)
    {
        gcc_assert(TYPE_P(type));
        return TREE_CODE(type) == POINTER_TYPE;
    }

    bool is_integral_type(tree type)
    {
        gcc_assert(TYPE_P(type));
        return TREE_CODE(type) == INTEGER_TYPE;
    }

    bool is_numeric_type(tree type)
    {
        return is_integral_type(type) || type == elna_float_type_node;
    }

    bool is_array_type(tree type)
    {
        gcc_assert(TYPE_P(type));
        return TREE_CODE(type) == ARRAY_TYPE;
    }

    bool is_void_type(tree type)
    {
        return type == NULL_TREE || type == void_type_node;
    }

    bool is_aggregate_type(tree type)
    {
        gcc_assert(TYPE_P(type));
        return TREE_CODE(type) == RECORD_TYPE || TREE_CODE(type) == UNION_TYPE;
    }

    bool are_compatible_pointers(tree lhs_type, tree rhs)
    {
        gcc_assert(TYPE_P(lhs_type));
        tree rhs_type = TREE_TYPE(rhs);

        return (is_pointer_type(lhs_type) && rhs == elna_pointer_nil_node)
            || (is_pointer_type(lhs_type) && lhs_type == rhs_type);
    }

    tree prepare_rvalue(tree rvalue)
    {
        if (DECL_P(rvalue) && TREE_CODE(TREE_TYPE(rvalue)) == FUNCTION_TYPE)
        {
            return build1(ADDR_EXPR, build_pointer_type_for_mode(TREE_TYPE(rvalue), VOIDmode, true), rvalue);
        }
        else
        {
            return rvalue;
        }
    }

    bool is_assignable_from(tree assignee, tree assignment)
    {
        return get_qualified_type(TREE_TYPE(assignment), TYPE_UNQUALIFIED) == assignee
            || are_compatible_pointers(assignee, assignment);
    }

    void append_statement(tree statement_tree)
    {
        if (!vec_safe_is_empty(f_binding_level->defers))
        {
            append_to_statement_list(statement_tree, &f_binding_level->defers->begin()->try_statements);
        }
        else
        {
            append_to_statement_list(statement_tree, &f_binding_level->statement_list);
        }
    }

    void defer(tree statement_tree)
    {
        defer_scope new_defer{ statement_tree, alloc_stmt_list() };
        vec_safe_insert(f_binding_level->defers, 0, new_defer);
    }

    tree chain_defer()
    {
        if (vec_safe_is_empty(f_binding_level->defers))
        {
            return f_binding_level->statement_list;
        }
        defer_scope *defer_iterator = f_binding_level->defers->begin();
        tree defer_tree = build2(TRY_FINALLY_EXPR, void_type_node,
                defer_iterator->try_statements, defer_iterator->defer_block);
        int i;

        FOR_EACH_VEC_ELT_FROM(*f_binding_level->defers, i, defer_iterator, 1)
        {
            append_to_statement_list(defer_tree, &defer_iterator->try_statements);
            defer_tree = build2(TRY_FINALLY_EXPR, void_type_node,
                    defer_iterator->try_statements, defer_iterator->defer_block);
        }
        return build2(COMPOUND_EXPR, TREE_TYPE(defer_tree), f_binding_level->statement_list, defer_tree);
    }

    tree build_field(location_t location, tree record_type, const std::string name, tree type)
    {
        tree field_declaration = build_decl(location,
                FIELD_DECL, get_identifier(name.c_str()), type);
        TREE_ADDRESSABLE(field_declaration) = 1;
        DECL_CONTEXT(field_declaration) = record_type;

        return field_declaration;
    }

    tree do_pointer_arithmetic(boot::binary_operator binary_operator, tree left, tree right)
    {
        tree left_type = get_qualified_type(TREE_TYPE(left), TYPE_UNQUALIFIED);
        tree right_type = get_qualified_type(TREE_TYPE(right), TYPE_UNQUALIFIED);
        if (binary_operator == boot::binary_operator::sum)
        {
            tree pointer{ NULL_TREE };
            tree offset{ NULL_TREE };

            if (is_pointer_type(left_type) && is_integral_type(right_type))
            {
                pointer = left;
                offset = right;
            }
            else if (is_integral_type(left_type) && is_pointer_type(right_type))
            {
                pointer = right;
                offset = left;
            }
            else
            {
                return error_mark_node;
            }
            tree size_exp = fold_convert(TREE_TYPE(offset), size_in_bytes(TREE_TYPE(TREE_TYPE(pointer))));

            offset = fold_build2(MULT_EXPR, TREE_TYPE(offset), offset, size_exp);
            offset = fold_convert(sizetype, offset);

            return fold_build2(POINTER_PLUS_EXPR, TREE_TYPE(pointer), pointer, offset);
        }
        else if (binary_operator == boot::binary_operator::subtraction)
        {
            if (is_pointer_type(left_type) && is_integral_type(right_type))
            {
                tree pointer_type = left_type;
                tree offset_type = right_type;
                tree size_exp = fold_convert(offset_type, size_in_bytes(TREE_TYPE(pointer_type)));

                tree convert_expression = fold_build2(MULT_EXPR, offset_type, right, size_exp);
                convert_expression = fold_convert(sizetype, convert_expression);

                convert_expression = fold_build1(NEGATE_EXPR, sizetype, convert_expression);
                return fold_build2(POINTER_PLUS_EXPR, pointer_type, left, convert_expression);
            }
            else if (is_pointer_type(left_type) && is_pointer_type(right_type) && left_type == right_type)
            {
                return fold_build2(POINTER_DIFF_EXPR, ssizetype, left, right);
            }
        }
        gcc_unreachable();
    }

    tree build_binary_operation(bool condition, boot::binary_expression *expression,
            tree_code operator_code, tree left, tree right, tree target_type)
    {
        location_t expression_location = get_location(&expression->position());
        tree left_type = get_qualified_type(TREE_TYPE(left), TYPE_UNQUALIFIED);
        tree right_type = get_qualified_type(TREE_TYPE(right), TYPE_UNQUALIFIED);

        if (condition)
        {
            return build2_loc(expression_location, operator_code, target_type, left, right);
        }
        else
        {
            error_at(expression_location,
                    "invalid operands of type '%s' and '%s' for operator %s",
                    print_type(left_type).c_str(), print_type(right_type).c_str(),
                    elna::boot::print_binary_operator(expression->operation()));
            return error_mark_node;
        }
    }
}
}