//
// Created by servostar on 5/28/24.
//

#include <llvm/llvm-ir/expr.h>
#include <llvm/llvm-ir/types.h>
#include <sys/log.h>
#include <mem/cache.h>

BackendError impl_bitwise_operation(LLVMBackendCompileUnit *unit,
                                    LLVMLocalScope *scope,
                                    LLVMBuilderRef builder,
                                    Operation *operation,
                                    LLVMValueRef *llvm_result) {
    Expression *rhs = NULL;
    Expression *lhs = NULL;
    LLVMValueRef llvm_rhs = NULL;
    LLVMValueRef llvm_lhs = NULL;

    if (operation->impl.bitwise == BitwiseNot) {
        // single operand
        rhs = g_array_index(operation->operands, Expression*, 0);
        impl_expr(unit, scope, builder, rhs, FALSE, 0, &llvm_rhs);
    } else {
        // two operands
        lhs = g_array_index(operation->operands, Expression*, 0);
        impl_expr(unit, scope, builder, lhs, FALSE, 0, &llvm_lhs);

        rhs = g_array_index(operation->operands, Expression*, 1);
        impl_expr(unit, scope, builder, rhs, FALSE, 0, &llvm_rhs);
    }

    switch (operation->impl.bitwise) {
        case BitwiseAnd:
            *llvm_result = LLVMBuildAnd(builder, llvm_lhs, llvm_rhs, "bitwise and");
            break;
        case BitwiseOr:
            *llvm_result = LLVMBuildOr(builder, llvm_lhs, llvm_rhs, "bitwise or");
            break;
        case BitwiseXor:
            *llvm_result = LLVMBuildXor(builder, llvm_lhs, llvm_rhs, "bitwise xor");
            break;
        case BitwiseNot:
            *llvm_result = LLVMBuildNot(builder, llvm_rhs, "bitwise not");
            break;
    }

    return SUCCESS;
}

/**
 * @brief Convert any integral type (integer) to a boolean value.
 *        A boolean value hereby meaning an integer of the same type as the input
 *        value but with the value of either 0 or one.
 * @param builder
 * @param integral
 * @return
 */
 [[maybe_unused]]
static LLVMValueRef convert_integral_to_boolean(
        LLVMBuilderRef builder, LLVMValueRef integral) {
    // type of input
    LLVMTypeRef valueType = LLVMTypeOf(integral);
    // zero value of same type as integral
    LLVMValueRef zero = LLVMConstIntOfString(valueType, "0", 10);
    // returns 1 if integral is not zero and zero otherwise
    return LLVMBuildICmp(builder, LLVMIntNE, zero, integral, "to boolean");
}

BackendError impl_logical_operation(LLVMBackendCompileUnit *unit,
                                    LLVMLocalScope *scope,
                                    LLVMBuilderRef builder,
                                    Operation *operation,
                                    LLVMValueRef *llvm_result) {
    Expression *rhs = NULL;
    Expression *lhs = NULL;
    LLVMValueRef llvm_rhs = NULL;
    LLVMValueRef llvm_lhs = NULL;

    if (operation->impl.logical == LogicalNot) {
        // single operand
        rhs = g_array_index(operation->operands, Expression*, 0);
        impl_expr(unit, scope, builder, rhs, FALSE, 0, &llvm_rhs);
    } else {
        // two operands
        lhs = g_array_index(operation->operands, Expression*, 0);
        impl_expr(unit, scope, builder, lhs, FALSE, 0, &llvm_lhs);

        rhs = g_array_index(operation->operands, Expression*, 1);
        impl_expr(unit, scope, builder, rhs, FALSE, 0, &llvm_rhs);
    }

    switch (operation->impl.logical) {
        case LogicalAnd:
            *llvm_result = LLVMBuildAnd(builder, llvm_lhs, llvm_rhs, "logical and");
            break;
        case LogicalOr:
            *llvm_result = LLVMBuildOr(builder, llvm_lhs, llvm_rhs, "logical or");
            break;
        case LogicalXor:
            *llvm_result = LLVMBuildXor(builder, llvm_lhs, llvm_rhs, "logical xor");
            break;
        case LogicalNot:
            *llvm_result = LLVMBuildNot(builder, llvm_rhs, "logical not");
            break;
    }

    return SUCCESS;
}

static LLVMBool is_floating_point(Type *value) {
    if (value->kind == TypeKindPrimitive) {
        return value->impl.primitive == Float;
    }

    if (value->kind == TypeKindComposite) {
        return value->impl.composite.primitive == Float;
    }

    return FALSE;
}

