#include "coduo_x87emu.h" /* defines EMULATE_X87; x87 shim when it is 1 */

#include "cm_leaf_queries_private.h"
#include "cm_trace_core_private.h"

int32_t
CM_SightTraceThroughTree(const traceWork_t *traceWork,
                         int32_t nodeNum,
                         float startFraction,
                         float endFraction,
                         const vec3_t start,
                         const vec3_t end)
{
    if (nodeNum < 0) {
        return CM_SightTraceThroughLeaf(traceWork, &cm_leafs[-1 - nodeNum]);
    }

    const coduo_collision_node_t *node = &cm_nodes[nodeNum];
    const cplane_t *plane = node->plane;
    float startDistance;
    float endDistance;
    float offset;

    if (plane->type < 3) {
#if EMULATE_X87
        startDistance = x87f_store_f32(
            x87f_sub(x87f_load_f32(start[plane->type]),
                     x87f_load_f32(plane->dist)));
        endDistance = x87f_store_f32(
            x87f_sub(x87f_load_f32(end[plane->type]),
                     x87f_load_f32(plane->dist)));
#else
        startDistance = start[plane->type] - plane->dist;
        endDistance = end[plane->type] - plane->dist;
#endif
        offset = traceWork->maxs[plane->type];
    } else {
        /* As in CM_TraceThroughTree: the original forms each distance as a
         * SINGLE 80-bit chain rounded once at the store (DLL 0x0805c90a:
         * fmul;fmul;faddp;fmul;faddp;fsub dist;fstp). A `d = a*b; d += c*d;
         * ...` form would round the float slot four times and does NOT match
         * the stock binary, so keep it one expression. */
#if EMULATE_X87
        startDistance = x87f_store_f32(x87f_sub(
            x87f_add(x87f_add(x87f_mul(x87f_load_f32(plane->normal[0]),
                                       x87f_load_f32(start[0])),
                              x87f_mul(x87f_load_f32(plane->normal[1]),
                                       x87f_load_f32(start[1]))),
                     x87f_mul(x87f_load_f32(plane->normal[2]),
                              x87f_load_f32(start[2]))),
            x87f_load_f32(plane->dist)));

        endDistance = x87f_store_f32(x87f_sub(
            x87f_add(x87f_add(x87f_mul(x87f_load_f32(plane->normal[0]),
                                       x87f_load_f32(end[0])),
                              x87f_mul(x87f_load_f32(plane->normal[1]),
                                       x87f_load_f32(end[1]))),
                     x87f_mul(x87f_load_f32(plane->normal[2]),
                              x87f_load_f32(end[2]))),
            x87f_load_f32(plane->dist)));
#else
        startDistance = ((plane->normal[0] * start[0]) +
                         (plane->normal[1] * start[1]) +
                         (plane->normal[2] * start[2])) -
                        plane->dist;

        endDistance = ((plane->normal[0] * end[0]) +
                       (plane->normal[1] * end[1]) +
                       (plane->normal[2] * end[2])) -
                      plane->dist;
#endif

        if (traceWork->isPoint == 0) {
            offset = 2048.0f;
        } else {
            offset = 0.0f;
        }
    }

#if EMULATE_X87
    /* offset+1 and -1-offset are formed and compared in 80-bit (no store). */
    const x87f sideOffsetPlusOne =
        x87f_add(x87f_load_f32(offset), x87f_load_f32(1.0f));
    const x87f sideMinusOneMinusOffset =
        x87f_sub(x87f_load_f32(-1.0f), x87f_load_f32(offset));

    if (!x87f_lt(x87f_load_f32(startDistance), sideOffsetPlusOne) &&
        !x87f_lt(x87f_load_f32(endDistance), sideOffsetPlusOne)) {
        return CM_SightTraceThroughTree(traceWork, node->children[0],
                                        startFraction, endFraction, start,
                                        end);
    }

    if (x87f_lt(x87f_load_f32(startDistance), sideMinusOneMinusOffset) &&
        x87f_lt(x87f_load_f32(endDistance), sideMinusOneMinusOffset)) {
        return CM_SightTraceThroughTree(traceWork, node->children[1],
                                        startFraction, endFraction, start,
                                        end);
    }
#else
    if (startDistance >= offset + 1.0f &&
        endDistance >= offset + 1.0f) {
        return CM_SightTraceThroughTree(traceWork, node->children[0],
                                        startFraction, endFraction, start,
                                        end);
    }

    if (startDistance < -1.0f - offset &&
        endDistance < -1.0f - offset) {
        return CM_SightTraceThroughTree(traceWork, node->children[1],
                                        startFraction, endFraction, start,
                                        end);
    }
#endif

    int32_t side;
    float firstFraction;
    float secondFraction;

    if (startDistance < endDistance) {
#if EMULATE_X87
        const float inverseDistance = x87f_store_f32(
            x87f_div(x87f_load_f32(1.0f),
                     x87f_sub(x87f_load_f32(startDistance),
                              x87f_load_f32(endDistance))));

        side = 1;
        firstFraction = x87f_store_f32(x87f_mul(
            x87f_add(x87f_add(x87f_load_f32(startDistance),
                              x87f_load_f32(offset)),
                     x87f_load_f32(0.125f)),
            x87f_load_f32(inverseDistance)));
        secondFraction = x87f_store_f32(x87f_mul(
            x87f_add(x87f_sub(x87f_load_f32(startDistance),
                              x87f_load_f32(offset)),
                     x87f_load_f32(0.125f)),
            x87f_load_f32(inverseDistance)));
#else
        const float inverseDistance = 1.0f / (startDistance - endDistance);

        side = 1;
        firstFraction = (startDistance + offset + 0.125f) * inverseDistance;
        secondFraction = (startDistance - offset + 0.125f) * inverseDistance;
#endif
    } else if (endDistance < startDistance) {
#if EMULATE_X87
        const float inverseDistance = x87f_store_f32(
            x87f_div(x87f_load_f32(1.0f),
                     x87f_sub(x87f_load_f32(startDistance),
                              x87f_load_f32(endDistance))));

        side = 0;
        firstFraction = x87f_store_f32(x87f_mul(
            x87f_sub(x87f_sub(x87f_load_f32(startDistance),
                              x87f_load_f32(offset)),
                     x87f_load_f32(0.125f)),
            x87f_load_f32(inverseDistance)));
        secondFraction = x87f_store_f32(x87f_mul(
            x87f_add(x87f_add(x87f_load_f32(startDistance),
                              x87f_load_f32(offset)),
                     x87f_load_f32(0.125f)),
            x87f_load_f32(inverseDistance)));
#else
        const float inverseDistance = 1.0f / (startDistance - endDistance);

        side = 0;
        firstFraction =
            (startDistance - offset - 0.125f) * inverseDistance;
        secondFraction = (startDistance + offset + 0.125f) * inverseDistance;
#endif
    } else {
        side = 0;
        firstFraction = 0.0f;
        secondFraction = 1.0f;
    }

    if (secondFraction < 0.0f) {
        secondFraction = 0.0f;
    }
    if (secondFraction > 1.0f) {
        secondFraction = 1.0f;
    }

#if EMULATE_X87
    const float secondMidFraction = x87f_store_f32(x87f_add(
        x87f_load_f32(startFraction),
        x87f_mul(x87f_sub(x87f_load_f32(endFraction),
                          x87f_load_f32(startFraction)),
                 x87f_load_f32(secondFraction))));
    vec3_t mid;

    for (int32_t axis = 0; axis < 3; ++axis) {
        mid[axis] = x87f_store_f32(x87f_add(
            x87f_load_f32(start[axis]),
            x87f_mul(x87f_sub(x87f_load_f32(end[axis]),
                              x87f_load_f32(start[axis])),
                     x87f_load_f32(secondFraction))));
    }
#else
    const float secondMidFraction =
        startFraction + (endFraction - startFraction) * secondFraction;
    vec3_t mid;

    mid[0] = start[0] + (end[0] - start[0]) * secondFraction;
    mid[1] = start[1] + (end[1] - start[1]) * secondFraction;
    mid[2] = start[2] + (end[2] - start[2]) * secondFraction;
#endif

    const int32_t sightHit =
        CM_SightTraceThroughTree(traceWork, node->children[side],
                                 startFraction, secondMidFraction, start,
                                 mid);
    if (sightHit != 0) {
        return sightHit;
    }

    if (firstFraction < 0.0f) {
        firstFraction = 0.0f;
    }
    if (firstFraction > 1.0f) {
        firstFraction = 1.0f;
    }

#if EMULATE_X87
    const float firstMidFraction = x87f_store_f32(x87f_add(
        x87f_load_f32(startFraction),
        x87f_mul(x87f_sub(x87f_load_f32(endFraction),
                          x87f_load_f32(startFraction)),
                 x87f_load_f32(firstFraction))));

    for (int32_t axis = 0; axis < 3; ++axis) {
        mid[axis] = x87f_store_f32(x87f_add(
            x87f_load_f32(start[axis]),
            x87f_mul(x87f_sub(x87f_load_f32(end[axis]),
                              x87f_load_f32(start[axis])),
                     x87f_load_f32(firstFraction))));
    }
#else
    const float firstMidFraction =
        startFraction + (endFraction - startFraction) * firstFraction;

    mid[0] = start[0] + (end[0] - start[0]) * firstFraction;
    mid[1] = start[1] + (end[1] - start[1]) * firstFraction;
    mid[2] = start[2] + (end[2] - start[2]) * firstFraction;
#endif

    return CM_SightTraceThroughTree(traceWork, node->children[side ^ 1],
                                    firstMidFraction, endFraction, mid, end);
}
