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

#include "cm_trace_core_private.h"

void CM_TraceThroughBrush(traceWork_t *traceWork,
                          const coduo_collision_brush_t *brush)
{
    float enterFraction = 0.0f;
    float leaveFraction = traceWork->trace.fraction;
    qboolean allSolid = 1;
    const coduo_collision_brush_side_t *hitSide = NULL;
    cplane_t axialPlane = {{0.0f, 0.0f, 0.0f}, 0.0f, 0, 0, {0, 0}};
    coduo_collision_brush_side_t axialSide = {&axialPlane, 0};

    for (int32_t boundsPass = 0; boundsPass <= 1; ++boundsPass) {
        const float sign = boundsPass == 0 ? -1.0f : 1.0f;
        const float *brushBounds =
            boundsPass == 0 ? brush->mins : brush->maxs;

        for (int32_t axis = 0; axis <= 2; ++axis) {
            float workOffset;

            if (traceWork->sphere.use != 0) {
                workOffset = traceWork->sphereExtents[axis];
            } else {
                workOffset = traceWork->maxs[axis];
            }

#if EMULATE_X87
            /* x87 (DLL 0x08058f0a): each distance is one 80-bit chain
             * (fld start; fsub bounds; fmul sign; fsub workOffset; fstp). */
            const float startDistance = x87f_store_f32(x87f_sub(
                x87f_mul(x87f_sub(x87f_load_f32(traceWork->start[axis]),
                                  x87f_load_f32(brushBounds[axis])),
                         x87f_load_f32(sign)),
                x87f_load_f32(workOffset)));
            const float endDistance = x87f_store_f32(x87f_sub(
                x87f_mul(x87f_sub(x87f_load_f32(traceWork->end[axis]),
                                  x87f_load_f32(brushBounds[axis])),
                         x87f_load_f32(sign)),
                x87f_load_f32(workOffset)));
#else
            const float startDistance =
                (traceWork->start[axis] - brushBounds[axis]) * sign -
                workOffset;
            const float endDistance =
                (traceWork->end[axis] - brushBounds[axis]) * sign -
                workOffset;
#endif

            if (startDistance > 0.0f) {
#if EMULATE_X87
                const float fractionDelta = x87f_store_f32(
                    x87f_sub(x87f_load_f32(startDistance),
                             x87f_load_f32(endDistance)));
#else
                const float fractionDelta = startDistance - endDistance;
#endif

                if (endDistance > 0.0f) {
                    if (fractionDelta <= 0.0f) {
                        return;
                    }
                    if (endDistance >= 0.125f) {
                        return;
                    }
                    allSolid = 0;
                }

                /* The original computes startDistance - 0.125f ONCE into a
                 * float slot and uses that same rounded value for both the
                 * compare and the divide (fld;fld 0.125f;fsubp;fstp, then
                 * fld tmp;fmul/fucompp and fld tmp;fdiv), so keep it in an
                 * explicit temp rather than writing the expression twice. */
#if EMULATE_X87
                const float enterLimit = x87f_store_f32(
                    x87f_sub(x87f_load_f32(startDistance),
                             x87f_load_f32(0.125f)));

                if (x87f_lt(x87f_mul(x87f_load_f32(enterFraction),
                                     x87f_load_f32(fractionDelta)),
                            x87f_load_f32(enterLimit))) {
                    enterFraction = x87f_store_f32(
                        x87f_div(x87f_load_f32(enterLimit),
                                 x87f_load_f32(fractionDelta)));
                    if (leaveFraction <= enterFraction) {
                        return;
                    }
                } else if (hitSide != NULL) {
                    continue;
                }
#else
                const float enterLimit = startDistance - 0.125f;

                if (enterFraction * fractionDelta < enterLimit) {
                    enterFraction = enterLimit / fractionDelta;
                    if (leaveFraction <= enterFraction) {
                        return;
                    }
                } else if (hitSide != NULL) {
                    continue;
                }
#endif

                axialPlane.normal[0] = 0.0f;
                axialPlane.normal[1] = 0.0f;
                axialPlane.normal[2] = 0.0f;
                axialPlane.normal[axis] = sign;
                axialSide.materialIndex =
                    brush->axialMaterialIndices[boundsPass * 3 + axis];
                hitSide = &axialSide;
            } else if (endDistance > 0.0f) {
#if EMULATE_X87
                const float fractionDelta = x87f_store_f32(
                    x87f_sub(x87f_load_f32(startDistance),
                             x87f_load_f32(endDistance)));

                allSolid = 0;
                if (x87f_lt(x87f_mul(x87f_load_f32(leaveFraction),
                                     x87f_load_f32(fractionDelta)),
                            x87f_load_f32(startDistance))) {
                    leaveFraction = x87f_store_f32(
                        x87f_div(x87f_load_f32(startDistance),
                                 x87f_load_f32(fractionDelta)));
                    if (leaveFraction <= enterFraction) {
                        return;
                    }
                }
#else
                const float fractionDelta = startDistance - endDistance;

                allSolid = 0;
                if (leaveFraction * fractionDelta < startDistance) {
                    leaveFraction = startDistance / fractionDelta;
                    if (leaveFraction <= enterFraction) {
                        return;
                    }
                }
#endif
            }
        }
    }

    const coduo_collision_brush_side_t *side = brush->sides;
    for (int32_t sideCount = brush->numSides; sideCount != 0;
         --sideCount, ++side) {
        const cplane_t *plane = side->plane;
        float startDistance;
        float endDistance;

        if (traceWork->sphere.use != 0) {
            const float planeDist = plane->dist + traceWork->sphere.radius;
            const float sphereOffset =
                ((plane->normal[0] * traceWork->sphere.offset[0]) +
                 (plane->normal[1] * traceWork->sphere.offset[1])) +
                (plane->normal[2] * traceWork->sphere.offset[2]);
            vec3_t start;
            vec3_t end;

            if (sphereOffset > 0.0f) {
                start[0] =
                    traceWork->start[0] - traceWork->sphere.offset[0];
                start[1] =
                    traceWork->start[1] - traceWork->sphere.offset[1];
                start[2] =
                    traceWork->start[2] - traceWork->sphere.offset[2];
                end[0] = traceWork->end[0] - traceWork->sphere.offset[0];
                end[1] = traceWork->end[1] - traceWork->sphere.offset[1];
                end[2] = traceWork->end[2] - traceWork->sphere.offset[2];
            } else {
                start[0] =
                    traceWork->start[0] + traceWork->sphere.offset[0];
                start[1] =
                    traceWork->start[1] + traceWork->sphere.offset[1];
                start[2] =
                    traceWork->start[2] + traceWork->sphere.offset[2];
                end[0] = traceWork->end[0] + traceWork->sphere.offset[0];
                end[1] = traceWork->end[1] + traceWork->sphere.offset[1];
                end[2] = traceWork->end[2] + traceWork->sphere.offset[2];
            }

            startDistance =
                (((start[0] * plane->normal[0]) +
                  (start[1] * plane->normal[1])) +
                 (start[2] * plane->normal[2])) -
                planeDist;
            endDistance =
                (((end[0] * plane->normal[0]) +
                  (end[1] * plane->normal[1])) +
                 (end[2] * plane->normal[2])) -
                planeDist;
        } else {
            const float *offset = traceWork->offsets[plane->signbits];
#if EMULATE_X87
            /* planeDist and each distance are single 80-bit chains rounded
             * once at their float slots. */
            const float planeDist = x87f_store_f32(x87f_sub(
                x87f_load_f32(plane->dist),
                x87f_add(x87f_add(x87f_mul(x87f_load_f32(offset[0]),
                                           x87f_load_f32(plane->normal[0])),
                                  x87f_mul(x87f_load_f32(offset[1]),
                                           x87f_load_f32(plane->normal[1]))),
                         x87f_mul(x87f_load_f32(offset[2]),
                                  x87f_load_f32(plane->normal[2])))));

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

            startDistance =
                (((traceWork->start[0] * plane->normal[0]) +
                  (traceWork->start[1] * plane->normal[1])) +
                 (traceWork->start[2] * plane->normal[2])) -
                planeDist;
            endDistance =
                (((traceWork->end[0] * plane->normal[0]) +
                  (traceWork->end[1] * plane->normal[1])) +
                 (traceWork->end[2] * plane->normal[2])) -
                planeDist;
#endif
        }

        if (startDistance > 0.0f) {
#if EMULATE_X87
            const float fractionDelta = x87f_store_f32(
                x87f_sub(x87f_load_f32(startDistance),
                         x87f_load_f32(endDistance)));
#else
            const float fractionDelta = startDistance - endDistance;
#endif

            if (endDistance > 0.0f) {
                if (fractionDelta <= 0.0f) {
                    return;
                }
                if (endDistance >= 0.125f) {
                    return;
                }
                allSolid = 0;
            }

            /* As in the axial pass: startDistance - 0.125f is computed once
             * into a float slot and reused by both the compare and the divide. */
#if EMULATE_X87
            const float enterLimit = x87f_store_f32(
                x87f_sub(x87f_load_f32(startDistance), x87f_load_f32(0.125f)));

            if (x87f_lt(x87f_mul(x87f_load_f32(enterFraction),
                                 x87f_load_f32(fractionDelta)),
                        x87f_load_f32(enterLimit))) {
                enterFraction = x87f_store_f32(
                    x87f_div(x87f_load_f32(enterLimit),
                             x87f_load_f32(fractionDelta)));
                if (leaveFraction <= enterFraction) {
                    return;
                }
            } else if (hitSide != NULL) {
                continue;
            }
#else
            const float enterLimit = startDistance - 0.125f;

            if (enterFraction * fractionDelta < enterLimit) {
                enterFraction = enterLimit / fractionDelta;
                if (leaveFraction <= enterFraction) {
                    return;
                }
            } else if (hitSide != NULL) {
                continue;
            }
#endif

            hitSide = side;
        } else if (endDistance > 0.0f) {
#if EMULATE_X87
            const float fractionDelta = x87f_store_f32(
                x87f_sub(x87f_load_f32(startDistance),
                         x87f_load_f32(endDistance)));

            allSolid = 0;
            if (x87f_lt(x87f_mul(x87f_load_f32(leaveFraction),
                                 x87f_load_f32(fractionDelta)),
                        x87f_load_f32(startDistance))) {
                leaveFraction = x87f_store_f32(
                    x87f_div(x87f_load_f32(startDistance),
                             x87f_load_f32(fractionDelta)));
                if (leaveFraction <= enterFraction) {
                    return;
                }
            }
#else
            const float fractionDelta = startDistance - endDistance;

            allSolid = 0;
            if (leaveFraction * fractionDelta < startDistance) {
                leaveFraction = startDistance / fractionDelta;
                if (leaveFraction <= enterFraction) {
                    return;
                }
            }
#endif
        }
    }

    traceWork->trace.contents = brush->contents;
    if (hitSide == NULL) {
        traceWork->trace.startsolid = 1;
        if (allSolid != 0) {
            traceWork->trace.allsolid = 1;
            traceWork->trace.fraction = 0.0f;
        }
        return;
    }

    traceWork->trace.fraction = enterFraction;
    traceWork->trace.normal[0] = hitSide->plane->normal[0];
    traceWork->trace.normal[1] = hitSide->plane->normal[1];
    traceWork->trace.normal[2] = hitSide->plane->normal[2];
    traceWork->trace.surfaceFlags =
        cm_materials[hitSide->materialIndex].surfaceFlags;
    traceWork->trace.material =
        cm_materials[hitSide->materialIndex].shader;
}