static LLVMBool is_integral(Type *value) {
    if (value->kind == TypeKindPrimitive) {
        return value->impl.primitive == Int;
    }

    if (value->kind == TypeKindComposite) {
        return value->impl.composite.primitive == Int;
    }

    return FALSE;
}

BackendError impl_relational_operation(LLVMBackendCompileUnit *unit,
                                       LLVMLocalScope *scope,
                                       LLVMBuilderRef builder,
                                       Operation *operation,
                                       LLVMValueRef *llvm_result) {
    Expression *rhs = NULL;
    Expression *lhs = NULL;
    LLVMValueRef llvm_rhs = NULL;
    LLVMValueRef llvm_lhs = NULL;

    // two operands
    lhs = g_array_index(operation->operands, Expression*, 0);
    impl_expr(unit, scope, builder, lhs, FALSE, 0, &llvm_lhs);

    rhs = g_array_index(operation->operands, Expression*, 1);
    impl_expr(unit, scope, builder, rhs, FALSE, 0, &llvm_rhs);

    if (is_integral(rhs->result)) {
        // integral type
        LLVMIntPredicate operator = 0;

        switch (operation->impl.relational) {
            case Equal:
                operator = LLVMIntEQ;
                break;
            case Greater:
                operator = LLVMIntSGT;
                break;
            case Less:
                operator = LLVMIntSLT;
                break;
        }

        *llvm_result = LLVMBuildICmp(builder, operator, llvm_lhs, llvm_rhs, "integral comparison");

    } else if (is_floating_point(rhs->result)) {
        // integral type
        LLVMRealPredicate operator = 0;

        switch (operation->impl.relational) {
            case Equal:
                operator = LLVMRealOEQ;
                break;
            case Greater:
                operator = LLVMRealOGT;
                break;
            case Less:
                operator = LLVMRealOLT;
                break;
        }

        *llvm_result = LLVMBuildFCmp(builder, operator, llvm_lhs, llvm_rhs, "floating point comparison");
    } else {
        PANIC("invalid type for relational operator");
    }

    // *llvm_result = convert_integral_to_boolean(builder, *llvm_result);

    return SUCCESS;
}

BackendError impl_arithmetic_operation(LLVMBackendCompileUnit *unit,
                                       LLVMLocalScope *scope,
                                       LLVMBuilderRef builder,
                                       Operation *operation,
                                       LLVMValueRef *llvm_result) {
    Expression *rhs = NULL;
    Expression *lhs = NULL;
    LLVMValueRef llvm_rhs = NULL;
    LLVMValueRef llvm_lhs = NULL;

    if (operation->impl.arithmetic == Negate) {
        // single operand
        rhs = g_array_index(operation->operands, Expression*, 0);
        impl_expr(unit, scope, builder, rhs, FALSE, 0, &llvm_rhs);
    } else {
        // two operands
        lhs = g_array_index(operation->operands, Expression*, 0);
        impl_expr(unit, scope, builder, lhs, FALSE, 0, &llvm_lhs);

        rhs = g_array_index(operation->operands, Expression*, 1);
        impl_expr(unit, scope, builder, rhs, FALSE, 0, &llvm_rhs);
    }

    if (is_integral(rhs->result)) {

        switch (operation->impl.arithmetic) {
            case Add:
                *llvm_result = LLVMBuildNSWAdd(builder, llvm_lhs, llvm_rhs, "signed integer addition");
                break;
            case Sub:
                *llvm_result = LLVMBuildNSWSub(builder, llvm_lhs, llvm_rhs, "signed integer subtraction");
                break;
            case Mul:
                *llvm_result = LLVMBuildNSWMul(builder, llvm_lhs, llvm_rhs, "signed integer multiply");
                break;
            case Div:
                *llvm_result = LLVMBuildSDiv(builder, llvm_lhs, llvm_rhs, "signed integer divide");
                break;
            case Negate:
                *llvm_result = LLVMBuildNeg(builder, llvm_rhs, "signed integer negate");
                break;
        }

    } else if (is_floating_point(rhs->result)) {

        switch (operation->impl.arithmetic) {
            case Add:
                *llvm_result = LLVMBuildFAdd(builder, llvm_lhs, llvm_rhs, "floating point addition");
                break;
            case Sub:
                *llvm_result = LLVMBuildFSub(builder, llvm_lhs, llvm_rhs, "floating point subtraction");
                break;
            case Mul:
                *llvm_result = LLVMBuildFMul(builder, llvm_lhs, llvm_rhs, "floating point multiply");
                break;
            case Div:
                *llvm_result = LLVMBuildFDiv(builder, llvm_lhs, llvm_rhs, "floating point divide");
                break;
            case Negate:
                *llvm_result = LLVMBuildFNeg(builder, llvm_rhs, "floating point negate");
                break;
        }

    } else {
        PANIC("invalid type for arithmetic operator");
    }

    return SUCCESS;
}

