#include <math.h>

#include "cm_terrain_private.h"

qboolean CM_SightTraceThroughTerrainCollide(
    const traceWork_t *traceWork,
    const patchCollide_t *patchCollide)
{
    if (traceWork->isPoint != 0) {
        return CM_SightTracePointThroughTerrainCollide(traceWork,
                                                       patchCollide);
    }

    const facet_t *facet = patchCollide->facets;
    for (int32_t facetIndex = 0; facetIndex < patchCollide->numFacets;
         ++facetIndex, ++facet) {
        float enterFraction = -1.0f;
        float leaveFraction = 1.0f;
        int32_t hitChildIndex = -1;

        const patchPlane_t *plane =
            &patchCollide->planes[facet->surfacePlane];
        vec4_t clipPlane = {plane->normal[0], plane->normal[1],
                            plane->normal[2], plane->dist};
        vec3_t start;
        vec3_t end;

        if (traceWork->sphere.use == 0) {
            const float *offset = traceWork->offsets[plane->signbits];
            const float planeOffset =
                ((offset[0] * clipPlane[0]) +
                 (offset[1] * clipPlane[1])) +
                (offset[2] * clipPlane[2]);
            clipPlane[3] -= planeOffset;
            start[0] = traceWork->start[0];
            start[1] = traceWork->start[1];
            start[2] = traceWork->start[2];
            end[0] = traceWork->end[0];
            end[1] = traceWork->end[1];
            end[2] = traceWork->end[2];
        } else {
            clipPlane[3] += traceWork->sphere.radius;
            const float sphereOffset =
                ((clipPlane[0] * traceWork->sphere.offset[0]) +
                 (clipPlane[1] * traceWork->sphere.offset[1])) +
                (clipPlane[2] * traceWork->sphere.offset[2]);

            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];
            }
        }

        int32_t hitPlaneChanged;
        if (CM_TerrainTraceThroughPlane(clipPlane, start, end, &enterFraction,
                                        &leaveFraction,
                                        &hitPlaneChanged) == 0) {
            continue;
        }

        int32_t childIndex;
        for (childIndex = 0; childIndex < facet->numBorders;
             ++childIndex) {
            plane = &patchCollide->planes[facet->borderPlanes[childIndex]];

            if (facet->borderInward[childIndex] == 0) {
                clipPlane[0] = plane->normal[0];
                clipPlane[1] = plane->normal[1];
                clipPlane[2] = plane->normal[2];
                clipPlane[3] = plane->dist;
            } else {
                clipPlane[0] = -plane->normal[0];
                clipPlane[1] = -plane->normal[1];
                clipPlane[2] = -plane->normal[2];
                clipPlane[3] = -plane->dist;
            }

            if (traceWork->sphere.use == 0) {
                const float *offset =
                    traceWork->offsets[plane->signbits];
                const float planeOffset =
                    ((offset[0] * clipPlane[0]) +
                     (offset[1] * clipPlane[1])) +
                    (offset[2] * clipPlane[2]);
                clipPlane[3] += fabsf(planeOffset);
                start[0] = traceWork->start[0];
                start[1] = traceWork->start[1];
                start[2] = traceWork->start[2];
                end[0] = traceWork->end[0];
                end[1] = traceWork->end[1];
                end[2] = traceWork->end[2];
            } else {
                clipPlane[3] += traceWork->sphere.radius;
                const float sphereOffset =
                    ((clipPlane[0] * traceWork->sphere.offset[0]) +
                     (clipPlane[1] * traceWork->sphere.offset[1])) +
                    (clipPlane[2] * traceWork->sphere.offset[2]);

                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];
                }
            }

            if (CM_TerrainTraceThroughPlane(clipPlane, start, end,
                                            &enterFraction, &leaveFraction,
                                            &hitPlaneChanged) == 0) {
                break;
            }

            if (hitPlaneChanged != 0) {
                hitChildIndex = childIndex;
            }
        }

        if (childIndex >= facet->numBorders &&
            hitChildIndex != facet->numBorders - 1 &&
            enterFraction < leaveFraction && enterFraction >= 0.0f) {
            return 0;
        }
    }

    return 1;
}