BackendError impl_operation(LLVMBackendCompileUnit *unit, LLVMLocalScope *scope,
                            LLVMBuilderRef builder, Operation *operation,
                            LLVMValueRef *llvm_result) {
    BackendError err;

    switch (operation->kind) {
        case Bitwise:
            err = impl_bitwise_operation(unit, scope, builder, operation,
                                         llvm_result);
            break;
        case Boolean:
            err = impl_logical_operation(unit, scope, builder, operation,
                                         llvm_result);
            break;
        case Relational:
            err = impl_relational_operation(unit, scope, builder, operation,
                                            llvm_result);
            break;
        case Arithmetic:
            err = impl_arithmetic_operation(unit, scope, builder, operation,
                                            llvm_result);
            break;
        default:
            PANIC("Invalid operator");
    }

    return err;
}

BackendError impl_transmute(LLVMBackendCompileUnit *unit, LLVMLocalScope *scope,
                            LLVMBuilderRef builder, Transmute *transmute,
                            LLVMValueRef *llvm_result) {

    LLVMValueRef operand = NULL;
    impl_expr(unit, scope, builder, transmute->operand, FALSE, 0, &operand);

    LLVMTypeRef target_type = NULL;
    BackendError err = get_type_impl(unit, scope->func_scope->global_scope,
                                     transmute->targetType, &target_type);
    // if target type is valid
    if (err.kind == Success) {
        *llvm_result =
                LLVMBuildBitCast(builder, operand, target_type, "transmute");
    }

    return err;
}

static LLVMBool is_type_signed(const Type *type) {
    switch (type->kind) {
        case TypeKindPrimitive:
            return 1;
        case TypeKindComposite:
            return type->impl.composite.sign == Signed;
        default:
            return 0;
    }
}

BackendError impl_typecast(LLVMBackendCompileUnit *unit, LLVMLocalScope *scope,
                           LLVMBuilderRef builder, TypeCast *typecast,
                           bool reference,
                           LLVMValueRef *llvm_result) {
    LLVMValueRef operand = NULL;
    impl_expr(unit, scope, builder, typecast->operand, reference, 0, &operand);

    LLVMTypeRef target_type = NULL;
    BackendError err = get_type_impl(unit, scope->func_scope->global_scope,
                                     typecast->targetType, &target_type);
    // if target type is valid
    if (err.kind != Success) {
        return err;
    }

    LLVMBool dst_signed = is_type_signed(typecast->targetType);
    LLVMBool src_signed = is_type_signed(typecast->operand->result);
    const LLVMOpcode opcode =
            LLVMGetCastOpcode(operand, src_signed, target_type, dst_signed);
    *llvm_result =
            LLVMBuildCast(builder, opcode, operand, target_type, "expr.typecast");

    return err;
}

BackendError impl_variable_load(LLVMBackendCompileUnit *unit, LLVMLocalScope *scope,
                                LLVMBuilderRef builder, Variable *variable,
                                LLVMBool reference,
                                LLVMValueRef *llvm_result) {

    LLVMValueRef llvm_variable = get_variable(scope, variable->name);

    Type* type;

    if (variable->kind == VariableKindDefinition) {
        type = variable->impl.definiton.declaration.type;
    } else {
        type = variable->impl.declaration.type;
    }

    if (llvm_variable == NULL) {
        return new_backend_impl_error(Implementation, NULL, "Variable not found");
    }

    if (reference) {
        // don't load in case variable is parameter?
        // only reference wanted

        *llvm_result = llvm_variable;

    } else {
        // no referencing, load value
        LLVMTypeRef llvm_type;

        get_type_impl(unit, scope->func_scope->global_scope, type, &llvm_type);

        if (LLVMGetTypeKind(LLVMTypeOf(llvm_variable)) == LLVMPointerTypeKind) {
            *llvm_result = LLVMBuildLoad2(builder, llvm_type, llvm_variable, "");
        } else {
            *llvm_result = llvm_variable;
        }
    }

    return SUCCESS;
}

BackendError impl_parameter_load(LLVMBackendCompileUnit *unit, LLVMLocalScope *scope,
                                LLVMBuilderRef builder, Parameter *parameter,
                                LLVMBool reference,
                                LLVMValueRef *llvm_result) {

    LLVMValueRef llvm_variable = NULL;
    if (g_hash_table_contains(scope->func_scope->params, parameter->name)) {
        llvm_variable = g_hash_table_lookup(scope->func_scope->params, parameter->name);
    }

    Type* type;

    ParameterDeclaration decl;

    if (parameter->kind == ParameterDeclarationKind) {
        decl = parameter->impl.definiton.declaration;
    } else {
        decl = parameter->impl.declaration;
    }
    type = decl.type;

    if (llvm_variable == NULL) {
        return new_backend_impl_error(Implementation, NULL, "Variable not found");
    }

    if (decl.qualifier == In || reference) {
        *llvm_result = llvm_variable;
    } else {
        // no referencing, load value
        LLVMTypeRef llvm_type;

        get_type_impl(unit, scope->func_scope->global_scope, type, &llvm_type);

        if (LLVMGetTypeKind(LLVMTypeOf(llvm_variable)) == LLVMPointerTypeKind) {
            *llvm_result = LLVMBuildLoad2(builder, llvm_type, llvm_variable, "");
        } else {
            *llvm_result = llvm_variable;
        }
    }

    return SUCCESS;
}

BackendError impl_address_of(LLVMBackendCompileUnit *unit, LLVMLocalScope *scope,
                                LLVMBuilderRef builder, AddressOf* addressOf,
                                LLVMValueRef *llvm_result) {

    BackendError err = impl_expr(unit, scope, builder, addressOf->variable, TRUE, 0, llvm_result);

    if (err.kind != Success) {
        return err;
    }

    return SUCCESS;
}

BackendError impl_deref(LLVMBackendCompileUnit *unit, LLVMLocalScope *scope,
                             LLVMBuilderRef builder, Dereference* dereference,
                             bool reference,
                             uint32_t deref_depth,
                             LLVMValueRef *llvm_result) {
    BackendError err;

    LLVMValueRef llvm_pointer = NULL;
    err = impl_expr(unit, scope, builder, dereference->variable, false, deref_depth + 1, &llvm_pointer);
    if (err.kind != Success) {
        return err;
    }

    LLVMTypeRef llvm_deref_type = NULL;
    err = get_type_impl(unit, scope->func_scope->global_scope, dereference->variable->result->impl.reference, &llvm_deref_type);
    if (err.kind != Success) {
        return err;
    }

    LLVMValueRef* index = mem_alloc(MemoryNamespaceLlvm, sizeof(LLVMValueRef));
    err = impl_expr(unit, scope, builder, dereference->index, FALSE, deref_depth + 1, index);
    if (err.kind != Success) {
        return err;
    }

    *llvm_result = LLVMBuildGEP2(builder, llvm_deref_type, llvm_pointer, index, 1, "expr.deref.gep2");

    if (!reference || deref_depth > 0) {
        *llvm_result = LLVMBuildLoad2(builder, llvm_deref_type, *llvm_result, "expr.deref.load");
    }

    return err;
}

BackendError impl_expr(LLVMBackendCompileUnit *unit, LLVMLocalScope *scope,
                       LLVMBuilderRef builder, Expression *expr,
                       LLVMBool reference,
                       uint32_t deref_depth,
                       LLVMValueRef *llvm_result) {
    DEBUG("implementing expression: %ld", expr->kind);
    BackendError err = SUCCESS;

    switch (expr->kind) {
        case ExpressionKindConstant:
            err = get_const_type_value(unit, scope->func_scope->global_scope,
                                       &expr->impl.constant, llvm_result);
            break;
        case ExpressionKindTransmute:
            err = impl_transmute(unit, scope, builder, &expr->impl.transmute,
                                 llvm_result);
            break;
        case ExpressionKindTypeCast:
            err = impl_typecast(unit, scope, builder, &expr->impl.typecast,
                                reference,
                                llvm_result);
            break;
        case ExpressionKindOperation:
            err = impl_operation(unit, scope, builder, &expr->impl.operation,
                                 llvm_result);
            break;
        case ExpressionKindVariable:
            err = impl_variable_load(unit, scope, builder, expr->impl.variable,
                                     reference,
                                     llvm_result);
            break;
        case ExpressionKindParameter:
            err = impl_parameter_load(unit, scope, builder, expr->impl.parameter,
                                     reference,
                                     llvm_result);
            break;
        case ExpressionKindAddressOf:
            err = impl_address_of(unit, scope, builder, &expr->impl.addressOf,
                                  llvm_result);
            break;
        case ExpressionKindDereference:
            err = impl_deref(unit, scope, builder, &expr->impl.dereference,
                             reference,
                             deref_depth,
                             llvm_result);
            break;
        default:
            err = new_backend_impl_error(Implementation, NULL, "unknown expression");
            break;
    }

    return err;
}
